1 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb775498%28v=vs.85%29.aspx
2 
3 // me@arsd:~/.kde/share/config$ vim kdeglobals
4 
5 // FIXME: i kinda like how you can show find locations in scrollbars in the chrome browisers i wanna support that here too.
6 
7 // for responsive design, a collapsible widget that if it doesn't have enough room, it just automatically becomes a "more" button or whatever.
8 
9 // responsive minigui, menu search, and file open with a preview hook on the side.
10 
11 // FIXME: add menu checkbox and menu icon eventually
12 
13 /*
14 
15 im tempted to add some css kind of thing to minigui. i've not done in the past cuz i have a lot of virtual functins i use but i think i have an evil plan
16 
17 the virtual functions remain as the default calculated values. then the reads go through some proxy object that can override it...
18 */
19 
20 // FIXME: a popup with slightly shaped window pointing at the mouse might eb useful in places
21 
22 // FIXME: text label must be copyable to the clipboard, at least as a full chunk.
23 
24 // FIXME: opt-in file picker widget with image support
25 
26 // FIXME: number widget
27 
28 // https://www.codeguru.com/cpp/controls/buttonctrl/advancedbuttons/article.php/c5161/Native-Win32-ThemeAware-OwnerDraw-Controls-No-MFC.htm
29 // https://docs.microsoft.com/en-us/windows/win32/controls/using-visual-styles
30 
31 // osx style menu search.
32 
33 // would be cool for a scroll bar to have marking capabilities
34 // kinda like vim's marks just on clicks etc and visual representation
35 // generically. may be cool to add an up arrow to the bottom too
36 //
37 // leave a shadow of where you last were for going back easily
38 
39 // So a window needs to have a selection, and that can be represented by a type. This is manipulated by various
40 // functions like cut, copy, paste. Widgets can have a selection and that would assert teh selection ownership for
41 // the window.
42 
43 // so what about context menus?
44 
45 // https://docs.microsoft.com/en-us/windows/desktop/Controls/about-custom-draw
46 
47 // FIXME: make the scroll thing go to bottom when the content changes.
48 
49 // add a knob slider view... you click and go up and down so basically same as a vertical slider, just presented as a round image
50 
51 // FIXME: the scroll area MUST be fixed to use the proper apis under the hood.
52 
53 
54 // FIXME: add a command search thingy built in and implement tip.
55 // FIXME: omg omg what if menu functions have arguments and it can pop up a gui or command line script them?!
56 
57 // On Windows:
58 // FIXME: various labels look broken in high contrast mode
59 // FIXME: changing themes while the program is upen doesn't trigger a redraw
60 
61 // add note about manifest to documentation. also icons.
62 
63 // a pager control is just a horizontal scroll area just with arrows on the sides instead of a scroll bar
64 // FIXME: clear the corner of scrollbars if they pop up
65 
66 // minigui needs to have a stdout redirection for gui mode on windows writeln
67 
68 // I kinda wanna do state reacting. sort of. idk tho
69 
70 // need a viewer widget that works like a web page - arrows scroll down consistently
71 
72 // I want a nanovega widget, and a svg widget with some kind of event handlers attached to the inside.
73 
74 // FIXME: the menus should be a bit more discoverable, at least a single click to open the others instead of two.
75 // and help info about menu items.
76 // and search in menus?
77 
78 // FIXME: a scroll area event signaling when a thing comes into view might be good
79 // FIXME: arrow key navigation and accelerators in dialog boxes will be a must
80 
81 // FIXME: unify Windows style line endings
82 
83 /*
84 	TODO:
85 
86 	pie menu
87 
88 	class Form with submit behavior -- see AutomaticDialog
89 
90 	disabled widgets and menu items
91 
92 	event cleanup
93 	tooltips.
94 	api improvements
95 
96 	margins are kinda broken, they don't collapse like they should. at least.
97 
98 	a table form btw would be a horizontal layout of vertical layouts holding each column
99 	that would give the same width things
100 */
101 
102 /*
103 
104 1(15:19:48) NotSpooky: Menus, text entry, label, notebook, box, frame, file dialogs and layout (this one is very useful because I can draw lines between its child widgets
105 */
106 
107 /++
108 	minigui is a smallish GUI widget library, aiming to be on par with at least
109 	HTML4 forms and a few other expected gui components. It uses native controls
110 	on Windows and does its own thing on Linux (Mac is not currently supported but
111 	may be later, and should use native controls) to keep size down. The Linux
112 	appearance is similar to Windows 95 and avoids using images to maintain network
113 	efficiency on remote X connections, though you can customize that.
114 
115 
116 	minigui's only required dependencies are [arsd.simpledisplay] and [arsd.color],
117 	on which it is built. simpledisplay provides the low-level interfaces and minigui
118 	builds the concept of widgets inside the windows on top of it.
119 
120 	Its #1 goal is to be useful without being large and complicated like GTK and Qt.
121 	It isn't hugely concerned with appearance - on Windows, it just uses the native
122 	controls and native theme, and on Linux, it keeps it simple and I may change that
123 	at any time, though after May 2021, you can customize some things with css-inspired
124 	[Widget.Style] classes. (On Windows, if you compile with `-version=custom_widgets`,
125 	you can use the custom implementation there too, but... you shouldn't.)
126 
127 	The event model is similar to what you use in the browser with Javascript and the
128 	layout engine tries to automatically fit things in, similar to a css flexbox.
129 
130 	FOR BEST RESULTS: be sure to link with the appropriate subsystem command
131 	`-L/SUBSYSTEM:WINDOWS:5.0`, for example, because otherwise you'll get a
132 	console and other visual bugs.
133 
134 	HTML_To_Classes:
135 	$(SMALL_TABLE
136 		HTML Code | Minigui Class
137 
138 		`<input type="text">` | [LineEdit]
139 		`<textarea>` | [TextEdit]
140 		`<select>` | [DropDownSelection]
141 		`<input type="checkbox">` | [Checkbox]
142 		`<input type="radio">` | [Radiobox]
143 		`<button>` | [Button]
144 	)
145 
146 
147 	Stretchiness:
148 		The default is 4. You can use larger numbers for things that should
149 		consume a lot of space, and lower numbers for ones that are better at
150 		smaller sizes.
151 
152 	Overlapped_input:
153 		COMING EVENTUALLY:
154 		minigui will include a little bit of I/O functionality that just works
155 		with the event loop. If you want to get fancy, I suggest spinning up
156 		another thread and posting events back and forth.
157 
158 	$(H2 Add ons)
159 		See the `minigui_addons` directory in the arsd repo for some add on widgets
160 		you can import separately too.
161 
162 	$(H3 XML definitions)
163 		If you use [arsd.minigui_xml], you can create widget trees from XML at runtime.
164 
165 	$(H3 Scriptability)
166 		minigui is compatible with [arsd.script]. If you see `@scriptable` on a method
167 		in this documentation, it means you can call it from the script language.
168 
169 		Tip: to allow easy creation of widget trees from script, import [arsd.minigui_xml]
170 		and make [arsd.minigui_xml.makeWidgetFromString] available to your script:
171 
172 		---
173 		import arsd.minigui_xml;
174 		import arsd.script;
175 
176 		var globals = var.emptyObject;
177 		globals.makeWidgetFromString = &makeWidgetFromString;
178 
179 		// this now works
180 		interpret(`var window = makeWidgetFromString("<MainWindow />");`, globals);
181 		---
182 
183 		More to come.
184 
185 	History:
186 		Minigui had mostly additive changes or bug fixes since its inception until May 2021.
187 
188 		In May 2021 (dub v10.0), minigui got an overhaul. If it was versioned independently, I'd
189 		tag this as version 2.0.
190 
191 		Among the changes:
192 		$(LIST
193 			* The event model changed to prefer strongly-typed events, though the Javascript string style ones still work, using properties off them is deprecated. It will still compile and function, but you should change the handler to use the classes in its argument list. I adapted my code to use the new model in just a few minutes, so it shouldn't too hard.
194 
195 			See [Event] for details.
196 
197 			* A [DoubleClickEvent] was added. Previously, you'd get two rapidly repeated click events. Now, you get one click event followed by a double click event. If you must recreate the old way exactly, you can listen for a DoubleClickEvent, set a flag upon receiving one, then send yourself a synthetic ClickEvent on the next MouseUpEvent, but your program might be better served just working with [MouseDownEvent]s instead.
198 
199 			See [DoubleClickEvent] for details.
200 
201 			* Styling hints were added, and the few that existed before have been moved to a new helper class. Deprecated forwarders exist for the (few) old properties to help you transition. Note that most of these only affect a `custom_events` build, which is the default on Linux, but opt in only on Windows.
202 
203 			See [Widget.Style] for details.
204 
205 			// * A widget must now opt in to receiving keyboard focus, rather than opting out.
206 
207 			* Widgets now draw their keyboard focus by default instead of opt in. You may wish to set `tabStop = false;` if it wasn't supposed to receive it.
208 
209 			* Most Widget constructors no longer have a default `parent` argument. You must pass the parent to almost all widgets, or in rare cases, an explict `null`, but more often than not, you need the parent so the default argument was not very useful at best and misleading to a crash at worst.
210 
211 			* [LabeledLineEdit] changed its default layout to vertical instead of horizontal. You can restore the old behavior by passing a `TextAlignment` argument to the constructor.
212 
213 			* Several conversions of public fields to properties, deprecated, or made private. It is unlikely this will affect you, but the compiler will tell you if it does.
214 
215 			* Various non-breaking additions.
216 		)
217 +/
218 module arsd.minigui;
219 
220 import arsd.core;
221 
222 /++
223 	This hello world sample will have an oversized button, but that's ok, you see your first window!
224 +/
225 version(Demo)
226 unittest {
227 	import arsd.minigui;
228 
229 	void main() {
230 		auto window = new MainWindow();
231 
232 		auto hello = new TextLabel("Hello, world!", TextAlignment.Center, window);
233 		auto button = new Button("Close", window);
234 		button.addWhenTriggered({
235 			window.close();
236 		});
237 
238 		window.loop();
239 	}
240 
241 	main(); // exclude from docs
242 }
243 
244 /++
245 	This example shows one way you can partition your window into a header
246 	and sidebar. Here, the header and sidebar have a fixed width, while the
247 	rest of the content sizes with the window.
248 
249 	It might be a new way of thinking about window layout to do things this
250 	way - perhaps [GridLayout] more matches your style of thought - but the
251 	concept here is to partition the window into sub-boxes with a particular
252 	size, then partition those boxes into further boxes.
253 
254 	$(IMG //arsdnet.net/minigui-screenshots/windows/layout.png, The example window has a header across the top, then below it a sidebar to the left and a content area to the right.)
255 
256 	So to make the header, start with a child layout that has a max height.
257 	It will use that space from the top, then the remaining children will
258 	split the remaining area, meaning you can think of is as just being another
259 	box you can split again. Keep splitting until you have the look you desire.
260 +/
261 // https://github.com/adamdruppe/arsd/issues/310
262 version(minigui_screenshots)
263 @Screenshot("layout")
264 unittest {
265 	import arsd.minigui;
266 
267 	// This helper class is just to help make the layout boxes visible.
268 	// think of it like a <div style="background-color: whatever;"></div> in HTML.
269 	class ColorWidget : Widget {
270 		this(Color color, Widget parent) {
271 			this.color = color;
272 			super(parent);
273 		}
274 		Color color;
275 		class Style : Widget.Style {
276 			override WidgetBackground background() { return WidgetBackground(color); }
277 		}
278 		mixin OverrideStyle!Style;
279 	}
280 
281 	void main() {
282 		auto window = new Window;
283 
284 		// the key is to give it a max height. This is one way to do it:
285 		auto header = new class HorizontalLayout {
286 			this() { super(window); }
287 			override int maxHeight() { return 50; }
288 		};
289 		// this next line is a shortcut way of doing it too, but it only works
290 		// for HorizontalLayout and VerticalLayout, and is less explicit, so it
291 		// is good to know how to make a new class like above anyway.
292 		// auto header = new HorizontalLayout(50, window);
293 
294 		auto bar = new HorizontalLayout(window);
295 
296 		// or since this is so common, VerticalLayout and HorizontalLayout both
297 		// can just take an argument in their constructor for max width/height respectively
298 
299 		// (could have tone this above too, but I wanted to demo both techniques)
300 		auto left = new VerticalLayout(100, bar);
301 
302 		// and this is the main section's container. A plain Widget instance is good enough here.
303 		auto container = new Widget(bar);
304 
305 		// and these just add color to the containers we made above for the screenshot.
306 		// in a real application, you can just add your actual controls instead of these.
307 		auto headerColorBox = new ColorWidget(Color.teal, header);
308 		auto leftColorBox = new ColorWidget(Color.green, left);
309 		auto rightColorBox = new ColorWidget(Color.purple, container);
310 
311 		window.loop();
312 	}
313 
314 	main(); // exclude from docs
315 }
316 
317 
318 public import arsd.simpledisplay;
319 /++
320 	Convenience import to override the Windows GDI Rectangle function (you can still use it through fully-qualified imports)
321 
322 	History:
323 		Was private until May 15, 2021.
324 +/
325 public alias Rectangle = arsd.color.Rectangle; // I specifically want this in here, not the win32 GDI Rectangle()
326 
327 version(Windows) {
328 	import core.sys.windows.winnls;
329 	import core.sys.windows.windef;
330 	import core.sys.windows.basetyps;
331 	import core.sys.windows.winbase;
332 	import core.sys.windows.winuser;
333 	import core.sys.windows.wingdi;
334 	static import gdi = core.sys.windows.wingdi;
335 }
336 
337 version(Windows) {
338 	version(minigui_manifest) {} else version=minigui_no_manifest;
339 
340 	version(minigui_no_manifest) {} else
341 	static if(__VERSION__ >= 2_083)
342 	version(CRuntime_Microsoft) { // FIXME: mingw?
343 		// assume we want commctrl6 whenever possible since there's really no reason not to
344 		// and this avoids some of the manifest hassle
345 		pragma(linkerDirective, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"");
346 	}
347 }
348 
349 // this is a hack to call the original window procedure on native win32 widgets if our event listener thing prevents default.
350 private bool lastDefaultPrevented;
351 
352 /// Methods marked with this are available from scripts if added to the [arsd.script] engine.
353 alias scriptable = arsd_jsvar_compatible;
354 
355 version(Windows) {
356 	// use native widgets when available unless specifically asked otherwise
357 	version(custom_widgets) {
358 		enum bool UsingCustomWidgets = true;
359 		enum bool UsingWin32Widgets = false;
360 	} else {
361 		version = win32_widgets;
362 		enum bool UsingCustomWidgets = false;
363 		enum bool UsingWin32Widgets = true;
364 	}
365 	// and native theming when needed
366 	//version = win32_theming;
367 } else {
368 	enum bool UsingCustomWidgets = true;
369 	enum bool UsingWin32Widgets = false;
370 	version=custom_widgets;
371 }
372 
373 
374 
375 /*
376 
377 	The main goals of minigui.d are to:
378 		1) Provide basic widgets that just work in a lightweight lib.
379 		   I basically want things comparable to a plain HTML form,
380 		   plus the easy and obvious things you expect from Windows
381 		   apps like a menu.
382 		2) Use native things when possible for best functionality with
383 		   least library weight.
384 		3) Give building blocks to provide easy extension for your
385 		   custom widgets, or hooking into additional native widgets
386 		   I didn't wrap.
387 		4) Provide interfaces for easy interaction between third
388 		   party minigui extensions. (event model, perhaps
389 		   signals/slots, drop-in ease of use bits.)
390 		5) Zero non-system dependencies, including Phobos as much as
391 		   I reasonably can. It must only import arsd.color and
392 		   my simpledisplay.d. If you need more, it will have to be
393 		   an extension module.
394 		6) An easy layout system that generally works.
395 
396 	A stretch goal is to make it easy to make gui forms with code,
397 	some kind of resource file (xml?) and even a wysiwyg designer.
398 
399 	Another stretch goal is to make it easy to hook data into the gui,
400 	including from reflection. So like auto-generate a form from a
401 	function signature or struct definition, or show a list from an
402 	array that automatically updates as the array is changed. Then,
403 	your program focuses on the data more than the gui interaction.
404 
405 
406 
407 	STILL NEEDED:
408 		* combo box. (this is diff than select because you can free-form edit too. more like a lineedit with autoselect)
409 		* slider
410 		* listbox
411 		* spinner
412 		* label?
413 		* rich text
414 */
415 
416 
417 /+
418 	enum LayoutMethods {
419 		 verticalFlex,
420 		 horizontalFlex,
421 		 inlineBlock, // left to right, no stretch, goes to next line as needed
422 		 static, // just set to x, y
423 		 verticalNoStretch, // browser style default
424 
425 		 inlineBlockFlex, // goes left to right, flexing, but when it runs out of space, it spills into next line
426 
427 		 grid, // magic
428 	}
429 +/
430 
431 /++
432 	The `Widget` is the base class for minigui's functionality, ranging from UI components like checkboxes or text displays to abstract groupings of other widgets like a layout container or a html `<div>`. You will likely want to use pre-made widgets as well as creating your own.
433 
434 
435 	To create your own widget, you must inherit from it and create a constructor that passes a parent to `super`. Everything else after that is optional.
436 
437 	---
438 	class MinimalWidget : Widget {
439 		this(Widget parent) {
440 			super(parent);
441 		}
442 	}
443 	---
444 
445 	$(SIDEBAR
446 		I'm not entirely happy with leaf, container, and windows all coming from the same base Widget class, but I so far haven't thought of a better solution that's good enough to justify the breakage of a transition. It hasn't been a major problem in practice anyway.
447 	)
448 
449 	Broadly, there's two kinds of widgets: leaf widgets, which are intended to be the direct user-interactive components, and container widgets, which organize, lay out, and aggregate other widgets in the object tree. A special case of a container widget is [Window], which represents a separate top-level window on the screen. Both leaf and container widgets inherit from `Widget`, so this distinction is more conventional than formal.
450 
451 	Among the things you'll most likely want to change in your custom widget:
452 
453 	$(LIST
454 		* In your constructor, set `tabStop = false;` if the widget is not supposed to receive keyboard focus. (Please note its childen still can, so `tabStop = false;` is appropriate on most container widgets.)
455 
456 		You may explicitly set `tabStop = true;` to ensure you get it, even against future changes to the library, though that's the default right now.
457 
458 		Do this $(I after) calling the `super` constructor.
459 
460 		* Override [paint] if you want full control of the widget's drawing area (except the area obscured by children!), or [paintContent] if you want to participate in the styling engine's system. You'll also possibly want to make a subclass of [Style] and use [OverrideStyle] to change the default hints given to the styling engine for widget.
461 
462 		Generally, painting is a job for leaf widgets, since child widgets would obscure your drawing area anyway. However, it is your decision.
463 
464 		* Override default event handlers with your behavior. For example [defaultEventHandler_click] may be overridden to make clicks do something. Again, this is generally a job for leaf widgets rather than containers; most events are dispatched to the lowest leaf on the widget tree, but they also pass through all their parents. See [Event] for more details about the event model.
465 
466 		* You may also want to override the various layout hints like [minWidth], [maxHeight], etc. In particular [Padding] and [Margin] are often relevant for both container and leaf widgets and the default values of 0 are often not what you want.
467 	)
468 
469 	On Microsoft Windows, many widgets are also based on native controls. You can also do this if `static if(UsingWin32Widgets)` passes. You should use the helper function [createWin32Window] to create the window and let minigui do what it needs to do to create its bridge structures. This will populate [Widget.hwnd] which you can access later for communcating with the native window. You may also consider overriding [Widget.handleWmCommand] and [Widget.handleWmNotify] for the widget to translate those messages into appropriate minigui [Event]s.
470 
471 	It is also possible to embed a [SimpleWindow]-based native window inside a widget. See [OpenGlWidget]'s source code as an example.
472 
473 	Your own custom-drawn and native system controls can exist side-by-side.
474 
475 	Later I'll add more complete examples, but for now [TextLabel] and [LabeledPasswordEdit] are both simple widgets you can view implementation to get some ideas.
476 +/
477 class Widget : ReflectableProperties {
478 
479 	private bool willDraw() {
480 		return true;
481 	}
482 
483 	/+
484 	/++
485 		Calling this directly after constructor can give you a reflectable object as-needed so you don't pay for what you don't need.
486 
487 		History:
488 			Added September 15, 2021
489 			implemented.... ???
490 	+/
491 	void prepareReflection(this This)() {
492 
493 	}
494 	+/
495 
496 	private bool _enabled = true;
497 
498 	/++
499 		Determines whether the control is marked enabled. Disabled controls are generally displayed as greyed out and clicking on them does nothing. It is also possible for a control to be disabled because its parent is disabled, in which case this will still return `true`, but setting `enabled = true` may have no effect. Check [disabledBy] to see which parent caused it to be disabled.
500 
501 		I also recommend you set a [disabledReason] if you chose to set `enabled = false` to tell the user why the control does not work and what they can do to enable it.
502 
503 		History:
504 			Added November 23, 2021 (dub v10.4)
505 
506 			Warning: the specific behavior of disabling with parents may change in the future.
507 		Bugs:
508 			Currently only implemented for widgets backed by native Windows controls.
509 
510 		See_Also: [disabledReason], [disabledBy]
511 	+/
512 	@property bool enabled() {
513 		return disabledBy() is null;
514 	}
515 
516 	/// ditto
517 	@property void enabled(bool yes) {
518 		_enabled = yes;
519 		version(win32_widgets) {
520 			if(hwnd)
521 				EnableWindow(hwnd, yes);
522 		}
523 		setDynamicState(DynamicState.disabled, yes);
524 	}
525 
526 	private string disabledReason_;
527 
528 	/++
529 		If the widget is not [enabled] this string may be presented to the user when they try to use it. The exact manner and time it gets displayed is up to the implementation of the control.
530 
531 		Setting this does NOT disable the widget. You need to call `enabled = false;` separately. It does set the data though.
532 
533 		History:
534 			Added November 23, 2021 (dub v10.4)
535 		See_Also: [enabled], [disabledBy]
536 	+/
537 	@property string disabledReason() {
538 		auto w = disabledBy();
539 		return (w is null) ? null : w.disabledReason_;
540 	}
541 
542 	/// ditto
543 	@property void disabledReason(string reason) {
544 		disabledReason_ = reason;
545 	}
546 
547 	/++
548 		Returns the widget that disabled this. It might be this or one of its parents all the way up the chain, or `null` if the widget is not disabled by anything. You can check [disabledReason] on the return value (after the null check!) to get a hint to display to the user.
549 
550 		History:
551 			Added November 25, 2021 (dub v10.4)
552 		See_Also: [enabled], [disabledReason]
553 	+/
554 	Widget disabledBy() {
555 		Widget p = this;
556 		while(p) {
557 			if(!p._enabled)
558 				return p;
559 			p = p.parent;
560 		}
561 		return null;
562 	}
563 
564 	/// Implementations of [ReflectableProperties] interface. See the interface for details.
565 	SetPropertyResult setPropertyFromString(string name, scope const(char)[] value, bool valueIsJson) {
566 		if(valueIsJson)
567 			return SetPropertyResult.wrongFormat;
568 		switch(name) {
569 			case "name":
570 				this.name = value.idup;
571 				return SetPropertyResult.success;
572 			case "statusTip":
573 				this.statusTip = value.idup;
574 				return SetPropertyResult.success;
575 			default:
576 				return SetPropertyResult.noSuchProperty;
577 		}
578 	}
579 	/// ditto
580 	void getPropertiesList(scope void delegate(string name) sink) const {
581 		sink("name");
582 		sink("statusTip");
583 	}
584 	/// ditto
585 	void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink) {
586 		switch(name) {
587 			case "name":
588 				sink(name, this.name, false);
589 				return;
590 			case "statusTip":
591 				sink(name, this.statusTip, false);
592 				return;
593 			default:
594 				sink(name, null, true);
595 		}
596 	}
597 
598 	/++
599 		Scales the given value to the system-reported DPI for the monitor on which the widget resides.
600 
601 		History:
602 			Added November 25, 2021 (dub v10.5)
603 			`Point` overload added January 12, 2022 (dub v10.6)
604 	+/
605 	int scaleWithDpi(int value, int assumedDpi = 96) {
606 		// avoid potential overflow with common special values
607 		if(value == int.max)
608 			return int.max;
609 		if(value == int.min)
610 			return int.min;
611 		if(value == 0)
612 			return 0;
613 
614 		auto divide = (parentWindow && parentWindow.win) ? parentWindow.win.actualDpi : assumedDpi;
615 		//divide = 138;
616 		// for lower values it is something i don't really want changed anyway since it is an old monitor and you don't want to scale down.
617 		// this also covers the case when actualDpi returns 0.
618 		if(divide < 96)
619 			divide = 96;
620 		return value * divide / assumedDpi;
621 	}
622 
623 	/// ditto
624 	Point scaleWithDpi(Point value, int assumedDpi = 96) {
625 		return Point(scaleWithDpi(value.x, assumedDpi), scaleWithDpi(value.y, assumedDpi));
626 	}
627 
628 	// avoid this it just forwards to a soon-to-be-deprecated function and is not remotely stable
629 	// I'll think up something better eventually
630 	protected final int defaultLineHeight() {
631 		auto cs = getComputedStyle();
632 		if(cs.font && !cs.font.isNull)
633 			return cs.font.height() * 5 / 4;
634 		else
635 			return scaleWithDpi(Window.lineHeight * 5/4);
636 	}
637 
638 	protected final int defaultTextWidth(const(char)[] text) {
639 		auto cs = getComputedStyle();
640 		if(cs.font && !cs.font.isNull)
641 			return cs.font.stringWidth(text);
642 		else
643 			return scaleWithDpi(Window.lineHeight * cast(int) text.length / 2);
644 	}
645 
646 	/++
647 		If `encapsulatedChildren` returns true, it changes the event handling mechanism to act as if events from the child widgets are actually targeted on this widget.
648 
649 		The idea is then you can use child widgets as part of your implementation, but not expose those details through the event system; if someone checks the mouse coordinates and target of the event once it bubbles past you, it will show as it it came from you.
650 
651 		History:
652 			Added May 22, 2021
653 	+/
654 	protected bool encapsulatedChildren() {
655 		return false;
656 	}
657 
658 	private void privateDpiChanged() {
659 		dpiChanged();
660 		foreach(child; children)
661 			child.privateDpiChanged();
662 	}
663 
664 	/++
665 		Virtual hook to update any caches or fonts you need on the event of a dpi scaling change.
666 
667 		History:
668 			Added January 12, 2022 (dub v10.6)
669 	+/
670 	protected void dpiChanged() {
671 
672 	}
673 
674 	// Default layout properties {
675 
676 		int minWidth() { return 0; }
677 		int minHeight() {
678 			// default widgets have a vertical layout, therefore the minimum height is the sum of the contents
679 			int sum = this.paddingTop + this.paddingBottom;
680 			foreach(child; children) {
681 				if(child.hidden)
682 					continue;
683 				sum += child.minHeight();
684 				sum += child.marginTop();
685 				sum += child.marginBottom();
686 			}
687 
688 			return sum;
689 		}
690 		int maxWidth() { return int.max; }
691 		int maxHeight() { return int.max; }
692 		int widthStretchiness() { return 4; }
693 		int heightStretchiness() { return 4; }
694 
695 		/++
696 			Where stretchiness will grow from the flex basis, this shrinkiness will let it get smaller if needed to make room for other items.
697 
698 			History:
699 				Added June 15, 2021 (dub v10.1)
700 		+/
701 		int widthShrinkiness() { return 0; }
702 		/// ditto
703 		int heightShrinkiness() { return 0; }
704 
705 		/++
706 			The initial size of the widget for layout calculations. Default is 0.
707 
708 			See_Also: [https://developer.mozilla.org/en-US/docs/Web/CSS/flex-basis|CSS flex-basis]
709 
710 			History:
711 				Added June 15, 2021 (dub v10.1)
712 		+/
713 		int flexBasisWidth() { return 0; }
714 		/// ditto
715 		int flexBasisHeight() { return 0; }
716 
717 		/++
718 			Not stable.
719 
720 			Values are scaled with dpi after assignment. If you override the virtual functions, this may be ignored.
721 
722 			So if you set defaultPadding to 4 and the user is on 150% zoom, it will multiply to return 6.
723 
724 			History:
725 				Added January 5, 2023
726 		+/
727 		Rectangle defaultMargin;
728 		/// ditto
729 		Rectangle defaultPadding;
730 
731 		int marginLeft() { return scaleWithDpi(defaultMargin.left); }
732 		int marginRight() { return scaleWithDpi(defaultMargin.right); }
733 		int marginTop() { return scaleWithDpi(defaultMargin.top); }
734 		int marginBottom() { return scaleWithDpi(defaultMargin.bottom); }
735 		int paddingLeft() { return scaleWithDpi(defaultPadding.left); }
736 		int paddingRight() { return scaleWithDpi(defaultPadding.right); }
737 		int paddingTop() { return scaleWithDpi(defaultPadding.top); }
738 		int paddingBottom() { return scaleWithDpi(defaultPadding.bottom); }
739 		//LinePreference linePreference() { return LinePreference.PreferOwnLine; }
740 
741 		private bool recomputeChildLayoutRequired = true;
742 		private static class RecomputeEvent {}
743 		private __gshared rce = new RecomputeEvent();
744 		protected final void queueRecomputeChildLayout() {
745 			recomputeChildLayoutRequired = true;
746 
747 			if(this.parentWindow) {
748 				auto sw = this.parentWindow.win;
749 				assert(sw !is null);
750 				if(!sw.eventQueued!RecomputeEvent) {
751 					sw.postEvent(rce);
752 					// import std.stdio; writeln("redraw requested from ", file,":",line," ", this.parentWindow.win.impl.window);
753 				}
754 			}
755 
756 		}
757 
758 		protected final void recomputeChildLayoutEntry() {
759 			if(recomputeChildLayoutRequired) {
760 				recomputeChildLayout();
761 				recomputeChildLayoutRequired = false;
762 				redraw();
763 			} else {
764 				// I still need to check the tree just in case one of them was queued up
765 				// and the event came up here instead of there.
766 				foreach(child; children)
767 					child.recomputeChildLayoutEntry();
768 			}
769 		}
770 
771 		// this function should (almost) never be called directly anymore... call recomputeChildLayoutEntry when executing it and queueRecomputeChildLayout if you just want it done soon
772 		void recomputeChildLayout() {
773 			.recomputeChildLayout!"height"(this);
774 		}
775 
776 	// }
777 
778 
779 	/++
780 		Returns the style's tag name string this object uses.
781 
782 		The default is to use the typeid() name trimmed down to whatever is after the last dot which is typically the identifier of the class.
783 
784 		This tag may never be used, it is just available for the [VisualTheme.getPropertyString] if it chooses to do something like CSS.
785 
786 		History:
787 			Added May 10, 2021
788 	+/
789 	string styleTagName() const {
790 		string n = typeid(this).name;
791 		foreach_reverse(idx, ch; n)
792 			if(ch == '.') {
793 				n = n[idx + 1 .. $];
794 				break;
795 			}
796 		return n;
797 	}
798 
799 	/// API for the [styleClassList]
800 	static struct ClassList {
801 		private Widget widget;
802 
803 		///
804 		void add(string s) {
805 			widget.styleClassList_ ~= s;
806 		}
807 
808 		///
809 		void remove(string s) {
810 			foreach(idx, s1; widget.styleClassList_)
811 				if(s1 == s) {
812 					widget.styleClassList_[idx] = widget.styleClassList_[$-1];
813 					widget.styleClassList_ = widget.styleClassList_[0 .. $-1];
814 					widget.styleClassList_.assumeSafeAppend();
815 					return;
816 				}
817 		}
818 
819 		/// Returns true if it was added, false if it was removed.
820 		bool toggle(string s) {
821 			if(contains(s)) {
822 				remove(s);
823 				return false;
824 			} else {
825 				add(s);
826 				return true;
827 			}
828 		}
829 
830 		///
831 		bool contains(string s) const {
832 			foreach(s1; widget.styleClassList_)
833 				if(s1 == s)
834 					return true;
835 			return false;
836 
837 		}
838 	}
839 
840 	private string[] styleClassList_;
841 
842 	/++
843 		Returns a "class list" that can be used by the visual theme's style engine via [VisualTheme.getPropertyString] if it chooses to do something like CSS.
844 
845 		It has no inherent meaning, it is really just a place to put some metadata tags on individual objects.
846 
847 		History:
848 			Added May 10, 2021
849 	+/
850 	inout(ClassList) styleClassList() inout {
851 		return cast(inout(ClassList)) ClassList(cast() this);
852 	}
853 
854 	/++
855 		List of dynamic states made available to the style engine, for cases like CSS pseudo-classes and also used by default paint methods. It is stored in a 64 bit variable attached to the widget that you can update. The style cache is aware of the fact that these can frequently change.
856 
857 		The lower 32 bits are defined here or reserved for future use by the library. You should keep these updated if you reasonably can on custom widgets if they apply to you, but don't use them for a purpose they aren't defined for.
858 
859 		The upper 32 bits are available for your own extensions.
860 
861 		History:
862 			Added May 10, 2021
863 	+/
864 	enum DynamicState : ulong {
865 		focus = (1 << 0), /// the widget currently has the keyboard focus
866 		hover = (1 << 1), /// the mouse is currently hovering over the widget (may not always be updated)
867 		valid = (1 << 2), /// the widget's content has been validated and it passed (do not set if not validation has been performed!)
868 		invalid = (1 << 3), /// the widget's content has been validated and it failed (do not set if not validation has been performed!)
869 		checked = (1 << 4), /// the widget is toggleable and currently toggled on
870 		selected = (1 << 5), /// the widget represents one option of many and is currently selected, but is not necessarily focused nor checked.
871 		disabled = (1 << 6), /// the widget is currently unable to perform its designated task
872 		indeterminate = (1 << 7), /// the widget has tri-state and is between checked and not checked
873 		depressed = (1 << 8), /// the widget is being actively pressed or clicked (compare to css `:active`). Can be combined with hover to visually indicate if a mouse up would result in a click event.
874 
875 		USER_BEGIN = (1UL << 32),
876 	}
877 
878 	// I want to add the primary and cancel styles to buttons at least at some point somehow.
879 
880 	/// ditto
881 	@property ulong dynamicState() { return dynamicState_; }
882 	/// ditto
883 	@property ulong dynamicState(ulong newValue) {
884 		if(dynamicState != newValue) {
885 			auto old = dynamicState_;
886 			dynamicState_ = newValue;
887 
888 			useStyleProperties((scope Widget.Style s) {
889 				if(s.variesWithState(old ^ newValue))
890 					redraw();
891 			});
892 		}
893 		return dynamicState_;
894 	}
895 
896 	/// ditto
897 	void setDynamicState(ulong flags, bool state) {
898 		auto ds = dynamicState_;
899 		if(state)
900 			ds |= flags;
901 		else
902 			ds &= ~flags;
903 
904 		dynamicState = ds;
905 	}
906 
907 	private ulong dynamicState_;
908 
909 	deprecated("Use dynamic styles instead now") {
910 		Color backgroundColor() { return backgroundColor_; }
911 		void backgroundColor(Color c){ this.backgroundColor_ = c; }
912 
913 		MouseCursor cursor() { return GenericCursor.Default; }
914 	} private Color backgroundColor_ = Color.transparent;
915 
916 
917 	/++
918 		Style properties are defined as an accessory class so they can be referenced and overridden independently, but they are nested so you can refer to them easily by name (e.g. generic `Widget.Style` vs `Button.Style` and such).
919 
920 		It is here so there can be a specificity switch.
921 
922 		See [OverrideStyle] for a helper function to use your own.
923 
924 		History:
925 			Added May 11, 2021
926 	+/
927 	static class Style/* : StyleProperties*/ {
928 		public Widget widget; // public because the mixin template needs access to it
929 
930 		/++
931 			You must override this to trigger automatic redraws if you ever uses the `dynamicState` flag in your style.
932 
933 			History:
934 				Added May 11, 2021, but changed on July 2, 2021 to return false by default. You MUST override this if you want declarative hover effects etc to take effect.
935 		+/
936 		bool variesWithState(ulong dynamicStateFlags) {
937 			version(win32_widgets) {
938 				if(widget.hwnd)
939 					return false;
940 			}
941 			return widget.tabStop && ((dynamicStateFlags & DynamicState.focus) ? true : false);
942 		}
943 
944 		///
945 		Color foregroundColor() {
946 			return WidgetPainter.visualTheme.foregroundColor;
947 		}
948 
949 		///
950 		WidgetBackground background() {
951 			// the default is a "transparent" background, which means
952 			// it goes as far up as it can to get the color
953 			if (widget.backgroundColor_ != Color.transparent)
954 				return WidgetBackground(widget.backgroundColor_);
955 			if (widget.parent)
956 				return widget.parent.getComputedStyle.background;
957 			return WidgetBackground(widget.backgroundColor_);
958 		}
959 
960 		private static OperatingSystemFont fontCached_;
961 		private OperatingSystemFont fontCached() {
962 			if(fontCached_ is null)
963 				fontCached_ = font();
964 			return fontCached_;
965 		}
966 
967 		/++
968 			Returns the default font to be used with this widget. The return value will be cached by the library, so you can not expect live updates.
969 		+/
970 		OperatingSystemFont font() {
971 			return null;
972 		}
973 
974 		/++
975 			Returns the cursor that should be used over this widget. You may change this and updates will be reflected next time the mouse enters the widget.
976 
977 			You can return a member of [GenericCursor] or your own [MouseCursor] instance.
978 
979 			History:
980 				Was previously a method directly on [Widget], moved to [Widget.Style] on May 12, 2021
981 		+/
982 		MouseCursor cursor() {
983 			return GenericCursor.Default;
984 		}
985 
986 		FrameStyle borderStyle() {
987 			return FrameStyle.none;
988 		}
989 
990 		/++
991 		+/
992 		Color borderColor() {
993 			return Color.transparent;
994 		}
995 
996 		FrameStyle outlineStyle() {
997 			if(widget.dynamicState & DynamicState.focus)
998 				return FrameStyle.dotted;
999 			else
1000 				return FrameStyle.none;
1001 		}
1002 
1003 		Color outlineColor() {
1004 			return foregroundColor;
1005 		}
1006 	}
1007 
1008 	/++
1009 		This mixin overrides the [useStyleProperties] method to direct it toward your own style class.
1010 		The basic usage is simple:
1011 
1012 		---
1013 		static class Style : YourParentClass.Style { /* YourParentClass is frequently Widget, of course, but not always */
1014 			// override style hints as-needed here
1015 		}
1016 		OverrideStyle!Style; // add the method
1017 		---
1018 
1019 		$(TIP
1020 			While the class is not forced to be `static`, for best results, it should be. A non-static class
1021 			can not be inherited by other objects whereas the static one can. A property on the base class,
1022 			called [Widget.Style.widget|widget], is available for you to access its properties.
1023 		)
1024 
1025 		This exists just because [useStyleProperties] has a somewhat convoluted signature and its overrides must
1026 		repeat them. Moreover, its implementation uses a stack class to optimize GC pressure from small fetches
1027 		and that's a little tedious to repeat in your child classes too when you only care about changing the type.
1028 
1029 
1030 		It also has a further facility to pick a wholly differnet class based on the [DynamicState] of the Widget.
1031 		You may also just override `variesWithState` when you use this flag.
1032 
1033 		---
1034 		mixin OverrideStyle!(
1035 			DynamicState.focus, YourFocusedStyle,
1036 			DynamicState.hover, YourHoverStyle,
1037 			YourDefaultStyle
1038 		)
1039 		---
1040 
1041 		It checks if `dynamicState` matches the state and if so, returns the object given.
1042 
1043 		If there is no state mask given, the next one matches everything. The first match given is used.
1044 
1045 		However, since in most cases you'll want check state inside your individual methods, you probably won't
1046 		find much use for this whole-class swap out.
1047 
1048 		History:
1049 			Added May 16, 2021
1050 	+/
1051 	static protected mixin template OverrideStyle(S...) {
1052 		static import amg = arsd.minigui;
1053 		override void useStyleProperties(scope void delegate(scope amg.Widget.Style props) dg) {
1054 			ulong mask = 0;
1055 			foreach(idx, thing; S) {
1056 				static if(is(typeof(thing) : ulong)) {
1057 					mask = thing;
1058 				} else {
1059 					if(!(idx & 1) || (this.dynamicState & mask) == mask) {
1060 						//static assert(!__traits(isNested, thing), thing.stringof ~ " is a nested class. For best results, mark it `static`. You can still access the widget through a `widget` variable inside the Style class.");
1061 						scope amg.Widget.Style s = new thing();
1062 						s.widget = this;
1063 						dg(s);
1064 						return;
1065 					}
1066 				}
1067 			}
1068 		}
1069 	}
1070 	/++
1071 		You can override this by hand, or use the [OverrideStyle] helper which is a bit less verbose.
1072 	+/
1073 	void useStyleProperties(scope void delegate(scope Style props) dg) {
1074 		scope Style s = new Style();
1075 		s.widget = this;
1076 		dg(s);
1077 	}
1078 
1079 
1080 	protected void sendResizeEvent() {
1081 		this.emit!ResizeEvent();
1082 	}
1083 
1084 	Menu contextMenu(int x, int y) { return null; }
1085 
1086 	final bool showContextMenu(int x, int y, int screenX = -2, int screenY = -2) {
1087 		if(parentWindow is null || parentWindow.win is null) return false;
1088 
1089 		auto menu = this.contextMenu(x, y);
1090 		if(menu is null)
1091 			return false;
1092 
1093 		version(win32_widgets) {
1094 			// FIXME: if it is -1, -1, do it at the current selection location instead
1095 			// tho the corner of the window, whcih it does now, isn't the literal worst.
1096 
1097 			if(screenX < 0 && screenY < 0) {
1098 				auto p = this.globalCoordinates();
1099 				if(screenX == -2)
1100 					p.x += x;
1101 				if(screenY == -2)
1102 					p.y += y;
1103 
1104 				screenX = p.x;
1105 				screenY = p.y;
1106 			}
1107 
1108 			if(!TrackPopupMenuEx(menu.handle, 0, screenX, screenY, parentWindow.win.impl.hwnd, null))
1109 				throw new Exception("TrackContextMenuEx");
1110 		} else version(custom_widgets) {
1111 			menu.popup(this, x, y);
1112 		}
1113 
1114 		return true;
1115 	}
1116 
1117 	/++
1118 		Removes this widget from its parent.
1119 
1120 		History:
1121 			`removeWidget` was made `final` on May 11, 2021.
1122 	+/
1123 	@scriptable
1124 	final void removeWidget() {
1125 		auto p = this.parent;
1126 		if(p) {
1127 			int item;
1128 			for(item = 0; item < p._children.length; item++)
1129 				if(p._children[item] is this)
1130 					break;
1131 			auto idx = item;
1132 			for(; item < p._children.length - 1; item++)
1133 				p._children[item] = p._children[item + 1];
1134 			p._children = p._children[0 .. $-1];
1135 
1136 			this.parent.widgetRemoved(idx, this);
1137 			//this.parent = null;
1138 
1139 			p.queueRecomputeChildLayout();
1140 		}
1141 		version(win32_widgets) {
1142 			removeAllChildren();
1143 			if(hwnd) {
1144 				DestroyWindow(hwnd);
1145 				hwnd = null;
1146 			}
1147 		}
1148 	}
1149 
1150 	/++
1151 		Notifies the subclass that a widget was removed. If you keep auxillary data about your children, you can override this to help keep that data in sync.
1152 
1153 		History:
1154 			Added September 19, 2021
1155 	+/
1156 	protected void widgetRemoved(size_t oldIndex, Widget oldReference) { }
1157 
1158 	/++
1159 		Removes all child widgets from `this`. You should not use the removed widgets again.
1160 
1161 		Note that on Windows, it also destroys the native handles for the removed children recursively.
1162 
1163 		History:
1164 			Added July 1, 2021 (dub v10.2)
1165 	+/
1166 	void removeAllChildren() {
1167 		version(win32_widgets)
1168 		foreach(child; _children) {
1169 			child.removeAllChildren();
1170 			if(child.hwnd) {
1171 				DestroyWindow(child.hwnd);
1172 				child.hwnd = null;
1173 			}
1174 		}
1175 		auto orig = this._children;
1176 		this._children = null;
1177 		foreach(idx, w; orig)
1178 			this.widgetRemoved(idx, w);
1179 
1180 		queueRecomputeChildLayout();
1181 	}
1182 
1183 	/++
1184 		Calls [getByName] with the generic type of Widget. Meant for script interop where instantiating a template is impossible.
1185 	+/
1186 	@scriptable
1187 	Widget getChildByName(string name) {
1188 		return getByName(name);
1189 	}
1190 	/++
1191 		Finds the nearest descendant with the requested type and [name]. May return `this`.
1192 	+/
1193 	final WidgetClass getByName(WidgetClass = Widget)(string name) {
1194 		if(this.name == name)
1195 			if(auto c = cast(WidgetClass) this)
1196 				return c;
1197 		foreach(child; children) {
1198 			auto w = child.getByName(name);
1199 			if(auto c = cast(WidgetClass) w)
1200 				return c;
1201 		}
1202 		return null;
1203 	}
1204 
1205 	/++
1206 		The name is a string tag that is used to reference the widget from scripts, gui loaders, declarative ui templates, etc. Similar to a HTML id attribute.
1207 		Names should be unique in a window.
1208 
1209 		See_Also: [getByName], [getChildByName]
1210 	+/
1211 	@scriptable string name;
1212 
1213 	private EventHandler[][string] bubblingEventHandlers;
1214 	private EventHandler[][string] capturingEventHandlers;
1215 
1216 	/++
1217 		Default event handlers. These are called on the appropriate
1218 		event unless [Event.preventDefault] is called on the event at
1219 		some point through the bubbling process.
1220 
1221 
1222 		If you are implementing your own widget and want to add custom
1223 		events, you should follow the same pattern here: create a virtual
1224 		function named `defaultEventHandler_eventname` with the implementation,
1225 		then, override [setupDefaultEventHandlers] and add a wrapped caller to
1226 		`defaultEventHandlers["eventname"]`. It should be wrapped like so:
1227 		`defaultEventHandlers["eventname"] = (Widget t, Event event) { t.defaultEventHandler_name(event); };`.
1228 		This ensures virtual dispatch based on the correct subclass.
1229 
1230 		Also, don't forget to call `super.setupDefaultEventHandlers();` too in your
1231 		overridden version.
1232 
1233 		You only need to do that on parent classes adding NEW event types. If you
1234 		just want to change the default behavior of an existing event type in a subclass,
1235 		you override the function (and optionally call `super.method_name`) like normal.
1236 
1237 	+/
1238 	protected EventHandler[string] defaultEventHandlers;
1239 
1240 	/// ditto
1241 	void setupDefaultEventHandlers() {
1242 		defaultEventHandlers["click"] = (Widget t, Event event) { t.defaultEventHandler_click(cast(ClickEvent) event); };
1243 		defaultEventHandlers["dblclick"] = (Widget t, Event event) { t.defaultEventHandler_dblclick(cast(DoubleClickEvent) event); };
1244 		defaultEventHandlers["keydown"] = (Widget t, Event event) { t.defaultEventHandler_keydown(cast(KeyDownEvent) event); };
1245 		defaultEventHandlers["keyup"] = (Widget t, Event event) { t.defaultEventHandler_keyup(cast(KeyUpEvent) event); };
1246 		defaultEventHandlers["mouseover"] = (Widget t, Event event) { t.defaultEventHandler_mouseover(cast(MouseOverEvent) event); };
1247 		defaultEventHandlers["mouseout"] = (Widget t, Event event) { t.defaultEventHandler_mouseout(cast(MouseOutEvent) event); };
1248 		defaultEventHandlers["mousedown"] = (Widget t, Event event) { t.defaultEventHandler_mousedown(cast(MouseDownEvent) event); };
1249 		defaultEventHandlers["mouseup"] = (Widget t, Event event) { t.defaultEventHandler_mouseup(cast(MouseUpEvent) event); };
1250 		defaultEventHandlers["mouseenter"] = (Widget t, Event event) { t.defaultEventHandler_mouseenter(cast(MouseEnterEvent) event); };
1251 		defaultEventHandlers["mouseleave"] = (Widget t, Event event) { t.defaultEventHandler_mouseleave(cast(MouseLeaveEvent) event); };
1252 		defaultEventHandlers["mousemove"] = (Widget t, Event event) { t.defaultEventHandler_mousemove(cast(MouseMoveEvent) event); };
1253 		defaultEventHandlers["char"] = (Widget t, Event event) { t.defaultEventHandler_char(cast(CharEvent) event); };
1254 		defaultEventHandlers["triggered"] = (Widget t, Event event) { t.defaultEventHandler_triggered(event); };
1255 		defaultEventHandlers["change"] = (Widget t, Event event) { t.defaultEventHandler_change(event); };
1256 		defaultEventHandlers["focus"] = (Widget t, Event event) { t.defaultEventHandler_focus(event); };
1257 		defaultEventHandlers["blur"] = (Widget t, Event event) { t.defaultEventHandler_blur(event); };
1258 		defaultEventHandlers["focusin"] = (Widget t, Event event) { t.defaultEventHandler_focusin(event); };
1259 		defaultEventHandlers["focusout"] = (Widget t, Event event) { t.defaultEventHandler_focusout(event); };
1260 	}
1261 
1262 	/// ditto
1263 	void defaultEventHandler_click(ClickEvent event) {}
1264 	/// ditto
1265 	void defaultEventHandler_dblclick(DoubleClickEvent event) {}
1266 	/// ditto
1267 	void defaultEventHandler_keydown(KeyDownEvent event) {}
1268 	/// ditto
1269 	void defaultEventHandler_keyup(KeyUpEvent event) {}
1270 	/// ditto
1271 	void defaultEventHandler_mousedown(MouseDownEvent event) {
1272 		if(event.button == MouseButton.left) {
1273 			if(this.tabStop)
1274 				this.focus();
1275 		}
1276 	}
1277 	/// ditto
1278 	void defaultEventHandler_mouseover(MouseOverEvent event) {}
1279 	/// ditto
1280 	void defaultEventHandler_mouseout(MouseOutEvent event) {}
1281 	/// ditto
1282 	void defaultEventHandler_mouseup(MouseUpEvent event) {}
1283 	/// ditto
1284 	void defaultEventHandler_mousemove(MouseMoveEvent event) {}
1285 	/// ditto
1286 	void defaultEventHandler_mouseenter(MouseEnterEvent event) {}
1287 	/// ditto
1288 	void defaultEventHandler_mouseleave(MouseLeaveEvent event) {}
1289 	/// ditto
1290 	void defaultEventHandler_char(CharEvent event) {}
1291 	/// ditto
1292 	void defaultEventHandler_triggered(Event event) {}
1293 	/// ditto
1294 	void defaultEventHandler_change(Event event) {}
1295 	/// ditto
1296 	void defaultEventHandler_focus(Event event) {}
1297 	/// ditto
1298 	void defaultEventHandler_blur(Event event) {}
1299 	/// ditto
1300 	void defaultEventHandler_focusin(Event event) {}
1301 	/// ditto
1302 	void defaultEventHandler_focusout(Event event) {}
1303 
1304 	/++
1305 		[Event]s use a Javascript-esque model. See more details on the [Event] page.
1306 
1307 		[addEventListener] returns an opaque handle that you can later pass to [removeEventListener].
1308 
1309 		addDirectEventListener just inserts a check `if(e.target !is this) return;` meaning it opts out
1310 		of participating in handler delegation.
1311 
1312 		$(TIP
1313 			Use `scope` on your handlers when you can. While it currently does nothing, this will future-proof your code against future optimizations I want to do. Instead of copying whole event objects out if you do need to store them, just copy the properties you need.
1314 		)
1315 	+/
1316 	EventListener addDirectEventListener(string event, void delegate() handler, bool useCapture = false) {
1317 		return addEventListener(event, (Widget, scope Event e) {
1318 			if(e.srcElement is this)
1319 				handler();
1320 		}, useCapture);
1321 	}
1322 
1323 	/// ditto
1324 	EventListener addDirectEventListener(string event, void delegate(Event) handler, bool useCapture = false) {
1325 		return addEventListener(event, (Widget, Event e) {
1326 			if(e.srcElement is this)
1327 				handler(e);
1328 		}, useCapture);
1329 	}
1330 
1331 	/// ditto
1332 	EventListener addDirectEventListener(Handler)(Handler handler, bool useCapture = false) {
1333 		static if(is(Handler Fn == delegate)) {
1334 		static if(is(Fn Params == __parameters)) {
1335 			return addEventListener(EventString!(Params[0]), (Widget, Event e) {
1336 				if(e.srcElement !is this)
1337 					return;
1338 				auto ty = cast(Params[0]) e;
1339 				if(ty !is null)
1340 					handler(ty);
1341 			}, useCapture);
1342 		} else static assert(0);
1343 		} else static assert(0, "Your handler wasn't usable because it wasn't passed a delegate. Use the delegate keyword at the call site.");
1344 	}
1345 
1346 	/// ditto
1347 	@scriptable
1348 	EventListener addEventListener(string event, void delegate() handler, bool useCapture = false) {
1349 		return addEventListener(event, (Widget, scope Event) { handler(); }, useCapture);
1350 	}
1351 
1352 	/// ditto
1353 	EventListener addEventListener(Handler)(Handler handler, bool useCapture = false) {
1354 		static if(is(Handler Fn == delegate)) {
1355 		static if(is(Fn Params == __parameters)) {
1356 			return addEventListener(EventString!(Params[0]), (Widget, Event e) {
1357 				auto ty = cast(Params[0]) e;
1358 				if(ty !is null)
1359 					handler(ty);
1360 			}, useCapture);
1361 		} else static assert(0);
1362 		} else static assert(0, "Your handler wasn't usable because it wasn't passed a delegate. Use the delegate keyword at the call site.");
1363 	}
1364 
1365 	/// ditto
1366 	EventListener addEventListener(string event, void delegate(Event) handler, bool useCapture = false) {
1367 		return addEventListener(event, (Widget, Event e) { handler(e); }, useCapture);
1368 	}
1369 
1370 	/// ditto
1371 	EventListener addEventListener(string event, EventHandler handler, bool useCapture = false) {
1372 		if(event.length > 2 && event[0..2] == "on")
1373 			event = event[2 .. $];
1374 
1375 		if(useCapture)
1376 			capturingEventHandlers[event] ~= handler;
1377 		else
1378 			bubblingEventHandlers[event] ~= handler;
1379 
1380 		return EventListener(this, event, handler, useCapture);
1381 	}
1382 
1383 	/// ditto
1384 	void removeEventListener(string event, EventHandler handler, bool useCapture = false) {
1385 		if(event.length > 2 && event[0..2] == "on")
1386 			event = event[2 .. $];
1387 
1388 		if(useCapture) {
1389 			if(event in capturingEventHandlers)
1390 			foreach(ref evt; capturingEventHandlers[event])
1391 				if(evt is handler) evt = null;
1392 		} else {
1393 			if(event in bubblingEventHandlers)
1394 			foreach(ref evt; bubblingEventHandlers[event])
1395 				if(evt is handler) evt = null;
1396 		}
1397 	}
1398 
1399 	/// ditto
1400 	void removeEventListener(EventListener listener) {
1401 		removeEventListener(listener.event, listener.handler, listener.useCapture);
1402 	}
1403 
1404 	static if(UsingSimpledisplayX11) {
1405 		void discardXConnectionState() {
1406 			foreach(child; children)
1407 				child.discardXConnectionState();
1408 		}
1409 
1410 		void recreateXConnectionState() {
1411 			foreach(child; children)
1412 				child.recreateXConnectionState();
1413 			redraw();
1414 		}
1415 	}
1416 
1417 	/++
1418 		Returns the coordinates of this widget on the screen, relative to the upper left corner of the whole screen.
1419 
1420 		History:
1421 			`globalCoordinates` was made `final` on May 11, 2021.
1422 	+/
1423 	Point globalCoordinates() {
1424 		int x = this.x;
1425 		int y = this.y;
1426 		auto p = this.parent;
1427 		while(p) {
1428 			x += p.x;
1429 			y += p.y;
1430 			p = p.parent;
1431 		}
1432 
1433 		static if(UsingSimpledisplayX11) {
1434 			auto dpy = XDisplayConnection.get;
1435 			arsd.simpledisplay.Window dummyw;
1436 			XTranslateCoordinates(dpy, this.parentWindow.win.impl.window, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
1437 		} else {
1438 			POINT pt;
1439 			pt.x = x;
1440 			pt.y = y;
1441 			MapWindowPoints(this.parentWindow.win.impl.hwnd, null, &pt, 1);
1442 			x = pt.x;
1443 			y = pt.y;
1444 		}
1445 
1446 		return Point(x, y);
1447 	}
1448 
1449 	version(win32_widgets)
1450 	int handleWmDrawItem(DRAWITEMSTRUCT* dis) { return 0; }
1451 
1452 	version(win32_widgets)
1453 	/// Called when a WM_COMMAND is sent to the associated hwnd.
1454 	void handleWmCommand(ushort cmd, ushort id) {}
1455 
1456 	version(win32_widgets)
1457 	/++
1458 		Called when a WM_NOTIFY is sent to the associated hwnd.
1459 
1460 		History:
1461 	+/
1462 	int handleWmNotify(NMHDR* hdr, int code, out int mustReturn) { return 0; }
1463 
1464 	version(win32_widgets)
1465 	deprecated("This overload is problematic since it is liable to discard return values. Add the `out int mustReturn` to your override as the last parameter and set it to 1 when you must forward the return value to Windows. Otherwise, you can just add the parameter then ignore it and use the default value of 0 to maintain the status quo.") int handleWmNotify(NMHDR* hdr, int code) { int ignored; return handleWmNotify(hdr, code, ignored); }
1466 
1467 	/++
1468 		This tip is displayed in the status bar (if there is one in the containing window) when the mouse moves over this widget.
1469 
1470 		Updates to this variable will only be made visible on the next mouse enter event.
1471 	+/
1472 	@scriptable string statusTip;
1473 	// string toolTip;
1474 	// string helpText;
1475 
1476 	/++
1477 		If true, this widget can be focused via keyboard control with the tab key.
1478 
1479 		If false, it is assumed the widget itself does will never receive the keyboard focus (though its childen are free to).
1480 	+/
1481 	bool tabStop = true;
1482 	/++
1483 		The tab key cycles through widgets by the order of a.tabOrder < b.tabOrder. If they are equal, it does them in child order (which is typically the order they were added to the widget.)
1484 	+/
1485 	int tabOrder;
1486 
1487 	version(win32_widgets) {
1488 		static Widget[HWND] nativeMapping;
1489 		/// The native handle, if there is one.
1490 		HWND hwnd;
1491 		WNDPROC originalWindowProcedure;
1492 
1493 		SimpleWindow simpleWindowWrappingHwnd;
1494 
1495 		// please note it IGNORES your return value and does NOT forward it to Windows!
1496 		int hookedWndProc(UINT iMessage, WPARAM wParam, LPARAM lParam) {
1497 			return 0;
1498 		}
1499 	}
1500 	private bool implicitlyCreated;
1501 
1502 	/// Child's position relative to the parent's origin. only the layout manager should be modifying this and even reading it is of limited utility. It may be made `private` at some point in the future without advance notice. Do NOT depend on it being available unless you are writing a layout manager.
1503 	int x;
1504 	/// ditto
1505 	int y;
1506 	private int _width;
1507 	private int _height;
1508 	private Widget[] _children;
1509 	private Widget _parent;
1510 	private Window _parentWindow;
1511 
1512 	/++
1513 		Returns the window to which this widget is attached.
1514 
1515 		History:
1516 			Prior to May 11, 2021, the `Window parentWindow` variable was directly available. Now, only this property getter is available and the actual store is private.
1517 	+/
1518 	final @property inout(Window) parentWindow() inout @nogc nothrow pure { return _parentWindow; }
1519 	private @property void parentWindow(Window parent) {
1520 		_parentWindow = parent;
1521 		foreach(child; children)
1522 			child.parentWindow = parent; // please note that this is recursive
1523 	}
1524 
1525 	/++
1526 		Returns the list of the widget's children.
1527 
1528 		History:
1529 			Prior to May 11, 2021, the `Widget[] children` was directly available. Now, only this property getter is available and the actual store is private.
1530 
1531 			Children should be added by the constructor most the time, but if that's impossible, use [addChild] and [removeWidget] to manage the list.
1532 	+/
1533 	final @property inout(Widget)[] children() inout @nogc nothrow pure { return _children; }
1534 
1535 	/++
1536 		Returns the widget's parent.
1537 
1538 		History:
1539 			Prior to May 11, 2021, the `Widget parent` variable was directly available. Now, only this property getter is permitted.
1540 
1541 			The parent should only be managed by the [addChild] and [removeWidget] method.
1542 	+/
1543 	final @property inout(Widget) parent() inout nothrow @nogc pure @safe return { return _parent; }
1544 
1545 	/// The widget's current size.
1546 	final @scriptable public @property int width() const nothrow @nogc pure @safe { return _width; }
1547 	/// ditto
1548 	final @scriptable public @property int height() const nothrow @nogc pure @safe { return _height; }
1549 
1550 	/// Only the layout manager should be calling these.
1551 	final protected @property int width(int a) @safe { return _width = a; }
1552 	/// ditto
1553 	final protected @property int height(int a) @safe { return _height = a; }
1554 
1555 	/++
1556 		This function is called by the layout engine after it has updated the position (in variables `x` and `y`) and the size (in properties `width` and `height`) to give you a chance to update the actual position of the native child window (if there is one) or whatever.
1557 
1558 		It is also responsible for calling [sendResizeEvent] to notify other listeners that the widget has changed size.
1559 	+/
1560 	protected void registerMovement() {
1561 		version(win32_widgets) {
1562 			if(hwnd) {
1563 				auto pos = getChildPositionRelativeToParentHwnd(this);
1564 				MoveWindow(hwnd, pos[0], pos[1], width, height, false); // setting this to false can sometimes speed things up but only if it is actually drawn later and that's kinda iffy to do right here so being slower but safer rn
1565 			}
1566 		}
1567 		sendResizeEvent();
1568 	}
1569 
1570 	/// Creates the widget and adds it to the parent.
1571 	this(Widget parent) {
1572 		if(parent !is null)
1573 			parent.addChild(this);
1574 		setupDefaultEventHandlers();
1575 	}
1576 
1577 	/// Returns true if this is the current focused widget inside the parent window. Please note it may return `true` when the window itself is unfocused. In that case, it indicates this widget will receive focuse again when the window does.
1578 	@scriptable
1579 	bool isFocused() {
1580 		return parentWindow && parentWindow.focusedWidget is this;
1581 	}
1582 
1583 	private bool showing_ = true;
1584 	///
1585 	bool showing() { return showing_; }
1586 	///
1587 	bool hidden() { return !showing_; }
1588 	/++
1589 		Shows or hides the window. Meant to be assigned as a property. If `recalculate` is true (the default), it recalculates the layout of the parent widget to use the space this widget being hidden frees up or make space for this widget to appear again.
1590 	+/
1591 	void showing(bool s, bool recalculate = true) {
1592 		auto so = showing_;
1593 		showing_ = s;
1594 		if(s != so) {
1595 			version(win32_widgets)
1596 			if(hwnd)
1597 				ShowWindow(hwnd, s ? SW_SHOW : SW_HIDE);
1598 
1599 			if(parent && recalculate) {
1600 				parent.queueRecomputeChildLayout();
1601 				parent.redraw();
1602 			}
1603 
1604 			foreach(child; children)
1605 				child.showing(s, false);
1606 
1607 		}
1608 		queueRecomputeChildLayout();
1609 		redraw();
1610 	}
1611 	/// Convenience method for `showing = true`
1612 	@scriptable
1613 	void show() {
1614 		showing = true;
1615 	}
1616 	/// Convenience method for `showing = false`
1617 	@scriptable
1618 	void hide() {
1619 		showing = false;
1620 	}
1621 
1622 	///
1623 	@scriptable
1624 	void focus() {
1625 		assert(parentWindow !is null);
1626 		if(isFocused())
1627 			return;
1628 
1629 		if(parentWindow.focusedWidget) {
1630 			// FIXME: more details here? like from and to
1631 			auto from = parentWindow.focusedWidget;
1632 			parentWindow.focusedWidget.setDynamicState(DynamicState.focus, false);
1633 			parentWindow.focusedWidget = null;
1634 			from.emit!BlurEvent();
1635 			this.emit!FocusOutEvent();
1636 		}
1637 
1638 
1639 		version(win32_widgets) {
1640 			if(this.hwnd !is null)
1641 				SetFocus(this.hwnd);
1642 		}
1643 		//else static if(UsingSimpledisplayX11)
1644 			//this.parentWindow.win.focus();
1645 
1646 		parentWindow.focusedWidget = this;
1647 		parentWindow.focusedWidget.setDynamicState(DynamicState.focus, true);
1648 		this.emit!FocusEvent();
1649 		this.emit!FocusInEvent();
1650 	}
1651 
1652 	/+
1653 	/++
1654 		Unfocuses the widget. This may reset
1655 	+/
1656 	@scriptable
1657 	void blur() {
1658 
1659 	}
1660 	+/
1661 
1662 
1663 	/++
1664 		This is called when the widget is added to a window. It gives you a chance to set up event hooks.
1665 
1666 		Update on May 11, 2021: I'm considering removing this method. You can usually achieve these things through looser-coupled methods.
1667 	+/
1668 	void attachedToWindow(Window w) {}
1669 	/++
1670 		Callback when the widget is added to another widget.
1671 
1672 		Update on May 11, 2021: I'm considering removing this method since I've never actually found it useful.
1673 	+/
1674 	void addedTo(Widget w) {}
1675 
1676 	/++
1677 		Adds a child to the given position. This is `protected` because you generally shouldn't be calling this directly. Instead, construct widgets with the parent directly.
1678 
1679 		This is available primarily to be overridden. For example, [MainWindow] overrides it to redirect its children into a central widget.
1680 	+/
1681 	protected void addChild(Widget w, int position = int.max) {
1682 		assert(w._parent !is this, "Child cannot be added twice to the same parent");
1683 		assert(w !is this, "Child cannot be its own parent!");
1684 		w._parent = this;
1685 		if(position == int.max || position == children.length) {
1686 			_children ~= w;
1687 		} else {
1688 			assert(position < _children.length);
1689 			_children.length = _children.length + 1;
1690 			for(int i = cast(int) _children.length - 1; i > position; i--)
1691 				_children[i] = _children[i - 1];
1692 			_children[position] = w;
1693 		}
1694 
1695 		this.parentWindow = this._parentWindow;
1696 
1697 		w.addedTo(this);
1698 
1699 		if(this.hidden)
1700 			w.showing = false;
1701 
1702 		if(parentWindow !is null) {
1703 			w.attachedToWindow(parentWindow);
1704 			parentWindow.queueRecomputeChildLayout();
1705 			parentWindow.redraw();
1706 		}
1707 	}
1708 
1709 	/++
1710 		Finds the child at the top of the z-order at the given coordinates (relative to the `this` widget's origin), or null if none are found.
1711 	+/
1712 	Widget getChildAtPosition(int x, int y) {
1713 		// it goes backward so the last one to show gets picked first
1714 		// might use z-index later
1715 		foreach_reverse(child; children) {
1716 			if(child.hidden)
1717 				continue;
1718 			if(child.x <= x && child.y <= y
1719 				&& ((x - child.x) < child.width)
1720 				&& ((y - child.y) < child.height))
1721 			{
1722 				return child;
1723 			}
1724 		}
1725 
1726 		return null;
1727 	}
1728 
1729 	/++
1730 		If the widget is a scrollable container, this should add the current scroll position to the given coordinates so the mouse events can be dispatched correctly.
1731 
1732 		History:
1733 			Added July 2, 2021 (v10.2)
1734 	+/
1735 	protected void addScrollPosition(ref int x, ref int y) {};
1736 
1737 	/++
1738 		Responsible for actually painting the widget to the screen. The clip rectangle and coordinate translation in the [WidgetPainter] are pre-configured so you can draw independently.
1739 
1740 		This function paints the entire widget, including styled borders, backgrounds, etc. You are also responsible for displaying any important active state to the user, including if you hold the active keyboard focus. If you only want to be responsible for the content while letting the style engine draw the rest, override [paintContent] instead.
1741 
1742 		[paint] is not called for system widgets as the OS library draws them instead.
1743 
1744 
1745 		The default implementation forwards to [WidgetPainter.drawThemed], passing [paintContent] as the delegate. If you override this, you might use those same functions or you can do your own thing.
1746 
1747 		You should also look at [WidgetPainter.visualTheme] to be theme aware.
1748 
1749 		History:
1750 			Prior to May 15, 2021, the default implementation was empty. Now, it is `painter.drawThemed(&paintContent);`. You may wish to override [paintContent] instead of [paint] to take advantage of the new styling engine.
1751 	+/
1752 	void paint(WidgetPainter painter) {
1753 		version(win32_widgets)
1754 			if(hwnd) {
1755 				return;
1756 			}
1757 		painter.drawThemed(&paintContent); // note this refers to the following overload
1758 	}
1759 
1760 	/++
1761 		Responsible for drawing the content as the theme engine is responsible for other elements.
1762 
1763 		$(WARNING If you override [paint], this method may never be used as it is only called from inside the default implementation of `paint`.)
1764 
1765 		Params:
1766 			painter = your painter (forwarded from [paint]) for drawing on the widget. The clip rectangle and coordinate translation are prepared for you ahead of time so you can use widget coordinates. It also has the theme foreground preloaded into the painter outline color, the theme font preloaded as the painter's active font, and the theme background preloaded as the painter's fill color.
1767 
1768 			bounds = the bounds, inside the widget, where your content should be drawn. This is the rectangle inside the border and padding (if any). The stuff outside is not clipped - it is still part of your widget - but you should respect these bounds for visual consistency and respecting the theme's area.
1769 
1770 			If you do want to clip it, you can of course call `auto oldClip = painter.setClipRectangle(bounds); scope(exit) painter.setClipRectangle(oldClip);` to modify it and return to the previous setting when you return.
1771 
1772 		Returns:
1773 			The rectangle representing your actual content. Typically, this is simply `return bounds;`. The theme engine uses this return value to determine where the outline and overlay should be.
1774 
1775 		History:
1776 			Added May 15, 2021
1777 	+/
1778 	Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
1779 		return bounds;
1780 	}
1781 
1782 	deprecated("Change ScreenPainter to WidgetPainter")
1783 	final void paint(ScreenPainter) { assert(0, "Change ScreenPainter to WidgetPainter and recompile your code"); }
1784 
1785 	/// I don't actually like the name of this
1786 	/// this draws a background on it
1787 	void erase(WidgetPainter painter) {
1788 		version(win32_widgets)
1789 			if(hwnd) return; // Windows will do it. I think.
1790 
1791 		auto c = getComputedStyle().background.color;
1792 		painter.fillColor = c;
1793 		painter.outlineColor = c;
1794 
1795 		version(win32_widgets) {
1796 			HANDLE b, p;
1797 			if(c.a == 0 && parent is parentWindow) {
1798 				// I don't remember why I had this really...
1799 				b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
1800 				p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
1801 			}
1802 		}
1803 		painter.drawRectangle(Point(0, 0), width, height);
1804 		version(win32_widgets) {
1805 			if(c.a == 0 && parent is parentWindow) {
1806 				SelectObject(painter.impl.hdc, p);
1807 				SelectObject(painter.impl.hdc, b);
1808 			}
1809 		}
1810 	}
1811 
1812 	///
1813 	WidgetPainter draw() {
1814 		int x = this.x, y = this.y;
1815 		auto parent = this.parent;
1816 		while(parent) {
1817 			x += parent.x;
1818 			y += parent.y;
1819 			parent = parent.parent;
1820 		}
1821 
1822 		auto painter = parentWindow.win.draw(true);
1823 		painter.originX = x;
1824 		painter.originY = y;
1825 		painter.setClipRectangle(Point(0, 0), width, height);
1826 		return WidgetPainter(painter, this);
1827 	}
1828 
1829 	/// This can be overridden by scroll things. It is responsible for actually calling [paint]. Do not override unless you've studied minigui.d's source code. There are no stability guarantees if you do override this; it can (and likely will) break without notice.
1830 	protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force, bool invalidate) {
1831 		if(hidden)
1832 			return;
1833 
1834 		int paintX = x;
1835 		int paintY = y;
1836 		if(this.useNativeDrawing()) {
1837 			paintX = 0;
1838 			paintY = 0;
1839 			lox = 0;
1840 			loy = 0;
1841 			containment = Rectangle(0, 0, int.max, int.max);
1842 		}
1843 
1844 		painter.originX = lox + paintX;
1845 		painter.originY = loy + paintY;
1846 
1847 		bool actuallyPainted = false;
1848 
1849 		const clip = containment.intersectionOf(Rectangle(Point(lox + paintX, loy + paintY), Size(width, height)));
1850 		if(clip == Rectangle.init) {
1851 			//import std.stdio; writeln(this, " clipped out");
1852 			return;
1853 		}
1854 
1855 		bool invalidateChildren = invalidate;
1856 
1857 		if(redrawRequested || force) {
1858 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
1859 
1860 			painter.drawingUpon = this;
1861 
1862 			erase(painter);
1863 			if(painter.visualTheme)
1864 				painter.visualTheme.doPaint(this, painter);
1865 			else
1866 				paint(painter);
1867 
1868 			if(invalidate) {
1869 				// sdpyPrintDebugString("invalidate " ~ typeid(this).name);
1870 				auto region = Rectangle(Point(clip.upperLeft.x - painter.originX, clip.upperRight.y - painter.originY), Size(clip.width, clip.height));
1871 				painter.invalidateRect(region);
1872 				// children are contained inside this, so no need to do extra work
1873 				invalidateChildren = false;
1874 			}
1875 
1876 			redrawRequested = false;
1877 			actuallyPainted = true;
1878 		}
1879 
1880 		foreach(child; children) {
1881 			version(win32_widgets)
1882 				if(child.useNativeDrawing()) continue;
1883 			child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted, invalidateChildren);
1884 		}
1885 
1886 		version(win32_widgets)
1887 		foreach(child; children) {
1888 			if(child.useNativeDrawing) {
1889 				painter = WidgetPainter(child.simpleWindowWrappingHwnd.draw(true), child);
1890 				child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted, true); // have to reset the invalidate flag since these are not necessarily affected the same way, being native children with a clip
1891 			}
1892 		}
1893 	}
1894 
1895 	protected bool useNativeDrawing() nothrow {
1896 		version(win32_widgets)
1897 			return hwnd !is null;
1898 		else
1899 			return false;
1900 	}
1901 
1902 	private static class RedrawEvent {}
1903 	private __gshared re = new RedrawEvent();
1904 
1905 	private bool redrawRequested;
1906 	///
1907 	final void redraw(string file = __FILE__, size_t line = __LINE__) {
1908 		redrawRequested = true;
1909 
1910 		if(this.parentWindow) {
1911 			auto sw = this.parentWindow.win;
1912 			assert(sw !is null);
1913 			if(!sw.eventQueued!RedrawEvent) {
1914 				sw.postEvent(re);
1915 				// import std.stdio; writeln("redraw requested from ", file,":",line," ", this.parentWindow.win.impl.window);
1916 			}
1917 		}
1918 	}
1919 
1920 	private SimpleWindow drawableWindow;
1921 
1922 	/++
1923 		Allows a class to easily dispatch its own statically-declared event (see [Emits]). The main benefit of using this over constructing an event yourself is simply that you ensure you haven't sent something you haven't documented you can send.
1924 
1925 		Returns:
1926 			`true` if you should do your default behavior.
1927 
1928 		History:
1929 			Added May 5, 2021
1930 
1931 		Bugs:
1932 			It does not do the static checks on gdc right now.
1933 	+/
1934 	final protected bool emit(EventType, this This, Args...)(Args args) {
1935 		version(GNU) {} else
1936 		static assert(classStaticallyEmits!(This, EventType), "The " ~ This.stringof ~ " class is not declared to emit " ~ EventType.stringof);
1937 		auto e = new EventType(this, args);
1938 		e.dispatch();
1939 		return !e.defaultPrevented;
1940 	}
1941 	/// ditto
1942 	final protected bool emit(string eventString, this This)() {
1943 		auto e = new Event(eventString, this);
1944 		e.dispatch();
1945 		return !e.defaultPrevented;
1946 	}
1947 
1948 	/++
1949 		Does the same as [addEventListener]'s delegate overload, but adds an additional check to ensure the event you are subscribing to is actually emitted by the static type you are using. Since it works on static types, if you have a generic [Widget], this can only subscribe to events declared as [Emits] inside [Widget] itself, not any child classes nor any child elements. If this is too restrictive, simply use [addEventListener] instead.
1950 
1951 		History:
1952 			Added May 5, 2021
1953 	+/
1954 	final public EventListener subscribe(EventType, this This)(void delegate(EventType) handler) {
1955 		static assert(classStaticallyEmits!(This, EventType), "The " ~ This.stringof ~ " class is not declared to emit " ~ EventType.stringof);
1956 		return addEventListener(handler);
1957 	}
1958 
1959 	/++
1960 		Gets the computed style properties from the visual theme.
1961 
1962 		You should use this in your paint and layout functions instead of the direct properties on the widget if you want to be style aware. (But when setting defaults in your classes, overriding is the right thing to do. Override to set defaults, but then read out of [getComputedStyle].)
1963 
1964 		History:
1965 			Added May 8, 2021
1966 	+/
1967 	final StyleInformation getComputedStyle() {
1968 		return StyleInformation(this);
1969 	}
1970 
1971 	int focusableWidgets(scope int delegate(Widget) dg) {
1972 		foreach(widget; WidgetStream(this)) {
1973 			if(widget.tabStop && !widget.hidden) {
1974 				int result = dg(widget);
1975 				if (result)
1976 					return result;
1977 			}
1978 		}
1979 		return 0;
1980 	}
1981 
1982 	/++
1983 		Calculates the border box (that is, the full width/height of the widget, from border edge to border edge)
1984 		for the given content box (the area between the padding)
1985 
1986 		History:
1987 			Added January 4, 2023 (dub v11.0)
1988 	+/
1989 	Rectangle borderBoxForContentBox(Rectangle contentBox) {
1990 		auto cs = getComputedStyle();
1991 
1992 		auto borderWidth = getBorderWidth(cs.borderStyle);
1993 
1994 		auto rect = contentBox;
1995 
1996 		rect.left -= borderWidth;
1997 		rect.right += borderWidth;
1998 		rect.top -= borderWidth;
1999 		rect.bottom += borderWidth;
2000 
2001 		auto insideBorderRect = rect;
2002 
2003 		rect.left -= cs.paddingLeft;
2004 		rect.right += cs.paddingRight;
2005 		rect.top -= cs.paddingTop;
2006 		rect.bottom += cs.paddingBottom;
2007 
2008 		return rect;
2009 	}
2010 
2011 
2012 	// FIXME: I kinda want to hide events from implementation widgets
2013 	// so it just catches them all and stops propagation...
2014 	// i guess i can do it with a event listener on star.
2015 
2016 	mixin Emits!KeyDownEvent; ///
2017 	mixin Emits!KeyUpEvent; ///
2018 	mixin Emits!CharEvent; ///
2019 
2020 	mixin Emits!MouseDownEvent; ///
2021 	mixin Emits!MouseUpEvent; ///
2022 	mixin Emits!ClickEvent; ///
2023 	mixin Emits!DoubleClickEvent; ///
2024 	mixin Emits!MouseMoveEvent; ///
2025 	mixin Emits!MouseOverEvent; ///
2026 	mixin Emits!MouseOutEvent; ///
2027 	mixin Emits!MouseEnterEvent; ///
2028 	mixin Emits!MouseLeaveEvent; ///
2029 
2030 	mixin Emits!ResizeEvent; ///
2031 
2032 	mixin Emits!BlurEvent; ///
2033 	mixin Emits!FocusEvent; ///
2034 
2035 	mixin Emits!FocusInEvent; ///
2036 	mixin Emits!FocusOutEvent; ///
2037 }
2038 
2039 /+
2040 /++
2041 	Interface to indicate that the widget has a simple value property.
2042 
2043 	History:
2044 		Added August 26, 2021
2045 +/
2046 interface HasValue!T {
2047 	/// Getter
2048 	@property T value();
2049 	/// Setter
2050 	@property void value(T);
2051 }
2052 
2053 /++
2054 	Interface to indicate that the widget has a range of possible values for its simple value property.
2055 	This would be present on something like a slider or possibly a number picker.
2056 
2057 	History:
2058 		Added September 11, 2021
2059 +/
2060 interface HasRangeOfValues!T : HasValue!T {
2061 	/// The minimum and maximum values in the range, inclusive.
2062 	@property T minValue();
2063 	@property void minValue(T); /// ditto
2064 	@property T maxValue(); /// ditto
2065 	@property void maxValue(T); /// ditto
2066 
2067 	/// The smallest step the user interface allows. User may still type in values without this limitation.
2068 	@property void step(T);
2069 	@property T step(); /// ditto
2070 }
2071 
2072 /++
2073 	Interface to indicate that the widget has a list of possible values the user can choose from.
2074 	This would be present on something like a drop-down selector.
2075 
2076 	The value is NOT necessarily one of the items on the list. Consider the case of a free-entry
2077 	combobox.
2078 
2079 	History:
2080 		Added September 11, 2021
2081 +/
2082 interface HasListOfValues!T : HasValue!T {
2083 	@property T[] values;
2084 	@property void values(T[]);
2085 
2086 	@property int selectedIndex(); // note it may return -1!
2087 	@property void selectedIndex(int);
2088 }
2089 +/
2090 
2091 /++
2092 	History:
2093 		Added September 2021 (dub v10.4)
2094 +/
2095 class GridLayout : Layout {
2096 
2097 	// FIXME: grid padding around edges and also cell spacing between units. even though you could do that by just specifying some gutter yourself in the layout.
2098 
2099 	/++
2100 		If a widget is too small to fill a grid cell, the graviy tells where it "sticks" to.
2101 	+/
2102 	enum Gravity {
2103 		Center    = 0,
2104 		NorthWest = North | West,
2105 		North     = 0b10_00,
2106 		NorthEast = North | East,
2107 		West      = 0b00_10,
2108 		East      = 0b00_01,
2109 		SouthWest = South | West,
2110 		South     = 0b01_00,
2111 		SouthEast = South | East,
2112 	}
2113 
2114 	/++
2115 		The width and height are in some proportional units and can often just be 12.
2116 	+/
2117 	this(int width, int height, Widget parent) {
2118 		this.gridWidth = width;
2119 		this.gridHeight = height;
2120 		super(parent);
2121 	}
2122 
2123 	/++
2124 		Sets the position of the given child.
2125 
2126 		The units of these arguments are in the proportional grid units you set in the constructor.
2127 	+/
2128 	Widget setChildPosition(return Widget child, int x, int y, int width, int height, Gravity gravity = Gravity.Center) {
2129 		// ensure it is in bounds
2130 		// then ensure no overlaps
2131 
2132 		ChildPosition p = ChildPosition(child, x, y, width, height, gravity);
2133 
2134 		foreach(ref position; positions) {
2135 			if(position.widget is child) {
2136 				position = p;
2137 				goto set;
2138 			}
2139 		}
2140 
2141 		positions ~= p;
2142 
2143 		set:
2144 
2145 		// FIXME: should this batch?
2146 		queueRecomputeChildLayout();
2147 
2148 		return child;
2149 	}
2150 
2151 	override void addChild(Widget w, int position = int.max) {
2152 		super.addChild(w, position);
2153 		//positions ~= ChildPosition(w);
2154 		if(position != int.max) {
2155 			// FIXME: align it so they actually match.
2156 		}
2157 	}
2158 
2159 	override void widgetRemoved(size_t idx, Widget w) {
2160 		// FIXME: keep the positions array aligned
2161 		// positions[idx].widget = null;
2162 	}
2163 
2164 	override void recomputeChildLayout() {
2165 		registerMovement();
2166 		int onGrid = cast(int) positions.length;
2167 		c: foreach(child; children) {
2168 			// just snap it to the grid
2169 			if(onGrid)
2170 			foreach(position; positions)
2171 				if(position.widget is child) {
2172 					child.x = this.width * position.x / this.gridWidth;
2173 					child.y = this.height * position.y / this.gridHeight;
2174 					child.width = this.width * position.width / this.gridWidth;
2175 					child.height = this.height * position.height / this.gridHeight;
2176 
2177 					auto diff = child.width - child.maxWidth();
2178 					// FIXME: gravity?
2179 					if(diff > 0) {
2180 						child.width = child.width - diff;
2181 
2182 						if(position.gravity & Gravity.West) {
2183 							// nothing needed, already aligned
2184 						} else if(position.gravity & Gravity.East) {
2185 							child.x += diff;
2186 						} else {
2187 							child.x += diff / 2;
2188 						}
2189 					}
2190 
2191 					diff = child.height - child.maxHeight();
2192 					// FIXME: gravity?
2193 					if(diff > 0) {
2194 						child.height = child.height - diff;
2195 
2196 						if(position.gravity & Gravity.North) {
2197 							// nothing needed, already aligned
2198 						} else if(position.gravity & Gravity.South) {
2199 							child.y += diff;
2200 						} else {
2201 							child.y += diff / 2;
2202 						}
2203 					}
2204 
2205 
2206 					child.recomputeChildLayout();
2207 					onGrid--;
2208 					continue c;
2209 				}
2210 			// the position isn't given on the grid array, we'll just fill in from where the explicit ones left off.
2211 		}
2212 	}
2213 
2214 	private struct ChildPosition {
2215 		Widget widget;
2216 		int x;
2217 		int y;
2218 		int width;
2219 		int height;
2220 		Gravity gravity;
2221 	}
2222 	private ChildPosition[] positions;
2223 
2224 	int gridWidth = 12;
2225 	int gridHeight = 12;
2226 }
2227 
2228 ///
2229 abstract class ComboboxBase : Widget {
2230 	// if the user can enter arbitrary data, we want to use  2 == CBS_DROPDOWN
2231 	// or to always show the list, we want CBS_SIMPLE == 1
2232 	version(win32_widgets)
2233 		this(uint style, Widget parent) {
2234 			super(parent);
2235 			createWin32Window(this, "ComboBox"w, null, style);
2236 		}
2237 	else version(custom_widgets)
2238 		this(Widget parent) {
2239 			super(parent);
2240 
2241 			addEventListener((KeyDownEvent event) {
2242 				if(event.key == Key.Up) {
2243 					if(selection_ > -1) { // -1 means select blank
2244 						selection_--;
2245 						fireChangeEvent();
2246 					}
2247 					event.preventDefault();
2248 				}
2249 				if(event.key == Key.Down) {
2250 					if(selection_ + 1 < options.length) {
2251 						selection_++;
2252 						fireChangeEvent();
2253 					}
2254 					event.preventDefault();
2255 				}
2256 
2257 			});
2258 
2259 		}
2260 	else static assert(false);
2261 
2262 	/++
2263 		Returns the current list of options in the selection.
2264 
2265 		History:
2266 			Property accessor added March 1, 2022 (dub v10.7). Prior to that, it was private.
2267 	+/
2268 	final @property string[] options() const {
2269 		return cast(string[]) options_;
2270 	}
2271 
2272 	private string[] options_;
2273 	private int selection_ = -1;
2274 
2275 	/++
2276 		Adds an option to the end of options array.
2277 	+/
2278 	void addOption(string s) {
2279 		options_ ~= s;
2280 		version(win32_widgets)
2281 		SendMessageW(hwnd, 323 /*CB_ADDSTRING*/, 0, cast(LPARAM) toWstringzInternal(s));
2282 	}
2283 
2284 	/++
2285 		Gets the current selection as an index into the [options] array. Returns -1 if nothing is selected.
2286 	+/
2287 	int getSelection() {
2288 		return selection_;
2289 	}
2290 
2291 	/++
2292 		Returns the current selection as a string.
2293 
2294 		History:
2295 			Added November 17, 2021
2296 	+/
2297 	string getSelectionString() {
2298 		return selection_ == -1 ? null : options[selection_];
2299 	}
2300 
2301 	/++
2302 		Sets the current selection to an index in the options array, or to the given option if present.
2303 		Please note that the string version may do a linear lookup.
2304 
2305 		Returns:
2306 			the index you passed in
2307 
2308 		History:
2309 			The `string` based overload was added on March 1, 2022 (dub v10.7).
2310 
2311 			The return value was `void` prior to March 1, 2022.
2312 	+/
2313 	int setSelection(int idx) {
2314 		selection_ = idx;
2315 		version(win32_widgets)
2316 		SendMessageW(hwnd, 334 /*CB_SETCURSEL*/, idx, 0);
2317 
2318 		auto t = new SelectionChangedEvent(this, selection_, selection_ == -1 ? null : options[selection_]);
2319 		t.dispatch();
2320 
2321 		return idx;
2322 	}
2323 
2324 	/// ditto
2325 	int setSelection(string s) {
2326 		if(s !is null)
2327 		foreach(idx, item; options)
2328 			if(item == s) {
2329 				return setSelection(cast(int) idx);
2330 			}
2331 		return setSelection(-1);
2332 	}
2333 
2334 	/++
2335 		This event is fired when the selection changes. Note it inherits
2336 		from ChangeEvent!string, meaning you can use that as well, and it also
2337 		fills in [Event.intValue].
2338 	+/
2339 	static class SelectionChangedEvent : ChangeEvent!string {
2340 		this(Widget target, int iv, string sv) {
2341 			super(target, &stringValue);
2342 			this.iv = iv;
2343 			this.sv = sv;
2344 		}
2345 		immutable int iv;
2346 		immutable string sv;
2347 
2348 		override @property string stringValue() { return sv; }
2349 		override @property int intValue() { return iv; }
2350 	}
2351 
2352 	version(win32_widgets)
2353 	override void handleWmCommand(ushort cmd, ushort id) {
2354 		if(cmd == CBN_SELCHANGE) {
2355 			selection_ = cast(int) SendMessageW(hwnd, 327 /* CB_GETCURSEL */, 0, 0);
2356 			fireChangeEvent();
2357 		}
2358 	}
2359 
2360 	private void fireChangeEvent() {
2361 		if(selection_ >= options.length)
2362 			selection_ = -1;
2363 
2364 		auto t = new SelectionChangedEvent(this, selection_, selection_ == -1 ? null : options[selection_]);
2365 		t.dispatch();
2366 	}
2367 
2368 	version(win32_widgets) {
2369 		override int minHeight() { return defaultLineHeight + 6; }
2370 		override int maxHeight() { return defaultLineHeight + 6; }
2371 	} else {
2372 		override int minHeight() { return defaultLineHeight + 4; }
2373 		override int maxHeight() { return defaultLineHeight + 4; }
2374 	}
2375 
2376 	version(custom_widgets) {
2377 
2378 		// FIXME: this should scroll if there's too many elements to reasonably fit on screen
2379 
2380 		SimpleWindow dropDown;
2381 		void popup() {
2382 			auto w = width;
2383 			// FIXME: suggestedDropdownHeight see below
2384 			auto h = cast(int) this.options.length * defaultLineHeight + 8;
2385 
2386 			auto coord = this.globalCoordinates();
2387 			auto dropDown = new SimpleWindow(
2388 				w, h,
2389 				null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.dropdownMenu, WindowFlags.dontAutoShow, parentWindow ? parentWindow.win : null);
2390 
2391 			dropDown.move(coord.x, coord.y + this.height);
2392 
2393 			{
2394 				auto cs = getComputedStyle();
2395 				auto painter = dropDown.draw();
2396 				draw3dFrame(0, 0, w, h, painter, FrameStyle.risen, getComputedStyle().background.color);
2397 				auto p = Point(4, 4);
2398 				painter.outlineColor = cs.foregroundColor;
2399 				foreach(option; options) {
2400 					painter.drawText(p, option);
2401 					p.y += defaultLineHeight;
2402 				}
2403 			}
2404 
2405 			dropDown.setEventHandlers(
2406 				(MouseEvent event) {
2407 					if(event.type == MouseEventType.buttonReleased) {
2408 						dropDown.close();
2409 						auto element = (event.y - 4) / defaultLineHeight;
2410 						if(element >= 0 && element <= options.length) {
2411 							selection_ = element;
2412 
2413 							fireChangeEvent();
2414 						}
2415 					}
2416 				}
2417 			);
2418 
2419 			dropDown.visibilityChanged = (bool visible) {
2420 				if(visible) {
2421 					this.redraw();
2422 					dropDown.grabInput();
2423 				} else {
2424 					dropDown.releaseInputGrab();
2425 				}
2426 			};
2427 
2428 			dropDown.show();
2429 		}
2430 
2431 	}
2432 }
2433 
2434 /++
2435 	A drop-down list where the user must select one of the
2436 	given options. Like `<select>` in HTML.
2437 +/
2438 class DropDownSelection : ComboboxBase {
2439 	this(Widget parent) {
2440 		version(win32_widgets)
2441 			super(3 /* CBS_DROPDOWNLIST */ | WS_VSCROLL, parent);
2442 		else version(custom_widgets) {
2443 			super(parent);
2444 
2445 			addEventListener("focus", () { this.redraw; });
2446 			addEventListener("blur", () { this.redraw; });
2447 			addEventListener(EventType.change, () { this.redraw; });
2448 			addEventListener("mousedown", () { this.focus(); this.popup(); });
2449 			addEventListener((KeyDownEvent event) {
2450 				if(event.key == Key.Space)
2451 					popup();
2452 			});
2453 		} else static assert(false);
2454 	}
2455 
2456 	mixin Padding!q{2};
2457 	static class Style : Widget.Style {
2458 		override FrameStyle borderStyle() { return FrameStyle.risen; }
2459 	}
2460 	mixin OverrideStyle!Style;
2461 
2462 	version(custom_widgets)
2463 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
2464 		auto cs = getComputedStyle();
2465 
2466 		painter.drawText(bounds.upperLeft, selection_ == -1 ? "" : options[selection_]);
2467 
2468 		painter.outlineColor = cs.foregroundColor;
2469 		painter.fillColor = cs.foregroundColor;
2470 		Point[4] triangle;
2471 		enum padding = 6;
2472 		enum paddingV = 7;
2473 		enum triangleWidth = 10;
2474 		triangle[0] = Point(width - padding - triangleWidth, paddingV);
2475 		triangle[1] = Point(width - padding - triangleWidth / 2, height - paddingV);
2476 		triangle[2] = Point(width - padding - 0, paddingV);
2477 		triangle[3] = triangle[0];
2478 		painter.drawPolygon(triangle[]);
2479 
2480 		return bounds;
2481 	}
2482 
2483 	version(win32_widgets)
2484 	override void registerMovement() {
2485 		version(win32_widgets) {
2486 			if(hwnd) {
2487 				auto pos = getChildPositionRelativeToParentHwnd(this);
2488 				// the height given to this from Windows' perspective is supposed
2489 				// to include the drop down's height. so I add to it to give some
2490 				// room for that.
2491 				// FIXME: maybe make the subclass provide a suggestedDropdownHeight thing
2492 				MoveWindow(hwnd, pos[0], pos[1], width, height + 200, true);
2493 			}
2494 		}
2495 		sendResizeEvent();
2496 	}
2497 }
2498 
2499 /++
2500 	A text box with a drop down arrow listing selections.
2501 	The user can choose from the list, or type their own.
2502 +/
2503 class FreeEntrySelection : ComboboxBase {
2504 	this(Widget parent) {
2505 		version(win32_widgets)
2506 			super(2 /* CBS_DROPDOWN */, parent);
2507 		else version(custom_widgets) {
2508 			super(parent);
2509 			auto hl = new HorizontalLayout(this);
2510 			lineEdit = new LineEdit(hl);
2511 
2512 			tabStop = false;
2513 
2514 			lineEdit.addEventListener("focus", &lineEdit.selectAll);
2515 
2516 			auto btn = new class ArrowButton {
2517 				this() {
2518 					super(ArrowDirection.down, hl);
2519 				}
2520 				override int maxHeight() {
2521 					return int.max;
2522 				}
2523 			};
2524 			//btn.addDirectEventListener("focus", &lineEdit.focus);
2525 			btn.addEventListener("triggered", &this.popup);
2526 			addEventListener(EventType.change, (Event event) {
2527 				lineEdit.content = event.stringValue;
2528 				lineEdit.focus();
2529 				redraw();
2530 			});
2531 		}
2532 		else static assert(false);
2533 	}
2534 
2535 	version(custom_widgets) {
2536 		LineEdit lineEdit;
2537 	}
2538 }
2539 
2540 /++
2541 	A combination of free entry with a list below it.
2542 +/
2543 class ComboBox : ComboboxBase {
2544 	this(Widget parent) {
2545 		version(win32_widgets)
2546 			super(1 /* CBS_SIMPLE */ | CBS_NOINTEGRALHEIGHT, parent);
2547 		else version(custom_widgets) {
2548 			super(parent);
2549 			lineEdit = new LineEdit(this);
2550 			listWidget = new ListWidget(this);
2551 			listWidget.multiSelect = false;
2552 			listWidget.addEventListener(EventType.change, delegate(Widget, Event) {
2553 				string c = null;
2554 				foreach(option; listWidget.options)
2555 					if(option.selected) {
2556 						c = option.label;
2557 						break;
2558 					}
2559 				lineEdit.content = c;
2560 			});
2561 
2562 			listWidget.tabStop = false;
2563 			this.tabStop = false;
2564 			listWidget.addEventListener("focus", &lineEdit.focus);
2565 			this.addEventListener("focus", &lineEdit.focus);
2566 
2567 			addDirectEventListener(EventType.change, {
2568 				listWidget.setSelection(selection_);
2569 				if(selection_ != -1)
2570 					lineEdit.content = options[selection_];
2571 				lineEdit.focus();
2572 				redraw();
2573 			});
2574 
2575 			lineEdit.addEventListener("focus", &lineEdit.selectAll);
2576 
2577 			listWidget.addDirectEventListener(EventType.change, {
2578 				int set = -1;
2579 				foreach(idx, opt; listWidget.options)
2580 					if(opt.selected) {
2581 						set = cast(int) idx;
2582 						break;
2583 					}
2584 				if(set != selection_)
2585 					this.setSelection(set);
2586 			});
2587 		} else static assert(false);
2588 	}
2589 
2590 	override int minHeight() { return defaultLineHeight * 3; }
2591 	override int maxHeight() { return cast(int) options.length * defaultLineHeight + defaultLineHeight; }
2592 	override int heightStretchiness() { return 5; }
2593 
2594 	version(custom_widgets) {
2595 		LineEdit lineEdit;
2596 		ListWidget listWidget;
2597 
2598 		override void addOption(string s) {
2599 			listWidget.options ~= ListWidget.Option(s);
2600 			ComboboxBase.addOption(s);
2601 		}
2602 	}
2603 }
2604 
2605 /+
2606 class Spinner : Widget {
2607 	version(win32_widgets)
2608 	this(Widget parent) {
2609 		super(parent);
2610 		parentWindow = parent.parentWindow;
2611 		auto hlayout = new HorizontalLayout(this);
2612 		lineEdit = new LineEdit(hlayout);
2613 		upDownControl = new UpDownControl(hlayout);
2614 	}
2615 
2616 	LineEdit lineEdit;
2617 	UpDownControl upDownControl;
2618 }
2619 
2620 class UpDownControl : Widget {
2621 	version(win32_widgets)
2622 	this(Widget parent) {
2623 		super(parent);
2624 		parentWindow = parent.parentWindow;
2625 		createWin32Window(this, "msctls_updown32"w, null, 4/*UDS_ALIGNRIGHT*/| 2 /* UDS_SETBUDDYINT */ | 16 /* UDS_AUTOBUDDY */ | 32 /* UDS_ARROWKEYS */);
2626 	}
2627 
2628 	override int minHeight() { return defaultLineHeight; }
2629 	override int maxHeight() { return defaultLineHeight * 3/2; }
2630 
2631 	override int minWidth() { return defaultLineHeight * 3/2; }
2632 	override int maxWidth() { return defaultLineHeight * 3/2; }
2633 }
2634 +/
2635 
2636 /+
2637 class DataView : Widget {
2638 	// this is the omnibus data viewer
2639 	// the internal data layout is something like:
2640 	// string[string][] but also each node can have parents
2641 }
2642 +/
2643 
2644 
2645 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb775491(v=vs.85).aspx#PROGRESS_CLASS
2646 
2647 // http://svn.dsource.org/projects/bindings/trunk/win32/commctrl.d
2648 
2649 // FIXME: menus should prolly capture the mouse. ugh i kno.
2650 /*
2651 	TextEdit needs:
2652 
2653 	* caret manipulation
2654 	* selection control
2655 	* convenience functions for appendText, insertText, insertTextAtCaret, etc.
2656 
2657 	For example:
2658 
2659 	connect(paste, &textEdit.insertTextAtCaret);
2660 
2661 	would be nice.
2662 
2663 
2664 
2665 	I kinda want an omnibus dataview that combines list, tree,
2666 	and table - it can be switched dynamically between them.
2667 
2668 	Flattening policy: only show top level, show recursive, show grouped
2669 	List styles: plain list (e.g. <ul>), tiles (some details next to it), icons (like Windows explorer)
2670 
2671 	Single select, multi select, organization, drag+drop
2672 */
2673 
2674 //static if(UsingSimpledisplayX11)
2675 version(win32_widgets) {}
2676 else version(custom_widgets) {
2677 	enum scrollClickRepeatInterval = 50;
2678 
2679 deprecated("Get these properties off `Widget.getComputedStyle` instead. The defaults are now set in the `WidgetPainter.visualTheme`.") {
2680 	enum windowBackgroundColor = Color(212, 212, 212); // used to be 192
2681 	enum activeTabColor = lightAccentColor;
2682 	enum hoveringColor = Color(228, 228, 228);
2683 	enum buttonColor = windowBackgroundColor;
2684 	enum depressedButtonColor = darkAccentColor;
2685 	enum activeListXorColor = Color(255, 255, 127);
2686 	enum progressBarColor = Color(0, 0, 128);
2687 	enum activeMenuItemColor = Color(0, 0, 128);
2688 
2689 }}
2690 else static assert(false);
2691 deprecated("Get these properties off the `visualTheme` instead.") {
2692 	// these are used by horizontal rule so not just custom_widgets. for now at least.
2693 	enum darkAccentColor = Color(172, 172, 172);
2694 	enum lightAccentColor = Color(223, 223, 223); // used to be 223
2695 }
2696 
2697 private const(wchar)* toWstringzInternal(in char[] s) {
2698 	wchar[] str;
2699 	str.reserve(s.length + 1);
2700 	foreach(dchar ch; s)
2701 		str ~= ch;
2702 	str ~= '\0';
2703 	return str.ptr;
2704 }
2705 
2706 static if(SimpledisplayTimerAvailable)
2707 void setClickRepeat(Widget w, int interval, int delay = 250) {
2708 	Timer timer;
2709 	int delayRemaining = delay / interval;
2710 	if(delayRemaining <= 1)
2711 		delayRemaining = 2;
2712 
2713 	immutable originalDelayRemaining = delayRemaining;
2714 
2715 	w.addDirectEventListener((scope MouseDownEvent ev) {
2716 		if(ev.srcElement !is w)
2717 			return;
2718 		if(timer !is null) {
2719 			timer.destroy();
2720 			timer = null;
2721 		}
2722 		delayRemaining = originalDelayRemaining;
2723 		timer = new Timer(interval, () {
2724 			if(delayRemaining > 0)
2725 				delayRemaining--;
2726 			else {
2727 				auto ev = new Event("triggered", w);
2728 				ev.sendDirectly();
2729 			}
2730 		});
2731 	});
2732 
2733 	w.addDirectEventListener((scope MouseUpEvent ev) {
2734 		if(ev.srcElement !is w)
2735 			return;
2736 		if(timer !is null) {
2737 			timer.destroy();
2738 			timer = null;
2739 		}
2740 	});
2741 
2742 	w.addDirectEventListener((scope MouseLeaveEvent ev) {
2743 		if(ev.srcElement !is w)
2744 			return;
2745 		if(timer !is null) {
2746 			timer.destroy();
2747 			timer = null;
2748 		}
2749 	});
2750 
2751 }
2752 else
2753 void setClickRepeat(Widget w, int interval, int delay = 250) {}
2754 
2755 enum FrameStyle {
2756 	none, ///
2757 	risen, /// a 3d pop-out effect (think Windows 95 button)
2758 	sunk, /// a 3d sunken effect (think Windows 95 button as you click on it)
2759 	solid, ///
2760 	dotted, ///
2761 	fantasy, /// a style based on a popular fantasy video game
2762 }
2763 
2764 version(custom_widgets)
2765 deprecated
2766 void draw3dFrame(Widget widget, ScreenPainter painter, FrameStyle style) {
2767 	draw3dFrame(0, 0, widget.width, widget.height, painter, style, WidgetPainter.visualTheme.windowBackgroundColor);
2768 }
2769 
2770 version(custom_widgets)
2771 void draw3dFrame(Widget widget, ScreenPainter painter, FrameStyle style, Color background) {
2772 	draw3dFrame(0, 0, widget.width, widget.height, painter, style, background);
2773 }
2774 
2775 version(custom_widgets)
2776 deprecated
2777 void draw3dFrame(int x, int y, int width, int height, ScreenPainter painter, FrameStyle style) {
2778 	draw3dFrame(x, y, width, height, painter, style, WidgetPainter.visualTheme.windowBackgroundColor);
2779 }
2780 
2781 int getBorderWidth(FrameStyle style) {
2782 	final switch(style) {
2783 		case FrameStyle.sunk, FrameStyle.risen:
2784 			return 2;
2785 		case FrameStyle.none:
2786 			return 0;
2787 		case FrameStyle.solid:
2788 			return 1;
2789 		case FrameStyle.dotted:
2790 			return 1;
2791 		case FrameStyle.fantasy:
2792 			return 3;
2793 	}
2794 }
2795 
2796 int draw3dFrame(int x, int y, int width, int height, ScreenPainter painter, FrameStyle style, Color background, Color border = Color.transparent) {
2797 	int borderWidth = getBorderWidth(style);
2798 	final switch(style) {
2799 		case FrameStyle.sunk, FrameStyle.risen:
2800 			// outer layer
2801 			painter.outlineColor = style == FrameStyle.sunk ? Color.white : Color.black;
2802 		break;
2803 		case FrameStyle.none:
2804 			painter.outlineColor = background;
2805 		break;
2806 		case FrameStyle.solid:
2807 			painter.pen = Pen(border, 1);
2808 		break;
2809 		case FrameStyle.dotted:
2810 			painter.pen = Pen(border, 1, Pen.Style.Dotted);
2811 		break;
2812 		case FrameStyle.fantasy:
2813 			painter.pen = Pen(border, 3);
2814 		break;
2815 	}
2816 
2817 	painter.fillColor = background;
2818 	painter.drawRectangle(Point(x + 0, y + 0), width, height);
2819 
2820 
2821 	if(style == FrameStyle.sunk || style == FrameStyle.risen) {
2822 		// 3d effect
2823 		auto vt = WidgetPainter.visualTheme;
2824 
2825 		painter.outlineColor = (style == FrameStyle.sunk) ? vt.darkAccentColor : vt.lightAccentColor;
2826 		painter.drawLine(Point(x + 0, y + 0), Point(x + width, y + 0));
2827 		painter.drawLine(Point(x + 0, y + 0), Point(x + 0, y + height - 1));
2828 
2829 		// inner layer
2830 		//right, bottom
2831 		painter.outlineColor = (style == FrameStyle.sunk) ? vt.lightAccentColor : vt.darkAccentColor;
2832 		painter.drawLine(Point(x + width - 2, y + 2), Point(x + width - 2, y + height - 2));
2833 		painter.drawLine(Point(x + 2, y + height - 2), Point(x + width - 2, y + height - 2));
2834 		// left, top
2835 		painter.outlineColor = (style == FrameStyle.sunk) ? Color.black : Color.white;
2836 		painter.drawLine(Point(x + 1, y + 1), Point(x + width, y + 1));
2837 		painter.drawLine(Point(x + 1, y + 1), Point(x + 1, y + height - 2));
2838 	} else if(style == FrameStyle.fantasy) {
2839 		painter.pen = Pen(Color.white, 1, Pen.Style.Solid);
2840 		painter.fillColor = Color.transparent;
2841 		painter.drawRectangle(Point(x + 1, y + 1), Point(x + width - 1, y + height - 1));
2842 	}
2843 
2844 	return borderWidth;
2845 }
2846 
2847 /++
2848 	An `Action` represents some kind of user action they can trigger through menu options, toolbars, hotkeys, and similar mechanisms. The text label, icon, and handlers are centrally held here instead of repeated in each UI element.
2849 
2850 	See_Also:
2851 		[MenuItem]
2852 		[ToolButton]
2853 		[Menu.addItem]
2854 +/
2855 class Action {
2856 	version(win32_widgets) {
2857 		private int id;
2858 		private static int lastId = 9000;
2859 		private static Action[int] mapping;
2860 	}
2861 
2862 	KeyEvent accelerator;
2863 
2864 	// FIXME: disable message
2865 	// and toggle thing?
2866 	// ??? and trigger arguments too ???
2867 
2868 	/++
2869 		Params:
2870 			label = the textual label
2871 			icon = icon ID. See [GenericIcons]. There is currently no way to do custom icons.
2872 			triggered = initial handler, more can be added via the [triggered] member.
2873 	+/
2874 	this(string label, ushort icon = 0, void delegate() triggered = null) {
2875 		this.label = label;
2876 		this.iconId = icon;
2877 		if(triggered !is null)
2878 			this.triggered ~= triggered;
2879 		version(win32_widgets) {
2880 			id = ++lastId;
2881 			mapping[id] = this;
2882 		}
2883 	}
2884 
2885 	private string label;
2886 	private ushort iconId;
2887 	// icon
2888 
2889 	// when it is triggered, the triggered event is fired on the window
2890 	/// The list of handlers when it is triggered.
2891 	void delegate()[] triggered;
2892 }
2893 
2894 /*
2895 	plan:
2896 		keyboard accelerators
2897 
2898 		* menus (and popups and tooltips)
2899 		* status bar
2900 		* toolbars and buttons
2901 
2902 		sortable table view
2903 
2904 		maybe notification area icons
2905 		basic clipboard
2906 
2907 		* radio box
2908 		splitter
2909 		toggle buttons (optionally mutually exclusive, like in Paint)
2910 		label, rich text display, multi line plain text (selectable)
2911 		* fieldset
2912 		* nestable grid layout
2913 		single line text input
2914 		* multi line text input
2915 		slider
2916 		spinner
2917 		list box
2918 		drop down
2919 		combo box
2920 		auto complete box
2921 		* progress bar
2922 
2923 		terminal window/widget (on unix it might even be a pty but really idk)
2924 
2925 		ok button
2926 		cancel button
2927 
2928 		keyboard hotkeys
2929 
2930 		scroll widget
2931 
2932 		event redirections and network transparency
2933 		script integration
2934 */
2935 
2936 
2937 /*
2938 	MENUS
2939 
2940 	auto bar = new MenuBar(window);
2941 	window.menuBar = bar;
2942 
2943 	auto fileMenu = bar.addItem(new Menu("&File"));
2944 	fileMenu.addItem(new MenuItem("&Exit"));
2945 
2946 
2947 	EVENTS
2948 
2949 	For controls, you should usually use "triggered" rather than "click", etc., because
2950 	triggered handles both keyboard (focus and press as well as hotkeys) and mouse activation.
2951 	This is the case on menus and pushbuttons.
2952 
2953 	"click", on the other hand, currently only fires when it is literally clicked by the mouse.
2954 */
2955 
2956 
2957 /*
2958 enum LinePreference {
2959 	AlwaysOnOwnLine, // always on its own line
2960 	PreferOwnLine, // it will always start a new line, and if max width <= line width, it will expand all the way
2961 	PreferToShareLine, // does not force new line, and if the next child likes to share too, they will div it up evenly. otherwise, it will expand as much as it can
2962 }
2963 */
2964 
2965 /++
2966 	Convenience mixin for overriding all four sides of margin or padding in a [Widget] with the same code. It mixes in the given string as the return value of the four overridden methods.
2967 
2968 	---
2969 	class MyWidget : Widget {
2970 		this(Widget parent) { super(parent); }
2971 
2972 		// set paddingLeft, paddingRight, paddingTop, and paddingBottom all to `return 4;` in one go:
2973 		mixin Padding!q{4};
2974 
2975 		// set marginLeft, marginRight, marginTop, and marginBottom all to `return 8;` in one go:
2976 		mixin Margin!q{8};
2977 
2978 		// but if I specify one outside, it overrides the override, so now marginLeft is 2,
2979 		// while Top/Bottom/Right remain 8 from the mixin above.
2980 		override int marginLeft() { return 2; }
2981 	}
2982 	---
2983 
2984 
2985 	The minigui layout model is based on the web's CSS box model. The layout engine* arranges widgets based on their margin for separation and assigns them a size based on thier preferences (e.g. [Widget.minHeight]) and the available space. Widgets are assigned a size by the layout engine. Inside this size, they have a border (see [Widget.Style.borderWidth]), then padding space, and then their content. Their content box may also have an outline drawn on top of it (see [Widget.Style.outlineStyle]).
2986 
2987 	Padding is the area inside a widget where its background is drawn, but the content avoids.
2988 
2989 	Margin is the area between widgets. The algorithm is the spacing between any two widgets is the max of their adjacent margins (not the sum!).
2990 
2991 	* Some widgets do not participate in placement, e.g. [StaticPosition], and some layout systems do their own separate thing too; ultimately, these properties are just hints to the layout function and you can always implement your own to do whatever you want. But this statement is still mostly true.
2992 +/
2993 mixin template Padding(string code) {
2994 	override int paddingLeft() { return mixin(code);}
2995 	override int paddingRight() { return mixin(code);}
2996 	override int paddingTop() { return mixin(code);}
2997 	override int paddingBottom() { return mixin(code);}
2998 }
2999 
3000 /// ditto
3001 mixin template Margin(string code) {
3002 	override int marginLeft() { return mixin(code);}
3003 	override int marginRight() { return mixin(code);}
3004 	override int marginTop() { return mixin(code);}
3005 	override int marginBottom() { return mixin(code);}
3006 }
3007 
3008 private
3009 void recomputeChildLayout(string relevantMeasure)(Widget parent) {
3010 	enum calcingV = relevantMeasure == "height";
3011 
3012 	parent.registerMovement();
3013 
3014 	if(parent.children.length == 0)
3015 		return;
3016 
3017 	auto parentStyle = parent.getComputedStyle();
3018 
3019 	enum firstThingy = relevantMeasure == "height" ? "Top" : "Left";
3020 	enum secondThingy = relevantMeasure == "height" ? "Bottom" : "Right";
3021 
3022 	enum otherFirstThingy = relevantMeasure == "height" ? "Left" : "Top";
3023 	enum otherSecondThingy = relevantMeasure == "height" ? "Right" : "Bottom";
3024 
3025 	// my own width and height should already be set by the caller of this function...
3026 	int spaceRemaining = mixin("parent." ~ relevantMeasure) -
3027 		mixin("parentStyle.padding"~firstThingy~"()") -
3028 		mixin("parentStyle.padding"~secondThingy~"()");
3029 
3030 	int stretchinessSum;
3031 	int stretchyChildSum;
3032 	int lastMargin = 0;
3033 
3034 	int shrinkinessSum;
3035 	int shrinkyChildSum;
3036 
3037 	// set initial size
3038 	foreach(child; parent.children) {
3039 
3040 		auto childStyle = child.getComputedStyle();
3041 
3042 		if(cast(StaticPosition) child)
3043 			continue;
3044 		if(child.hidden)
3045 			continue;
3046 
3047 		const iw = child.flexBasisWidth();
3048 		const ih = child.flexBasisHeight();
3049 
3050 		static if(calcingV) {
3051 			child.width = parent.width -
3052 				mixin("childStyle.margin"~otherFirstThingy~"()") -
3053 				mixin("childStyle.margin"~otherSecondThingy~"()") -
3054 				mixin("parentStyle.padding"~otherFirstThingy~"()") -
3055 				mixin("parentStyle.padding"~otherSecondThingy~"()");
3056 
3057 			if(child.width < 0)
3058 				child.width = 0;
3059 			if(child.width > childStyle.maxWidth())
3060 				child.width = childStyle.maxWidth();
3061 
3062 			if(iw > 0) {
3063 				auto totalPossible = child.width;
3064 				if(child.width > iw && child.widthStretchiness() == 0)
3065 					child.width = iw;
3066 			}
3067 
3068 			child.height = mymax(childStyle.minHeight(), ih);
3069 		} else {
3070 			// set to take all the space
3071 			child.height = parent.height -
3072 				mixin("childStyle.margin"~firstThingy~"()") -
3073 				mixin("childStyle.margin"~secondThingy~"()") -
3074 				mixin("parentStyle.padding"~firstThingy~"()") -
3075 				mixin("parentStyle.padding"~secondThingy~"()");
3076 
3077 			// then clamp it
3078 			if(child.height < 0)
3079 				child.height = 0;
3080 			if(child.height > childStyle.maxHeight())
3081 				child.height = childStyle.maxHeight();
3082 
3083 			// and if possible, respect the ideal target
3084 			if(ih > 0) {
3085 				auto totalPossible = child.height;
3086 				if(child.height > ih && child.heightStretchiness() == 0)
3087 					child.height = ih;
3088 			}
3089 
3090 			// if we have an ideal, try to respect it, otehrwise, just use the minimum
3091 			child.width = mymax(childStyle.minWidth(), iw);
3092 		}
3093 
3094 		spaceRemaining -= mixin("child." ~ relevantMeasure);
3095 
3096 		int thisMargin = mymax(lastMargin, mixin("childStyle.margin"~firstThingy~"()"));
3097 		auto margin = mixin("childStyle.margin" ~ secondThingy ~ "()");
3098 		lastMargin = margin;
3099 		spaceRemaining -= thisMargin + margin;
3100 
3101 		auto s = mixin("child." ~ relevantMeasure ~ "Stretchiness()");
3102 		stretchinessSum += s;
3103 		if(s > 0)
3104 			stretchyChildSum++;
3105 
3106 		auto s2 = mixin("child." ~ relevantMeasure ~ "Shrinkiness()");
3107 		shrinkinessSum += s2;
3108 		if(s2 > 0)
3109 			shrinkyChildSum++;
3110 	}
3111 
3112 	if(spaceRemaining < 0 && shrinkyChildSum) {
3113 		// shrink to get into the space if it is possible
3114 		auto toRemove = -spaceRemaining;
3115 		auto removalPerItem  = toRemove * shrinkinessSum / shrinkyChildSum;
3116 		auto remainder = toRemove * shrinkinessSum % shrinkyChildSum;
3117 
3118 		// FIXME: wtf why am i shrinking things with no shrinkiness?
3119 
3120 		foreach(child; parent.children) {
3121 			auto childStyle = child.getComputedStyle();
3122 			if(cast(StaticPosition) child)
3123 				continue;
3124 			if(child.hidden)
3125 				continue;
3126 			static if(calcingV) {
3127 				auto maximum = childStyle.maxHeight();
3128 			} else {
3129 				auto maximum = childStyle.maxWidth();
3130 			}
3131 
3132 			mixin("child._" ~ relevantMeasure) -= removalPerItem + remainder; // this is removing more than needed to trigger the next thing. ugh.
3133 
3134 			spaceRemaining += removalPerItem + remainder;
3135 		}
3136 	}
3137 
3138 	// stretch to fill space
3139 	while(spaceRemaining > 0 && stretchinessSum && stretchyChildSum) {
3140 		auto spacePerChild = spaceRemaining / stretchinessSum;
3141 		bool spreadEvenly;
3142 		bool giveToBiggest;
3143 		if(spacePerChild <= 0) {
3144 			spacePerChild = spaceRemaining / stretchyChildSum;
3145 			spreadEvenly = true;
3146 		}
3147 		if(spacePerChild <= 0) {
3148 			giveToBiggest = true;
3149 		}
3150 		int previousSpaceRemaining = spaceRemaining;
3151 		stretchinessSum = 0;
3152 		Widget mostStretchy;
3153 		int mostStretchyS;
3154 		foreach(child; parent.children) {
3155 			auto childStyle = child.getComputedStyle();
3156 			if(cast(StaticPosition) child)
3157 				continue;
3158 			if(child.hidden)
3159 				continue;
3160 			static if(calcingV) {
3161 				auto maximum = childStyle.maxHeight();
3162 			} else {
3163 				auto maximum = childStyle.maxWidth();
3164 			}
3165 
3166 			if(mixin("child." ~ relevantMeasure) >= maximum) {
3167 				auto adj = mixin("child." ~ relevantMeasure) - maximum;
3168 				mixin("child._" ~ relevantMeasure) -= adj;
3169 				spaceRemaining += adj;
3170 				continue;
3171 			}
3172 			auto s = mixin("child." ~ relevantMeasure ~ "Stretchiness()");
3173 			if(s <= 0)
3174 				continue;
3175 			auto spaceAdjustment = spacePerChild * (spreadEvenly ? 1 : s);
3176 			mixin("child._" ~ relevantMeasure) += spaceAdjustment;
3177 			spaceRemaining -= spaceAdjustment;
3178 			if(mixin("child." ~ relevantMeasure) > maximum) {
3179 				auto diff = mixin("child." ~ relevantMeasure) - maximum;
3180 				mixin("child._" ~ relevantMeasure) -= diff;
3181 				spaceRemaining += diff;
3182 			} else if(mixin("child._" ~ relevantMeasure) < maximum) {
3183 				stretchinessSum += mixin("child." ~ relevantMeasure ~ "Stretchiness()");
3184 				if(mostStretchy is null || s >= mostStretchyS) {
3185 					mostStretchy = child;
3186 					mostStretchyS = s;
3187 				}
3188 			}
3189 		}
3190 
3191 		if(giveToBiggest && mostStretchy !is null) {
3192 			auto child = mostStretchy;
3193 			auto childStyle = child.getComputedStyle();
3194 			int spaceAdjustment = spaceRemaining;
3195 
3196 			static if(calcingV)
3197 				auto maximum = childStyle.maxHeight();
3198 			else
3199 				auto maximum = childStyle.maxWidth();
3200 
3201 			mixin("child._" ~ relevantMeasure) += spaceAdjustment;
3202 			spaceRemaining -= spaceAdjustment;
3203 			if(mixin("child._" ~ relevantMeasure) > maximum) {
3204 				auto diff = mixin("child." ~ relevantMeasure) - maximum;
3205 				mixin("child._" ~ relevantMeasure) -= diff;
3206 				spaceRemaining += diff;
3207 			}
3208 		}
3209 
3210 		if(spaceRemaining == previousSpaceRemaining) {
3211 			if(mostStretchy !is null) {
3212 				static if(calcingV)
3213 					auto maximum = mostStretchy.maxHeight();
3214 				else
3215 					auto maximum = mostStretchy.maxWidth();
3216 
3217 				mixin("mostStretchy._" ~ relevantMeasure) += spaceRemaining;
3218 				if(mixin("mostStretchy._" ~ relevantMeasure) > maximum)
3219 					mixin("mostStretchy._" ~ relevantMeasure) = maximum;
3220 			}
3221 			break; // apparently nothing more we can do
3222 		}
3223 
3224 	}
3225 
3226 	// position
3227 	lastMargin = 0;
3228 	int currentPos = mixin("parent.padding"~firstThingy~"()");
3229 	foreach(child; parent.children) {
3230 		auto childStyle = child.getComputedStyle();
3231 		if(cast(StaticPosition) child) {
3232 			child.recomputeChildLayout();
3233 			continue;
3234 		}
3235 		if(child.hidden)
3236 			continue;
3237 		auto margin = mixin("childStyle.margin" ~ secondThingy ~ "()");
3238 		int thisMargin = mymax(lastMargin, mixin("childStyle.margin"~firstThingy~"()"));
3239 		currentPos += thisMargin;
3240 		static if(calcingV) {
3241 			child.x = parentStyle.paddingLeft() + childStyle.marginLeft();
3242 			child.y = currentPos;
3243 		} else {
3244 			child.x = currentPos;
3245 			child.y = parentStyle.paddingTop() + childStyle.marginTop();
3246 
3247 		}
3248 		currentPos += mixin("child." ~ relevantMeasure);
3249 		currentPos += margin;
3250 		lastMargin = margin;
3251 
3252 		child.recomputeChildLayout();
3253 	}
3254 }
3255 
3256 int mymax(int a, int b) { return a > b ? a : b; }
3257 int mymax(int a, int b, int c) {
3258 	auto d = mymax(a, b);
3259 	return c > d ? c : d;
3260 }
3261 
3262 // OK so we need to make getting at the native window stuff possible in simpledisplay.d
3263 // and here, it must be integrable with the layout, the event system, and not be painted over.
3264 version(win32_widgets) {
3265 
3266 	// this function just does stuff that a parent window needs for redirection
3267 	int WindowProcedureHelper(Widget this_, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
3268 		this_.hookedWndProc(msg, wParam, lParam);
3269 
3270 		switch(msg) {
3271 
3272 			case WM_VSCROLL, WM_HSCROLL:
3273 				auto pos = HIWORD(wParam);
3274 				auto m = LOWORD(wParam);
3275 
3276 				auto scrollbarHwnd = cast(HWND) lParam;
3277 
3278 				if(auto widgetp = scrollbarHwnd in Widget.nativeMapping) {
3279 
3280 					//auto smw = cast(ScrollMessageWidget) widgetp.parent;
3281 
3282 					switch(m) {
3283 						/+
3284 						// I don't think those messages are ever actually sent normally by the widget itself,
3285 						// they are more used for the keyboard interface. methinks.
3286 						case SB_BOTTOM:
3287 							//import std.stdio; writeln("end");
3288 							auto event = new Event("scrolltoend", *widgetp);
3289 							event.dispatch();
3290 							//if(!event.defaultPrevented)
3291 						break;
3292 						case SB_TOP:
3293 							//import std.stdio; writeln("top");
3294 							auto event = new Event("scrolltobeginning", *widgetp);
3295 							event.dispatch();
3296 						break;
3297 						case SB_ENDSCROLL:
3298 							// idk
3299 						break;
3300 						+/
3301 						case SB_LINEDOWN:
3302 							(*widgetp).emitCommand!"scrolltonextline"();
3303 						return 0;
3304 						case SB_LINEUP:
3305 							(*widgetp).emitCommand!"scrolltopreviousline"();
3306 						return 0;
3307 						case SB_PAGEDOWN:
3308 							(*widgetp).emitCommand!"scrolltonextpage"();
3309 						return 0;
3310 						case SB_PAGEUP:
3311 							(*widgetp).emitCommand!"scrolltopreviouspage"();
3312 						return 0;
3313 						case SB_THUMBPOSITION:
3314 							auto ev = new ScrollToPositionEvent(*widgetp, pos);
3315 							ev.dispatch();
3316 						return 0;
3317 						case SB_THUMBTRACK:
3318 							// eh kinda lying but i like the real time update display
3319 							auto ev = new ScrollToPositionEvent(*widgetp, pos);
3320 							ev.dispatch();
3321 
3322 							// the event loop doesn't seem to carry on with a requested redraw..
3323 							// so we request it to get our dirty bit set...
3324 							// then we need to immediately actually redraw it too for instant feedback to user
3325 							SimpleWindow.processAllCustomEvents();
3326 							SimpleWindow.processAllCustomEvents();
3327 							//if(this_.parentWindow)
3328 								//this_.parentWindow.actualRedraw();
3329 
3330 							// and this ensures the WM_PAINT message is sent fairly quickly
3331 							// still seems to lag a little in large windows but meh it basically works.
3332 							if(this_.parentWindow) {
3333 								// FIXME: if painting is slow, this does still lag
3334 								// we probably will want to expose some user hook to ScrollWindowEx
3335 								// or something.
3336 								UpdateWindow(this_.parentWindow.hwnd);
3337 							}
3338 						return 0;
3339 						default:
3340 					}
3341 				}
3342 			break;
3343 
3344 			case WM_CONTEXTMENU:
3345 				auto hwndFrom = cast(HWND) wParam;
3346 
3347 				auto xPos = cast(short) LOWORD(lParam);
3348 				auto yPos = cast(short) HIWORD(lParam);
3349 
3350 				if(auto widgetp = hwndFrom in Widget.nativeMapping) {
3351 					POINT p;
3352 					p.x = xPos;
3353 					p.y = yPos;
3354 					ScreenToClient(hwnd, &p);
3355 					auto clientX = cast(ushort) p.x;
3356 					auto clientY = cast(ushort) p.y;
3357 
3358 					auto wap = widgetAtPoint(*widgetp, clientX, clientY);
3359 
3360 					if(wap.widget.showContextMenu(wap.x, wap.y, xPos, yPos)) {
3361 						return 0;
3362 					}
3363 				}
3364 			break;
3365 
3366 			case WM_DRAWITEM:
3367 				auto dis = cast(DRAWITEMSTRUCT*) lParam;
3368 				if(auto widgetp = dis.hwndItem in Widget.nativeMapping) {
3369 					return (*widgetp).handleWmDrawItem(dis);
3370 				}
3371 			break;
3372 
3373 			case WM_NOTIFY:
3374 				auto hdr = cast(NMHDR*) lParam;
3375 				auto hwndFrom = hdr.hwndFrom;
3376 				auto code = hdr.code;
3377 
3378 				if(auto widgetp = hwndFrom in Widget.nativeMapping) {
3379 					return (*widgetp).handleWmNotify(hdr, code, mustReturn);
3380 				}
3381 			break;
3382 			case WM_COMMAND:
3383 				auto handle = cast(HWND) lParam;
3384 				auto cmd = HIWORD(wParam);
3385 				return processWmCommand(hwnd, handle, cmd, LOWORD(wParam));
3386 
3387 			default:
3388 				// pass it on
3389 		}
3390 		return 0;
3391 	}
3392 
3393 
3394 
3395 	extern(Windows)
3396 	private
3397 	// this is called by native child windows, whereas the other hook is done by simpledisplay windows
3398 	// but can i merge them?!
3399 	LRESULT HookedWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
3400 		//import std.stdio; try { writeln(iMessage); } catch(Exception e) {};
3401 
3402 		if(auto te = hWnd in Widget.nativeMapping) {
3403 			try {
3404 
3405 				te.hookedWndProc(iMessage, wParam, lParam);
3406 
3407 				int mustReturn;
3408 				auto ret = WindowProcedureHelper(*te, hWnd, iMessage, wParam, lParam, mustReturn);
3409 				if(mustReturn)
3410 					return ret;
3411 
3412 				if(iMessage == WM_SETFOCUS) {
3413 					auto lol = *te;
3414 					while(lol !is null && lol.implicitlyCreated)
3415 						lol = lol.parent;
3416 					lol.focus();
3417 					//(*te).parentWindow.focusedWidget = lol;
3418 				}
3419 
3420 
3421 				if(iMessage == WM_CTLCOLORBTN || iMessage == WM_CTLCOLORSTATIC) {
3422 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
3423 					return cast(typeof(return)) GetSysColorBrush(COLOR_3DFACE); // this is the window background color...
3424 						//GetStockObject(NULL_BRUSH);
3425 				}
3426 
3427 				auto pos = getChildPositionRelativeToParentOrigin(*te);
3428 				lastDefaultPrevented = false;
3429 				// try {import std.stdio; writeln(typeid(*te)); } catch(Exception e) {}
3430 				if(SimpleWindow.triggerEvents(hWnd, iMessage, wParam, lParam, pos[0], pos[1], (*te).parentWindow.win) || !lastDefaultPrevented)
3431 					return CallWindowProcW((*te).originalWindowProcedure, hWnd, iMessage, wParam, lParam);
3432 				else {
3433 					// it was something we recognized, should only call the window procedure if the default was not prevented
3434 				}
3435 			} catch(Exception e) {
3436 				assert(0, e.toString());
3437 			}
3438 			return 0;
3439 		}
3440 		assert(0, "shouldn't be receiving messages for this window....");
3441 		//import std.conv;
3442 		//assert(0, to!string(hWnd) ~ " :: " ~ to!string(TextEdit.nativeMapping)); // not supposed to happen
3443 	}
3444 
3445 	extern(Windows)
3446 	private
3447 	// see for info https://jeffpar.github.io/kbarchive/kb/079/Q79982/
3448 	LRESULT HookedWndProcBSGROUPBOX_HACK(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
3449 		if(iMessage == WM_ERASEBKGND) {
3450 			auto dc = GetDC(hWnd);
3451 			auto b = SelectObject(dc, GetSysColorBrush(COLOR_3DFACE));
3452 			auto p = SelectObject(dc, GetStockObject(NULL_PEN));
3453 			RECT r;
3454 			GetWindowRect(hWnd, &r);
3455 			// since the pen is null, to fill the whole space, we need the +1 on both.
3456 			gdi.Rectangle(dc, 0, 0, r.right - r.left + 1, r.bottom - r.top + 1);
3457 			SelectObject(dc, p);
3458 			SelectObject(dc, b);
3459 			ReleaseDC(hWnd, dc);
3460 			InvalidateRect(hWnd, null, false); // redraw the border
3461 			return 1;
3462 		}
3463 		return HookedWndProc(hWnd, iMessage, wParam, lParam);
3464 	}
3465 
3466 	/++
3467 		Calls MS Windows' CreateWindowExW function to create a native backing for the given widget. It will create
3468 		needed mappings, window procedure hooks, and other private member variables needed to tie it into the rest
3469 		of minigui's expectations.
3470 
3471 		This should be called in your widget's constructor AFTER you call `super(parent);`. The parent window
3472 		member MUST already be initialized for this function to succeed, which is done by [Widget]'s base constructor.
3473 
3474 		It assumes `className` is zero-terminated. It should come from a `"wide string literal"w`.
3475 
3476 		To check if you can use this, use `static if(UsingWin32Widgets)`.
3477 	+/
3478 	void createWin32Window(Widget p, const(wchar)[] className, string windowText, DWORD style, DWORD extStyle = 0) {
3479 		assert(p.parentWindow !is null);
3480 		assert(p.parentWindow.win.impl.hwnd !is null);
3481 
3482 		auto bsgroupbox = style == BS_GROUPBOX;
3483 
3484 		HWND phwnd;
3485 
3486 		auto wtf = p.parent;
3487 		while(wtf) {
3488 			if(wtf.hwnd !is null) {
3489 				phwnd = wtf.hwnd;
3490 				break;
3491 			}
3492 			wtf = wtf.parent;
3493 		}
3494 
3495 		if(phwnd is null)
3496 			phwnd = p.parentWindow.win.impl.hwnd;
3497 
3498 		assert(phwnd !is null);
3499 
3500 		WCharzBuffer wt = WCharzBuffer(windowText);
3501 
3502 		style |= WS_VISIBLE | WS_CHILD;
3503 		//if(className != WC_TABCONTROL)
3504 			style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
3505 		p.hwnd = CreateWindowExW(extStyle, className.ptr, wt.ptr, style,
3506 				CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
3507 				phwnd, null, cast(HINSTANCE) GetModuleHandle(null), null);
3508 
3509 		assert(p.hwnd !is null);
3510 
3511 
3512 		static HFONT font;
3513 		if(font is null) {
3514 			NONCLIENTMETRICS params;
3515 			params.cbSize = params.sizeof;
3516 			if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
3517 				font = CreateFontIndirect(&params.lfMessageFont);
3518 			}
3519 		}
3520 
3521 		if(font)
3522 			SendMessage(p.hwnd, WM_SETFONT, cast(uint) font, true);
3523 
3524 		p.simpleWindowWrappingHwnd = new SimpleWindow(p.hwnd);
3525 		p.simpleWindowWrappingHwnd.beingOpenKeepsAppOpen = false;
3526 		Widget.nativeMapping[p.hwnd] = p;
3527 
3528 		if(bsgroupbox)
3529 		p.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(p.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProcBSGROUPBOX_HACK);
3530 		else
3531 		p.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(p.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc);
3532 
3533 		EnumChildWindows(p.hwnd, &childHandler, cast(LPARAM) cast(void*) p);
3534 
3535 		p.registerMovement();
3536 	}
3537 }
3538 
3539 version(win32_widgets)
3540 private
3541 extern(Windows) BOOL childHandler(HWND hwnd, LPARAM lparam) {
3542 	if(hwnd is null || hwnd in Widget.nativeMapping)
3543 		return true;
3544 	auto parent = cast(Widget) cast(void*) lparam;
3545 	Widget p = new Widget(null);
3546 	p._parent = parent;
3547 	p.parentWindow = parent.parentWindow;
3548 	p.hwnd = hwnd;
3549 	p.implicitlyCreated = true;
3550 	Widget.nativeMapping[p.hwnd] = p;
3551 	p.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(p.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc);
3552 	return true;
3553 }
3554 
3555 /++
3556 	Encapsulates the simpledisplay [ScreenPainter] for use on a [Widget], with [VisualTheme] and invalidated area awareness.
3557 +/
3558 struct WidgetPainter {
3559 	this(ScreenPainter screenPainter, Widget drawingUpon) {
3560 		this.drawingUpon = drawingUpon;
3561 		this.screenPainter = screenPainter;
3562 		if(auto font = visualTheme.defaultFontCached)
3563 			this.screenPainter.setFont(font);
3564 	}
3565 
3566 	/++
3567 		EXPERIMENTAL. subject to change.
3568 
3569 		When you draw a cursor, you can draw this to notify your window of where it is,
3570 		for IME systems to use.
3571 	+/
3572 	void notifyCursorPosition(int x, int y, int width, int height) {
3573 		if(auto a = drawingUpon.parentWindow)
3574 		if(auto w = a.inputProxy) {
3575 			w.setIMEPopupLocation(x + screenPainter.originX + width, y + screenPainter.originY + height);
3576 		}
3577 	}
3578 
3579 
3580 	///
3581 	ScreenPainter screenPainter;
3582 	/// Forward to the screen painter for other methods
3583 	alias screenPainter this;
3584 
3585 	private Widget drawingUpon;
3586 
3587 	/++
3588 		This is the list of rectangles that actually need to be redrawn.
3589 
3590 		Not actually implemented yet.
3591 	+/
3592 	Rectangle[] invalidatedRectangles;
3593 
3594 	private static BaseVisualTheme _visualTheme;
3595 
3596 	/++
3597 		Functions to access the visual theme and helpers to easily use it.
3598 
3599 		These are aware of the current widget's computed style out of the theme.
3600 	+/
3601 	static @property BaseVisualTheme visualTheme() {
3602 		if(_visualTheme is null)
3603 			_visualTheme = new DefaultVisualTheme();
3604 		return _visualTheme;
3605 	}
3606 
3607 	/// ditto
3608 	static @property void visualTheme(BaseVisualTheme theme) {
3609 		_visualTheme = theme;
3610 
3611 		// FIXME: notify all windows about the new theme
3612 	}
3613 
3614 	/// ditto
3615 	Color themeForeground() {
3616 		return drawingUpon.getComputedStyle().foregroundColor();
3617 	}
3618 
3619 	/// ditto
3620 	Color themeBackground() {
3621 		return drawingUpon.getComputedStyle().background.color;
3622 	}
3623 
3624 	int isDarkTheme() {
3625 		return 0; // unspecified, yes, no as enum. FIXME
3626 	}
3627 
3628 	/++
3629 		Draws the general pattern of a widget if you don't need anything particularly special and/or control the other details through your widget's style theme hints.
3630 
3631 		It gives your draw delegate a [Rectangle] representing the coordinates inside your border and padding.
3632 
3633 		If you change teh clip rectangle, you should change it back before you return.
3634 
3635 
3636 		The sequence it uses is:
3637 			background
3638 			content (delegated to you)
3639 			border
3640 			focused outline
3641 			selected overlay
3642 
3643 		Example code:
3644 
3645 		---
3646 		void paint(WidgetPainter painter) {
3647 			painter.drawThemed((bounds) {
3648 				return bounds; // if the selection overlay should be contained, you can return it here.
3649 			});
3650 		}
3651 		---
3652 	+/
3653 	void drawThemed(scope Rectangle delegate(const Rectangle bounds) drawBody) {
3654 		drawThemed((WidgetPainter painter, const Rectangle bounds) {
3655 			return drawBody(bounds);
3656 		});
3657 	}
3658 	// this overload is actually mroe for setting the delegate to a virtual function
3659 	void drawThemed(scope Rectangle delegate(WidgetPainter painter, const Rectangle bounds) drawBody) {
3660 		Rectangle rect = Rectangle(0, 0, drawingUpon.width, drawingUpon.height);
3661 
3662 		auto cs = drawingUpon.getComputedStyle();
3663 
3664 		auto bg = cs.background.color;
3665 
3666 		auto borderWidth = draw3dFrame(0, 0, drawingUpon.width, drawingUpon.height, this, cs.borderStyle, bg, cs.borderColor);
3667 
3668 		rect.left += borderWidth;
3669 		rect.right -= borderWidth;
3670 		rect.top += borderWidth;
3671 		rect.bottom -= borderWidth;
3672 
3673 		auto insideBorderRect = rect;
3674 
3675 		rect.left += cs.paddingLeft;
3676 		rect.right -= cs.paddingRight;
3677 		rect.top += cs.paddingTop;
3678 		rect.bottom -= cs.paddingBottom;
3679 
3680 		this.outlineColor = this.themeForeground;
3681 		this.fillColor = bg;
3682 
3683 		auto widgetFont = cs.fontCached;
3684 		if(widgetFont !is null)
3685 			this.setFont(widgetFont);
3686 
3687 		rect = drawBody(this, rect);
3688 
3689 		if(widgetFont !is null) {
3690 			if(auto vtFont = visualTheme.defaultFontCached)
3691 				this.setFont(vtFont);
3692 			else
3693 				this.setFont(null);
3694 		}
3695 
3696 		if(auto os = cs.outlineStyle()) {
3697 			this.pen = Pen(cs.outlineColor(), 1, os == FrameStyle.dotted ? Pen.Style.Dotted : Pen.Style.Solid);
3698 			this.fillColor = Color.transparent;
3699 			this.drawRectangle(insideBorderRect);
3700 		}
3701 	}
3702 
3703 	/++
3704 		First, draw the background.
3705 		Then draw your content.
3706 		Next, draw the border.
3707 		And the focused indicator.
3708 		And the is-selected box.
3709 
3710 		If it is focused i can draw the outline too...
3711 
3712 		If selected i can even do the xor action but that's at the end.
3713 	+/
3714 	void drawThemeBackground() {
3715 
3716 	}
3717 
3718 	void drawThemeBorder() {
3719 
3720 	}
3721 
3722 	// all this stuff is a dangerous experiment....
3723 	static class ScriptableVersion {
3724 		ScreenPainterImplementation* p;
3725 		int originX, originY;
3726 
3727 		@scriptable:
3728 		void drawRectangle(int x, int y, int width, int height) {
3729 			p.drawRectangle(x + originX, y + originY, width, height);
3730 		}
3731 		void drawLine(int x1, int y1, int x2, int y2) {
3732 			p.drawLine(x1 + originX, y1 + originY, x2 + originX, y2 + originY);
3733 		}
3734 		void drawText(int x, int y, string text) {
3735 			p.drawText(x + originX, y + originY, 100000, 100000, text, 0);
3736 		}
3737 		void setOutlineColor(int r, int g, int b) {
3738 			p.pen = Pen(Color(r,g,b), 1);
3739 		}
3740 		void setFillColor(int r, int g, int b) {
3741 			p.fillColor = Color(r,g,b);
3742 		}
3743 	}
3744 
3745 	ScriptableVersion toArsdJsvar() {
3746 		auto sv = new ScriptableVersion;
3747 		sv.p = this.screenPainter.impl;
3748 		sv.originX = this.screenPainter.originX;
3749 		sv.originY = this.screenPainter.originY;
3750 		return sv;
3751 	}
3752 
3753 	static WidgetPainter fromJsVar(T)(T t) {
3754 		return WidgetPainter.init;
3755 	}
3756 	// done..........
3757 }
3758 
3759 
3760 struct Style {
3761 	static struct helper(string m, T) {
3762 		enum method = m;
3763 		T v;
3764 
3765 		mixin template MethodOverride(typeof(this) v) {
3766 			mixin("override typeof(v.v) "~v.method~"() { return v.v; }");
3767 		}
3768 	}
3769 
3770 	static auto opDispatch(string method, T)(T value) {
3771 		return helper!(method, T)(value);
3772 	}
3773 }
3774 
3775 /++
3776 	Implementation detail of the [ControlledBy] UDA.
3777 
3778 	History:
3779 		Added Oct 28, 2020
3780 +/
3781 struct ControlledBy_(T, Args...) {
3782 	Args args;
3783 
3784 	static if(Args.length)
3785 	this(Args args) {
3786 		this.args = args;
3787 	}
3788 
3789 	private T construct(Widget parent) {
3790 		return new T(args, parent);
3791 	}
3792 }
3793 
3794 /++
3795 	User-defined attribute you can add to struct members contrlled by [addDataControllerWidget] or [dialog] to tell which widget you want created for them.
3796 
3797 	History:
3798 		Added Oct 28, 2020
3799 +/
3800 auto ControlledBy(T, Args...)(Args args) {
3801 	return ControlledBy_!(T, Args)(args);
3802 }
3803 
3804 struct ContainerMeta {
3805 	string name;
3806 	ContainerMeta[] children;
3807 	Widget function(Widget parent) factory;
3808 
3809 	Widget instantiate(Widget parent) {
3810 		auto n = factory(parent);
3811 		n.name = name;
3812 		foreach(child; children)
3813 			child.instantiate(n);
3814 		return n;
3815 	}
3816 }
3817 
3818 /++
3819 	This is a helper for [addDataControllerWidget]. You can use it as a UDA on the type. See
3820 	http://dpldocs.info/this-week-in-d/Blog.Posted_2020_11_02.html for more information.
3821 
3822 	Please note that as of May 28, 2021, a dmd bug prevents this from compiling on module-level
3823 	structures. It works fine on structs declared inside functions though.
3824 
3825 	See: https://issues.dlang.org/show_bug.cgi?id=21984
3826 +/
3827 template Container(CArgs...) {
3828 	static if(CArgs.length && is(CArgs[0] : Widget)) {
3829 		private alias Super = CArgs[0];
3830 		private alias CArgs2 = CArgs[1 .. $];
3831 	} else {
3832 		private alias Super = Layout;
3833 		private alias CArgs2 = CArgs;
3834 	}
3835 
3836 	class Container : Super {
3837 		this(Widget parent) { super(parent); }
3838 
3839 		// just to partially support old gdc versions
3840 		version(GNU) {
3841 			static if(CArgs2.length >= 1) { enum tmp0 = CArgs2[0]; mixin typeof(tmp0).MethodOverride!(CArgs2[0]); }
3842 			static if(CArgs2.length >= 2) { enum tmp1 = CArgs2[1]; mixin typeof(tmp1).MethodOverride!(CArgs2[1]); }
3843 			static if(CArgs2.length >= 3) { enum tmp2 = CArgs2[2]; mixin typeof(tmp2).MethodOverride!(CArgs2[2]); }
3844 			static if(CArgs2.length > 3) static assert(0, "only a few overrides like this supported on your compiler version at this time");
3845 		} else mixin(q{
3846 			static foreach(Arg; CArgs2) {
3847 				mixin Arg.MethodOverride!(Arg);
3848 			}
3849 		});
3850 
3851 		static ContainerMeta opCall(string name, ContainerMeta[] children...) {
3852 			return ContainerMeta(
3853 				name,
3854 				children.dup,
3855 				function (Widget parent) { return new typeof(this)(parent); }
3856 			);
3857 		}
3858 
3859 		static ContainerMeta opCall(ContainerMeta[] children...) {
3860 			return opCall(null, children);
3861 		}
3862 	}
3863 }
3864 
3865 /++
3866 	The data controller widget is created by reflecting over the given
3867 	data type. You can use [ControlledBy] as a UDA on a struct or
3868 	just let it create things automatically.
3869 
3870 	Unlike [dialog], this uses real-time updating of the data and
3871 	you add it to another window yourself.
3872 
3873 	---
3874 		struct Test {
3875 			int x;
3876 			int y;
3877 		}
3878 
3879 		auto window = new Window();
3880 		auto dcw = new DataControllerWidget!Test(new Test, window);
3881 	---
3882 
3883 	The way it works is any public members are given a widget based
3884 	on their data type, and public methods trigger an action button
3885 	if no relevant parameters or a dialog action if it does have
3886 	parameters, similar to the [menu] facility.
3887 
3888 	If you change data programmatically, without going through the
3889 	DataControllerWidget methods, you will have to tell it something
3890 	has changed and it needs to redraw. This is done with the `invalidate`
3891 	method.
3892 
3893 	History:
3894 		Added Oct 28, 2020
3895 +/
3896 /// Group: generating_from_code
3897 class DataControllerWidget(T) : WidgetContainer {
3898 	static if(is(T == class) || is(T == interface) || is(T : const E[], E))
3899 		private alias Tref = T;
3900 	else
3901 		private alias Tref = T*;
3902 
3903 	Tref datum;
3904 
3905 	/++
3906 		See_also: [addDataControllerWidget]
3907 	+/
3908 	this(Tref datum, Widget parent) {
3909 		this.datum = datum;
3910 
3911 		Widget cp = this;
3912 
3913 		super(parent);
3914 
3915 		foreach(attr; __traits(getAttributes, T))
3916 			static if(is(typeof(attr) == ContainerMeta)) {
3917 				cp = attr.instantiate(this);
3918 			}
3919 
3920 		auto def = this.getByName("default");
3921 		if(def !is null)
3922 			cp = def;
3923 
3924 		Widget helper(string name) {
3925 			auto maybe = this.getByName(name);
3926 			if(maybe is null)
3927 				return cp;
3928 			return maybe;
3929 
3930 		}
3931 
3932 		foreach(member; __traits(allMembers, T))
3933 		static if(member != "this") // wtf https://issues.dlang.org/show_bug.cgi?id=22011
3934 		static if(is(typeof(__traits(getMember, this.datum, member))))
3935 		static if(__traits(getProtection, __traits(getMember, this.datum, member)) == "public") {
3936 			void delegate() update;
3937 
3938 			auto w = widgetFor!(__traits(getMember, T, member))(&__traits(getMember, this.datum, member), helper(member), update);
3939 
3940 			if(update)
3941 				updaters ~= update;
3942 
3943 			static if(is(typeof(__traits(getMember, this.datum, member)) == function)) {
3944 				w.addEventListener("triggered", delegate() {
3945 					makeAutomaticHandler!(__traits(getMember, this.datum, member))(&__traits(getMember, this.datum, member))();
3946 					notifyDataUpdated();
3947 				});
3948 			} else static if(is(typeof(w.isChecked) == bool)) {
3949 				w.addEventListener(EventType.change, (Event ev) {
3950 					__traits(getMember, this.datum, member) = w.isChecked;
3951 				});
3952 			} else static if(is(typeof(w.value) == string) || is(typeof(w.content) == string)) {
3953 				w.addEventListener("change", (Event e) { genericSetValue(&__traits(getMember, this.datum, member), e.stringValue); } );
3954 			} else static if(is(typeof(w.value) == int)) {
3955 				w.addEventListener("change", (Event e) { genericSetValue(&__traits(getMember, this.datum, member), e.intValue); } );
3956 			} else static if(is(typeof(w) == DropDownSelection)) {
3957 				// special case for this to kinda support enums and such. coudl be better though
3958 				w.addEventListener("change", (Event e) { genericSetValue(&__traits(getMember, this.datum, member), e.intValue); } );
3959 			} else {
3960 				//static assert(0, "unsupported type " ~ typeof(__traits(getMember, this.datum, member)).stringof ~ " " ~ typeof(w).stringof);
3961 			}
3962 		}
3963 	}
3964 
3965 	/++
3966 		If you modify the data in the structure directly, you need to call this to update the UI and propagate any change messages.
3967 
3968 		History:
3969 			Added May 28, 2021
3970 	+/
3971 	void notifyDataUpdated() {
3972 		foreach(updater; updaters)
3973 			updater();
3974 
3975 		this.emit!(ChangeEvent!void)(delegate{});
3976 	}
3977 
3978 	private Widget[string] memberWidgets;
3979 	private void delegate()[] updaters;
3980 
3981 	mixin Emits!(ChangeEvent!void);
3982 }
3983 
3984 private int saturatedSum(int[] values...) {
3985 	int sum;
3986 	foreach(value; values) {
3987 		if(value == int.max)
3988 			return int.max;
3989 		sum += value;
3990 	}
3991 	return sum;
3992 }
3993 
3994 void genericSetValue(T, W)(T* where, W what) {
3995 	import std.conv;
3996 	*where = to!T(what);
3997 	//*where = cast(T) stringToLong(what);
3998 }
3999 
4000 /++
4001 	Creates a widget for the value `tt`, which is pointed to at runtime by `valptr`, with the given parent.
4002 
4003 	The `update` delegate can be called if you change `*valptr` to reflect those changes in the widget.
4004 
4005 	Note that this creates the widget but does not attach any event handlers to it.
4006 +/
4007 private static auto widgetFor(alias tt, P)(P valptr, Widget parent, out void delegate() update) {
4008 
4009 	string displayName = __traits(identifier, tt).beautify;
4010 
4011 	static if(controlledByCount!tt == 1) {
4012 		foreach(i, attr; __traits(getAttributes, tt)) {
4013 			static if(is(typeof(attr) == ControlledBy_!(T, Args), T, Args...)) {
4014 				auto w = attr.construct(parent);
4015 				static if(__traits(compiles, w.setPosition(*valptr)))
4016 					update = () { w.setPosition(*valptr); };
4017 				else static if(__traits(compiles, w.setValue(*valptr)))
4018 					update = () { w.setValue(*valptr); };
4019 
4020 				if(update)
4021 					update();
4022 				return w;
4023 			}
4024 		}
4025 	} else static if(controlledByCount!tt == 0) {
4026 		static if(is(typeof(tt) == enum)) {
4027 			// FIXME: update
4028 			auto dds = new DropDownSelection(parent);
4029 			foreach(idx, option; __traits(allMembers, typeof(tt))) {
4030 				dds.addOption(option);
4031 				if(__traits(getMember, typeof(tt), option) == *valptr)
4032 					dds.setSelection(cast(int) idx);
4033 			}
4034 			return dds;
4035 		} else static if(is(typeof(tt) == bool)) {
4036 			auto box = new Checkbox(displayName, parent);
4037 			update = () { box.isChecked = *valptr; };
4038 			update();
4039 			return box;
4040 		} else static if(is(typeof(tt) : const long)) {
4041 			auto le = new LabeledLineEdit(displayName, parent);
4042 			update = () { le.content = toInternal!string(*valptr); };
4043 			update();
4044 			return le;
4045 		} else static if(is(typeof(tt) : const double)) {
4046 			auto le = new LabeledLineEdit(displayName, parent);
4047 			import std.conv;
4048 			update = () { le.content = to!string(*valptr); };
4049 			update();
4050 			return le;
4051 		} else static if(is(typeof(tt) : const string)) {
4052 			auto le = new LabeledLineEdit(displayName, parent);
4053 			update = () { le.content = *valptr; };
4054 			update();
4055 			return le;
4056 		} else static if(is(typeof(tt) == function)) {
4057 			auto w = new Button(displayName, parent);
4058 			return w;
4059 		} else static if(is(typeof(tt) == class) || is(typeof(tt) == interface)) {
4060 			return parent.addDataControllerWidget(tt);
4061 		} else static assert(0, typeof(tt).stringof);
4062 	} else static assert(0, "multiple controllers not yet supported");
4063 }
4064 
4065 private template controlledByCount(alias tt) {
4066 	static int helper() {
4067 		int count;
4068 		foreach(i, attr; __traits(getAttributes, tt))
4069 			static if(is(typeof(attr) == ControlledBy_!(T, Args), T, Args...))
4070 				count++;
4071 		return count;
4072 	}
4073 
4074 	enum controlledByCount = helper;
4075 }
4076 
4077 /++
4078 	Intended for UFCS action like `window.addDataControllerWidget(new MyObject());`
4079 
4080 	If you provide a `redrawOnChange` widget, it will automatically register a change event handler that calls that widget's redraw method.
4081 
4082 	History:
4083 		The `redrawOnChange` parameter was added on May 28, 2021.
4084 +/
4085 DataControllerWidget!T addDataControllerWidget(T)(Widget parent, T t, Widget redrawOnChange = null) if(is(T == class) || is(T == interface)) {
4086 	auto dcw = new DataControllerWidget!T(t, parent);
4087 	initializeDataControllerWidget(dcw, redrawOnChange);
4088 	return dcw;
4089 }
4090 
4091 /// ditto
4092 DataControllerWidget!T addDataControllerWidget(T)(Widget parent, T* t, Widget redrawOnChange = null) if(is(T == struct)) {
4093 	auto dcw = new DataControllerWidget!T(t, parent);
4094 	initializeDataControllerWidget(dcw, redrawOnChange);
4095 	return dcw;
4096 }
4097 
4098 private void initializeDataControllerWidget(Widget w, Widget redrawOnChange) {
4099 	if(redrawOnChange !is null)
4100 		w.addEventListener("change", delegate() { redrawOnChange.redraw(); });
4101 }
4102 
4103 /++
4104 	Get this through [Widget.getComputedStyle]. It provides access to the [Widget.Style] style hints and [Widget] layout hints, possibly modified through the [VisualTheme], through a unifed interface.
4105 
4106 	History:
4107 		Finalized on June 3, 2021 for the dub v10.0 release
4108 +/
4109 struct StyleInformation {
4110 	private Widget w;
4111 	private BaseVisualTheme visualTheme;
4112 
4113 	private this(Widget w) {
4114 		this.w = w;
4115 		this.visualTheme = WidgetPainter.visualTheme;
4116 	}
4117 
4118 	/++
4119 		Forwards to [Widget.Style]
4120 
4121 		Bugs:
4122 			It is supposed to fall back to the [VisualTheme] if
4123 			the style doesn't override the default, but that is
4124 			not generally implemented. Many of them may end up
4125 			being explicit overloads instead of the generic
4126 			opDispatch fallback, like [font] is now.
4127 	+/
4128 	public @property opDispatch(string name)() {
4129 		typeof(__traits(getMember, Widget.Style.init, name)()) prop;
4130 		w.useStyleProperties((scope Widget.Style props) {
4131 		//visualTheme.useStyleProperties(w, (props) {
4132 			prop = __traits(getMember, props, name);
4133 		});
4134 		return prop;
4135 	}
4136 
4137 	/++
4138 		Returns the cached font object associated with the widget,
4139 		if overridden by the [Widget.Style|Style], or the [VisualTheme] if not.
4140 
4141 		History:
4142 			Prior to March 21, 2022 (dub v10.7), `font` went through
4143 			[opDispatch], which did not use the cache. You can now call it
4144 			repeatedly without guilt.
4145 	+/
4146 	public @property OperatingSystemFont font() {
4147 		OperatingSystemFont prop;
4148 		w.useStyleProperties((scope Widget.Style props) {
4149 			prop = props.fontCached;
4150 		});
4151 		if(prop is null)
4152 			prop = visualTheme.defaultFontCached;
4153 		return prop;
4154 	}
4155 
4156 	@property {
4157 		// Layout helpers. Currently just forwarding since I haven't made up my mind on a better way.
4158 		/** */ int paddingLeft() { return w.paddingLeft(); }
4159 		/** */ int paddingRight() { return w.paddingRight(); }
4160 		/** */ int paddingTop() { return w.paddingTop(); }
4161 		/** */ int paddingBottom() { return w.paddingBottom(); }
4162 
4163 		/** */ int marginLeft() { return w.marginLeft(); }
4164 		/** */ int marginRight() { return w.marginRight(); }
4165 		/** */ int marginTop() { return w.marginTop(); }
4166 		/** */ int marginBottom() { return w.marginBottom(); }
4167 
4168 		/** */ int maxHeight() { return w.maxHeight(); }
4169 		/** */ int minHeight() { return w.minHeight(); }
4170 
4171 		/** */ int maxWidth() { return w.maxWidth(); }
4172 		/** */ int minWidth() { return w.minWidth(); }
4173 
4174 		/** */ int flexBasisWidth() { return w.flexBasisWidth(); }
4175 		/** */ int flexBasisHeight() { return w.flexBasisHeight(); }
4176 
4177 		/** */ int heightStretchiness() { return w.heightStretchiness(); }
4178 		/** */ int widthStretchiness() { return w.widthStretchiness(); }
4179 
4180 		/** */ int heightShrinkiness() { return w.heightShrinkiness(); }
4181 		/** */ int widthShrinkiness() { return w.widthShrinkiness(); }
4182 
4183 		// Global helpers some of these are unstable.
4184 		static:
4185 		/** */ Color windowBackgroundColor() { return WidgetPainter.visualTheme.windowBackgroundColor(); }
4186 		/** */ Color widgetBackgroundColor() { return WidgetPainter.visualTheme.widgetBackgroundColor(); }
4187 		/** */ Color lightAccentColor() { return WidgetPainter.visualTheme.lightAccentColor(); }
4188 		/** */ Color darkAccentColor() { return WidgetPainter.visualTheme.darkAccentColor(); }
4189 		/** */ Color selectionForegroundColor() { return WidgetPainter.visualTheme.selectionForegroundColor(); }
4190 		/** */ Color selectionBackgroundColor() { return WidgetPainter.visualTheme.selectionBackgroundColor(); }
4191 
4192 		/** */ Color activeTabColor() { return lightAccentColor; }
4193 		/** */ Color buttonColor() { return windowBackgroundColor; }
4194 		/** */ Color depressedButtonColor() { return darkAccentColor; }
4195 		/** */ Color hoveringColor() { return lightAccentColor; }
4196 		deprecated("Use selectionForegroundColor and selectionBackgroundColor instead") Color activeListXorColor() {
4197 			auto c = WidgetPainter.visualTheme.selectionColor();
4198 			return Color(c.r ^ 255, c.g ^ 255, c.b ^ 255, c.a);
4199 		}
4200 		/** */ Color progressBarColor() { return WidgetPainter.visualTheme.selectionBackgroundColor(); }
4201 		/** */ Color activeMenuItemColor() { return WidgetPainter.visualTheme.selectionBackgroundColor(); }
4202 	}
4203 
4204 
4205 
4206 	/+
4207 
4208 	private static auto extractStyleProperty(string name)(Widget w) {
4209 		typeof(__traits(getMember, Widget.Style.init, name)()) prop;
4210 		w.useStyleProperties((props) {
4211 			prop = __traits(getMember, props, name);
4212 		});
4213 		return prop;
4214 	}
4215 
4216 	// FIXME: clear this upon a X server disconnect
4217 	private static OperatingSystemFont[string] fontCache;
4218 
4219 	T getProperty(T)(string name, lazy T default_) {
4220 		if(visualTheme !is null) {
4221 			auto str = visualTheme.getPropertyString(w, name);
4222 			if(str is null)
4223 				return default_;
4224 			static if(is(T == Color))
4225 				return Color.fromString(str);
4226 			else static if(is(T == Measurement))
4227 				return Measurement(cast(int) toInternal!int(str));
4228 			else static if(is(T == WidgetBackground))
4229 				return WidgetBackground.fromString(str);
4230 			else static if(is(T == OperatingSystemFont)) {
4231 				if(auto f = str in fontCache)
4232 					return *f;
4233 				else
4234 					return fontCache[str] = new OperatingSystemFont(str);
4235 			} else static if(is(T == FrameStyle)) {
4236 				switch(str) {
4237 					default:
4238 						return FrameStyle.none;
4239 					foreach(style; __traits(allMembers, FrameStyle))
4240 					case style:
4241 						return __traits(getMember, FrameStyle, style);
4242 				}
4243 			} else static assert(0);
4244 		} else
4245 			return default_;
4246 	}
4247 
4248 	static struct Measurement {
4249 		int value;
4250 		alias value this;
4251 	}
4252 
4253 	@property:
4254 
4255 	int paddingLeft() { return getProperty("padding-left", Measurement(w.paddingLeft())); }
4256 	int paddingRight() { return getProperty("padding-right", Measurement(w.paddingRight())); }
4257 	int paddingTop() { return getProperty("padding-top", Measurement(w.paddingTop())); }
4258 	int paddingBottom() { return getProperty("padding-bottom", Measurement(w.paddingBottom())); }
4259 
4260 	int marginLeft() { return getProperty("margin-left", Measurement(w.marginLeft())); }
4261 	int marginRight() { return getProperty("margin-right", Measurement(w.marginRight())); }
4262 	int marginTop() { return getProperty("margin-top", Measurement(w.marginTop())); }
4263 	int marginBottom() { return getProperty("margin-bottom", Measurement(w.marginBottom())); }
4264 
4265 	int maxHeight() { return getProperty("max-height", Measurement(w.maxHeight())); }
4266 	int minHeight() { return getProperty("min-height", Measurement(w.minHeight())); }
4267 
4268 	int maxWidth() { return getProperty("max-width", Measurement(w.maxWidth())); }
4269 	int minWidth() { return getProperty("min-width", Measurement(w.minWidth())); }
4270 
4271 
4272 	WidgetBackground background() { return getProperty("background", extractStyleProperty!"background"(w)); }
4273 	Color foregroundColor() { return getProperty("foreground-color", extractStyleProperty!"foregroundColor"(w)); }
4274 
4275 	OperatingSystemFont font() { return getProperty("font", extractStyleProperty!"fontCached"(w)); }
4276 
4277 	FrameStyle borderStyle() { return getProperty("border-style", extractStyleProperty!"borderStyle"(w)); }
4278 	Color borderColor() { return getProperty("border-color", extractStyleProperty!"borderColor"(w)); }
4279 
4280 	FrameStyle outlineStyle() { return getProperty("outline-style", extractStyleProperty!"outlineStyle"(w)); }
4281 	Color outlineColor() { return getProperty("outline-color", extractStyleProperty!"outlineColor"(w)); }
4282 
4283 
4284 	Color windowBackgroundColor() { return WidgetPainter.visualTheme.windowBackgroundColor(); }
4285 	Color widgetBackgroundColor() { return WidgetPainter.visualTheme.widgetBackgroundColor(); }
4286 	Color lightAccentColor() { return WidgetPainter.visualTheme.lightAccentColor(); }
4287 	Color darkAccentColor() { return WidgetPainter.visualTheme.darkAccentColor(); }
4288 
4289 	Color activeTabColor() { return lightAccentColor; }
4290 	Color buttonColor() { return windowBackgroundColor; }
4291 	Color depressedButtonColor() { return darkAccentColor; }
4292 	Color hoveringColor() { return Color(228, 228, 228); }
4293 	Color activeListXorColor() {
4294 		auto c = WidgetPainter.visualTheme.selectionColor();
4295 		return Color(c.r ^ 255, c.g ^ 255, c.b ^ 255, c.a);
4296 	}
4297 	Color progressBarColor() { return WidgetPainter.visualTheme.selectionColor(); }
4298 	Color activeMenuItemColor() { return WidgetPainter.visualTheme.selectionColor(); }
4299 	+/
4300 }
4301 
4302 
4303 
4304 // pragma(msg, __traits(classInstanceSize, Widget));
4305 
4306 /*private*/ template EventString(E) {
4307 	static if(is(typeof(E.EventString)))
4308 		enum EventString = E.EventString;
4309 	else
4310 		enum EventString = E.mangleof; // FIXME fqn? or something more user friendly
4311 }
4312 
4313 /*private*/ template EventStringIdentifier(E) {
4314 	string helper() {
4315 		auto es = EventString!E;
4316 		char[] id = new char[](es.length * 2);
4317 		size_t idx;
4318 		foreach(char ch; es) {
4319 			id[idx++] = cast(char)('a' + (ch >> 4));
4320 			id[idx++] = cast(char)('a' + (ch & 0x0f));
4321 		}
4322 		return cast(string) id;
4323 	}
4324 
4325 	enum EventStringIdentifier = helper();
4326 }
4327 
4328 
4329 template classStaticallyEmits(This, EventType) {
4330 	static if(is(This Base == super))
4331 		static if(is(Base : Widget))
4332 			enum baseEmits = classStaticallyEmits!(Base, EventType);
4333 		else
4334 			enum baseEmits = false;
4335 	else
4336 		enum baseEmits = false;
4337 
4338 	enum thisEmits = is(typeof(__traits(getMember, This, "emits_" ~ EventStringIdentifier!EventType)) == EventType[0]);
4339 
4340 	enum classStaticallyEmits = thisEmits || baseEmits;
4341 }
4342 
4343 /++
4344 	A helper to make widgets out of other native windows.
4345 
4346 	History:
4347 		Factored out of OpenGlWidget on November 5, 2021
4348 +/
4349 class NestedChildWindowWidget : Widget {
4350 	SimpleWindow win;
4351 
4352 	/++
4353 		Used on X to send focus to the appropriate child window when requested by the window manager.
4354 
4355 		Normally returns its own nested window. Can also return another child or null to revert to the parent
4356 		if you override it in a child class.
4357 
4358 		History:
4359 			Added April 2, 2022 (dub v10.8)
4360 	+/
4361 	SimpleWindow focusableWindow() {
4362 		return win;
4363 	}
4364 
4365 	///
4366 	// win = new SimpleWindow(640, 480, null, OpenGlOptions.yes, Resizability.automaticallyScaleIfPossible, WindowTypes.nestedChild, WindowFlags.normal, getParentWindow(parent));
4367 	this(SimpleWindow win, Widget parent) {
4368 		this.parentWindow = parent.parentWindow;
4369 		this.win = win;
4370 
4371 		super(parent);
4372 		windowsetup(win);
4373 	}
4374 
4375 	static protected SimpleWindow getParentWindow(Widget parent) {
4376 		assert(parent !is null);
4377 		SimpleWindow pwin = parent.parentWindow.win;
4378 
4379 		version(win32_widgets) {
4380 			HWND phwnd;
4381 			auto wtf = parent;
4382 			while(wtf) {
4383 				if(wtf.hwnd) {
4384 					phwnd = wtf.hwnd;
4385 					break;
4386 				}
4387 				wtf = wtf.parent;
4388 			}
4389 			// kinda a hack here just because the ctor below just needs a SimpleWindow wrapper....
4390 			if(phwnd)
4391 				pwin = new SimpleWindow(phwnd);
4392 		}
4393 
4394 		return pwin;
4395 	}
4396 
4397 	/++
4398 		Called upon the nested window being destroyed.
4399 		Remember the window has already been destroyed at
4400 		this point, so don't use the native handle for anything.
4401 
4402 		History:
4403 			Added April 3, 2022 (dub v10.8)
4404 	+/
4405 	protected void dispose() {
4406 
4407 	}
4408 
4409 	protected void windowsetup(SimpleWindow w) {
4410 		/*
4411 		win.onFocusChange = (bool getting) {
4412 			if(getting)
4413 				this.focus();
4414 		};
4415 		*/
4416 
4417 		/+
4418 		win.onFocusChange = (bool getting) {
4419 			if(getting) {
4420 				this.parentWindow.focusedWidget = this;
4421 				this.emit!FocusEvent();
4422 				this.emit!FocusInEvent();
4423 			} else {
4424 				this.emit!BlurEvent();
4425 				this.emit!FocusOutEvent();
4426 			}
4427 		};
4428 		+/
4429 
4430 		win.onDestroyed = () {
4431 			this.dispose();
4432 		};
4433 
4434 		version(win32_widgets) {
4435 			Widget.nativeMapping[win.hwnd] = this;
4436 			this.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(win.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc);
4437 		} else {
4438 			win.setEventHandlers(
4439 				(MouseEvent e) {
4440 					Widget p = this;
4441 					while(p ! is parentWindow) {
4442 						e.x += p.x;
4443 						e.y += p.y;
4444 						p = p.parent;
4445 					}
4446 					parentWindow.dispatchMouseEvent(e);
4447 				},
4448 				(KeyEvent e) {
4449 					//import std.stdio; writefln("%s %x   %s", cast(void*) win, cast(uint) e.key, e.key);
4450 					parentWindow.dispatchKeyEvent(e);
4451 				},
4452 				(dchar e) {
4453 					parentWindow.dispatchCharEvent(e);
4454 				},
4455 			);
4456 		}
4457 
4458 	}
4459 
4460 	override void showing(bool s, bool recalc) {
4461 		auto cur = hidden;
4462 		win.hidden = !s;
4463 		if(cur != s && s)
4464 			redraw();
4465 	}
4466 
4467 	/// OpenGL widgets cannot have child widgets. Do not call this.
4468 	/* @disable */ final override void addChild(Widget, int) {
4469 		throw new Error("cannot add children to OpenGL widgets");
4470 	}
4471 
4472 	/// When an opengl widget is laid out, it will adjust the glViewport for you automatically.
4473 	/// Keep in mind that events like mouse coordinates are still relative to your size.
4474 	override void registerMovement() {
4475 		//import std.stdio; writefln("%d %d %d %d", x,y,width,height);
4476 		version(win32_widgets)
4477 			auto pos = getChildPositionRelativeToParentHwnd(this);
4478 		else
4479 			auto pos = getChildPositionRelativeToParentOrigin(this);
4480 		win.moveResize(pos[0], pos[1], width, height);
4481 
4482 		registerMovementAdditionalWork();
4483 		sendResizeEvent();
4484 	}
4485 
4486 	abstract void registerMovementAdditionalWork();
4487 }
4488 
4489 /++
4490 	Nests an opengl capable window inside this window as a widget.
4491 
4492 	You may also just want to create an additional [SimpleWindow] with
4493 	[OpenGlOptions.yes] yourself.
4494 
4495 	An OpenGL widget cannot have child widgets. It will throw if you try.
4496 +/
4497 static if(OpenGlEnabled)
4498 class OpenGlWidget : NestedChildWindowWidget {
4499 
4500 	override void registerMovementAdditionalWork() {
4501 		win.setAsCurrentOpenGlContext();
4502 	}
4503 
4504 	///
4505 	this(Widget parent) {
4506 		auto win = new SimpleWindow(640, 480, null, OpenGlOptions.yes, Resizability.automaticallyScaleIfPossible, WindowTypes.nestedChild, WindowFlags.normal, getParentWindow(parent));
4507 		super(win, parent);
4508 	}
4509 
4510 	override void paint(WidgetPainter painter) {
4511 		win.setAsCurrentOpenGlContext();
4512 		glViewport(0, 0, this.width, this.height);
4513 		win.redrawOpenGlSceneNow();
4514 	}
4515 
4516 	void redrawOpenGlScene(void delegate() dg) {
4517 		win.redrawOpenGlScene = dg;
4518 	}
4519 }
4520 
4521 /++
4522 	This demo shows how to draw text in an opengl scene.
4523 +/
4524 unittest {
4525 	import arsd.minigui;
4526 	import arsd.ttf;
4527 
4528 	void main() {
4529 		auto window = new Window();
4530 
4531 		auto widget = new OpenGlWidget(window);
4532 
4533 		// old means non-shader code so compatible with glBegin etc.
4534 		// tbh I haven't implemented new one in font yet...
4535 		// anyway, declaring here, will construct soon.
4536 		OpenGlLimitedFont!(OpenGlFontGLVersion.old) glfont;
4537 
4538 		// this is a little bit awkward, calling some methods through
4539 		// the underlying SimpleWindow `win` method, and you can't do this
4540 		// on a nanovega widget due to conflicts so I should probably fix
4541 		// the api to be a bit easier. But here it will work.
4542 		//
4543 		// Alternatively, you could load the font on the first draw, inside
4544 		// the redrawOpenGlScene, and keep a flag so you don't do it every
4545 		// time. That'd be a bit easier since the lib sets up the context
4546 		// by then guaranteed.
4547 		//
4548 		// But still, I wanna show this.
4549 		widget.win.visibleForTheFirstTime = delegate {
4550 			// must set the opengl context
4551 			widget.win.setAsCurrentOpenGlContext();
4552 
4553 			// if you were doing a OpenGL 3+ shader, this
4554 			// gets especially important to do in order. With
4555 			// old-style opengl, I think you can even do it
4556 			// in main(), but meh, let's show it more correctly.
4557 
4558 			// Anyway, now it is time to load the font from the
4559 			// OS (you can alternatively load one from a .ttf file
4560 			// you bundle with the application), then load the
4561 			// font into texture for drawing.
4562 
4563 			auto osfont = new OperatingSystemFont("DejaVu Sans", 18);
4564 
4565 			assert(!osfont.isNull()); // make sure it actually loaded
4566 
4567 			// using typeof to avoid repeating the long name lol
4568 			glfont = new typeof(glfont)(
4569 				// get the raw data from the font for loading in here
4570 				// since it doesn't use the OS function to draw the
4571 				// text, we gotta treat it more as a file than as
4572 				// a drawing api.
4573 				osfont.getTtfBytes(),
4574 				18, // need to respecify size since opengl world is different coordinate system
4575 
4576 				// these last two numbers are why it is called
4577 				// "Limited" font. It only loads the characters
4578 				// in the given range, since the texture atlas
4579 				// it references is all a big image generated ahead
4580 				// of time. You could maybe do the whole thing but
4581 				// idk how much memory that is.
4582 				//
4583 				// But here, 0-128 represents the ASCII range, so
4584 				// good enough for most English things, numeric labels,
4585 				// etc.
4586 				0,
4587 				128
4588 			);
4589 		};
4590 
4591 		widget.redrawOpenGlScene = () {
4592 			// now we can use the glfont's drawString function
4593 
4594 			// first some opengl setup. You can do this in one place
4595 			// on window first visible too in many cases, just showing
4596 			// here cuz it is easier for me.
4597 
4598 			// gonna need some alpha blending or it just looks awful
4599 			glEnable(GL_BLEND);
4600 			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
4601 			glClearColor(0,0,0,0);
4602 			glDepthFunc(GL_LEQUAL);
4603 
4604 			// Also need to enable 2d textures, since it draws the
4605 			// font characters as images baked in
4606 			glMatrixMode(GL_MODELVIEW);
4607 			glLoadIdentity();
4608 			glDisable(GL_DEPTH_TEST);
4609 			glEnable(GL_TEXTURE_2D);
4610 
4611 			// the orthographic matrix is best for 2d things like text
4612 			// so let's set that up. This matrix makes the coordinates
4613 			// in the opengl scene be one-to-one with the actual pixels
4614 			// on screen. (Not necessarily best, you may wish to scale
4615 			// things, but it does help keep fonts looking normal.)
4616 			glMatrixMode(GL_PROJECTION);
4617 			glLoadIdentity();
4618 			glOrtho(0, widget.width, widget.height, 0, 0, 1);
4619 
4620 			// you can do other glScale, glRotate, glTranslate, etc
4621 			// to the matrix here of course if you want.
4622 
4623 			// note the x,y coordinates here are for the text baseline
4624 			// NOT the upper-left corner. The baseline is like the line
4625 			// in the notebook you write on. Most the letters are actually
4626 			// above it, but some, like p and q, dip a bit below it.
4627 			//
4628 			// So if you're used to the upper left coordinate like the
4629 			// rest of simpledisplay/minigui usually do, do the
4630 			// y + glfont.ascent to bring it down a little. So this
4631 			// example puts the string in the upper left of the window.
4632 			glfont.drawString(0, 0 + glfont.ascent, "Hello!!", Color.green);
4633 
4634 			// re color btw: the function sets a solid color internally,
4635 			// but you actually COULD do your own thing for rainbow effects
4636 			// and the sort if you wanted too, by pulling its guts out.
4637 			// Just view its source for an idea of how it actually draws:
4638 			// http://arsd-official.dpldocs.info/source/arsd.ttf.d.html#L332
4639 
4640 			// it gets a bit complicated with the character positioning,
4641 			// but the opengl parts are fairly simple: bind a texture,
4642 			// set the color, draw a quad for each letter.
4643 
4644 
4645 			// the last optional argument there btw is a bounding box
4646 			// it will/ use to word wrap and return an object you can
4647 			// use to implement scrolling or pagination; it tells how
4648 			// much of the string didn't fit in the box. But for simple
4649 			// labels we can just ignore that.
4650 
4651 
4652 			// I'd suggest drawing text as the last step, after you
4653 			// do your other drawing. You might use the push/pop matrix
4654 			// stuff to keep your place. You, in theory, should be able
4655 			// to do text in a 3d space but I've never actually tried
4656 			// that....
4657 		};
4658 
4659 		window.loop();
4660 	}
4661 }
4662 
4663 version(custom_widgets)
4664 	private alias ListWidgetBase = ScrollableWidget;
4665 else
4666 	private alias ListWidgetBase = Widget;
4667 
4668 /++
4669 	A list widget contains a list of strings that the user can examine and select.
4670 
4671 
4672 	In the future, items in the list may be possible to be more than just strings.
4673 
4674 	See_Also:
4675 		[TableView]
4676 +/
4677 class ListWidget : ListWidgetBase {
4678 	/// Sends a change event when the selection changes, but the data is not attached to the event. You must instead loop the options to see if they are selected.
4679 	mixin Emits!(ChangeEvent!void);
4680 
4681 	static struct Option {
4682 		string label;
4683 		bool selected;
4684 		void* tag;
4685 	}
4686 
4687 	/++
4688 		Sets the current selection to the `y`th item in the list. Will emit [ChangeEvent] when complete.
4689 	+/
4690 	void setSelection(int y) {
4691 		if(!multiSelect)
4692 			foreach(ref opt; options)
4693 				opt.selected = false;
4694 		if(y >= 0 && y < options.length)
4695 			options[y].selected = !options[y].selected;
4696 
4697 		this.emit!(ChangeEvent!void)(delegate {});
4698 
4699 		version(custom_widgets)
4700 			redraw();
4701 	}
4702 
4703 	/++
4704 		Gets the index of the selected item. In case of multi select, the index of the first selected item is returned.
4705 		Returns -1 if nothing is selected.
4706 	+/
4707 	int getSelection()
4708 	{
4709 		foreach(i, opt; options) {
4710 			if (opt.selected)
4711 				return cast(int) i;
4712 		}
4713 		return -1;
4714 	}
4715 
4716 	version(custom_widgets)
4717 	override void defaultEventHandler_click(ClickEvent event) {
4718 		this.focus();
4719 		if(event.button == MouseButton.left) {
4720 			auto y = (event.clientY - 4) / defaultLineHeight;
4721 			if(y >= 0 && y < options.length) {
4722 				setSelection(y);
4723 			}
4724 		}
4725 		super.defaultEventHandler_click(event);
4726 	}
4727 
4728 	this(Widget parent) {
4729 		tabStop = false;
4730 		super(parent);
4731 		version(win32_widgets)
4732 			createWin32Window(this, WC_LISTBOX, "",
4733 				0|WS_CHILD|WS_VISIBLE|LBS_NOTIFY, 0);
4734 	}
4735 
4736 	version(win32_widgets)
4737 	override void handleWmCommand(ushort code, ushort id) {
4738 		switch(code) {
4739 			case LBN_SELCHANGE:
4740 				auto sel = SendMessageW(hwnd, LB_GETCURSEL, 0, 0);
4741 				setSelection(cast(int) sel);
4742 			break;
4743 			default:
4744 		}
4745 	}
4746 
4747 
4748 	version(custom_widgets)
4749 	override void paintFrameAndBackground(WidgetPainter painter) {
4750 		draw3dFrame(this, painter, FrameStyle.sunk, painter.visualTheme.widgetBackgroundColor);
4751 	}
4752 
4753 	version(custom_widgets)
4754 	override void paint(WidgetPainter painter) {
4755 		auto cs = getComputedStyle();
4756 		auto pos = Point(4, 4);
4757 		foreach(idx, option; options) {
4758 			painter.fillColor = painter.visualTheme.widgetBackgroundColor;
4759 			painter.outlineColor = painter.visualTheme.widgetBackgroundColor;
4760 			painter.drawRectangle(pos, width - 8, defaultLineHeight);
4761 			if(option.selected) {
4762 				//painter.rasterOp = RasterOp.xor;
4763 				painter.outlineColor = cs.selectionForegroundColor;
4764 				painter.fillColor = cs.selectionBackgroundColor;
4765 				painter.drawRectangle(pos, width - 8, defaultLineHeight);
4766 				//painter.rasterOp = RasterOp.normal;
4767 			}
4768 			painter.outlineColor = option.selected ? cs.selectionForegroundColor : cs.foregroundColor;
4769 			painter.drawText(pos, option.label);
4770 			pos.y += defaultLineHeight;
4771 		}
4772 	}
4773 
4774 	static class Style : Widget.Style {
4775 		override WidgetBackground background() {
4776 			return WidgetBackground(WidgetPainter.visualTheme.widgetBackgroundColor);
4777 		}
4778 	}
4779 	mixin OverrideStyle!Style;
4780 	//mixin Padding!q{2};
4781 
4782 	void addOption(string text, void* tag = null) {
4783 		options ~= Option(text, false, tag);
4784 		version(win32_widgets) {
4785 			WCharzBuffer buffer = WCharzBuffer(text);
4786 			SendMessageW(hwnd, LB_ADDSTRING, 0, cast(LPARAM) buffer.ptr);
4787 		}
4788 		version(custom_widgets) {
4789 			setContentSize(width, cast(int) (options.length * defaultLineHeight));
4790 			redraw();
4791 		}
4792 	}
4793 
4794 	void clear() {
4795 		options = null;
4796 		version(win32_widgets) {
4797 			while(SendMessageW(hwnd, LB_DELETESTRING, 0, 0) > 0)
4798 				{}
4799 
4800 		} else version(custom_widgets) {
4801 			scrollTo(Point(0, 0));
4802 			redraw();
4803 		}
4804 	}
4805 
4806 	Option[] options;
4807 	version(win32_widgets)
4808 		enum multiSelect = false; /// not implemented yet
4809 	else
4810 		bool multiSelect;
4811 
4812 	override int heightStretchiness() { return 6; }
4813 }
4814 
4815 
4816 
4817 /// For [ScrollableWidget], determines when to show the scroll bar to the user.
4818 enum ScrollBarShowPolicy {
4819 	automatic, /// automatically show the scroll bar if it is necessary
4820 	never, /// never show the scroll bar (scrolling must be done programmatically)
4821 	always /// always show the scroll bar, even if it is disabled
4822 }
4823 
4824 /++
4825 	A widget that tries (with, at best, limited success) to offer scrolling that is transparent to the inner.
4826 
4827 	It isn't very good and will very likely be removed. Try [ScrollMessageWidget] or [ScrollableContainerWidget] instead for new code.
4828 +/
4829 // FIXME ScrollBarShowPolicy
4830 // FIXME: use the ScrollMessageWidget in here now that it exists
4831 class ScrollableWidget : Widget {
4832 	// FIXME: make line size configurable
4833 	// FIXME: add keyboard controls
4834 	version(win32_widgets) {
4835 		override int hookedWndProc(UINT msg, WPARAM wParam, LPARAM lParam) {
4836 			if(msg == WM_VSCROLL || msg == WM_HSCROLL) {
4837 				auto pos = HIWORD(wParam);
4838 				auto m = LOWORD(wParam);
4839 
4840 				// FIXME: I can reintroduce the
4841 				// scroll bars now by using this
4842 				// in the top-level window handler
4843 				// to forward comamnds
4844 				auto scrollbarHwnd = lParam;
4845 				switch(m) {
4846 					case SB_BOTTOM:
4847 						if(msg == WM_HSCROLL)
4848 							horizontalScrollTo(contentWidth_);
4849 						else
4850 							verticalScrollTo(contentHeight_);
4851 					break;
4852 					case SB_TOP:
4853 						if(msg == WM_HSCROLL)
4854 							horizontalScrollTo(0);
4855 						else
4856 							verticalScrollTo(0);
4857 					break;
4858 					case SB_ENDSCROLL:
4859 						// idk
4860 					break;
4861 					case SB_LINEDOWN:
4862 						if(msg == WM_HSCROLL)
4863 							horizontalScroll(scaleWithDpi(16));
4864 						else
4865 							verticalScroll(scaleWithDpi(16));
4866 					break;
4867 					case SB_LINEUP:
4868 						if(msg == WM_HSCROLL)
4869 							horizontalScroll(scaleWithDpi(-16));
4870 						else
4871 							verticalScroll(scaleWithDpi(-16));
4872 					break;
4873 					case SB_PAGEDOWN:
4874 						if(msg == WM_HSCROLL)
4875 							horizontalScroll(scaleWithDpi(100));
4876 						else
4877 							verticalScroll(scaleWithDpi(100));
4878 					break;
4879 					case SB_PAGEUP:
4880 						if(msg == WM_HSCROLL)
4881 							horizontalScroll(scaleWithDpi(-100));
4882 						else
4883 							verticalScroll(scaleWithDpi(-100));
4884 					break;
4885 					case SB_THUMBPOSITION:
4886 					case SB_THUMBTRACK:
4887 						if(msg == WM_HSCROLL)
4888 							horizontalScrollTo(pos);
4889 						else
4890 							verticalScrollTo(pos);
4891 
4892 						if(m == SB_THUMBTRACK) {
4893 							// the event loop doesn't seem to carry on with a requested redraw..
4894 							// so we request it to get our dirty bit set...
4895 							redraw();
4896 
4897 							// then we need to immediately actually redraw it too for instant feedback to user
4898 
4899 							SimpleWindow.processAllCustomEvents();
4900 							//if(parentWindow)
4901 								//parentWindow.actualRedraw();
4902 						}
4903 					break;
4904 					default:
4905 				}
4906 			}
4907 			return super.hookedWndProc(msg, wParam, lParam);
4908 		}
4909 	}
4910 	///
4911 	this(Widget parent) {
4912 		this.parentWindow = parent.parentWindow;
4913 
4914 		version(win32_widgets) {
4915 			createWin32Window(this, Win32Class!"arsd_minigui_ScrollableWidget"w, "",
4916 				0|WS_CHILD|WS_VISIBLE|WS_HSCROLL|WS_VSCROLL, 0);
4917 			super(parent);
4918 		} else version(custom_widgets) {
4919 			outerContainer = new InternalScrollableContainerWidget(this, parent);
4920 			super(outerContainer);
4921 		} else static assert(0);
4922 	}
4923 
4924 	version(custom_widgets)
4925 		InternalScrollableContainerWidget outerContainer;
4926 
4927 	override void defaultEventHandler_click(ClickEvent event) {
4928 		if(event.button == MouseButton.wheelUp)
4929 			verticalScroll(scaleWithDpi(-16));
4930 		if(event.button == MouseButton.wheelDown)
4931 			verticalScroll(scaleWithDpi(16));
4932 		super.defaultEventHandler_click(event);
4933 	}
4934 
4935 	override void defaultEventHandler_keydown(KeyDownEvent event) {
4936 		switch(event.key) {
4937 			case Key.Left:
4938 				horizontalScroll(scaleWithDpi(-16));
4939 			break;
4940 			case Key.Right:
4941 				horizontalScroll(scaleWithDpi(16));
4942 			break;
4943 			case Key.Up:
4944 				verticalScroll(scaleWithDpi(-16));
4945 			break;
4946 			case Key.Down:
4947 				verticalScroll(scaleWithDpi(16));
4948 			break;
4949 			case Key.Home:
4950 				verticalScrollTo(0);
4951 			break;
4952 			case Key.End:
4953 				verticalScrollTo(contentHeight);
4954 			break;
4955 			case Key.PageUp:
4956 				verticalScroll(scaleWithDpi(-160));
4957 			break;
4958 			case Key.PageDown:
4959 				verticalScroll(scaleWithDpi(160));
4960 			break;
4961 			default:
4962 		}
4963 		super.defaultEventHandler_keydown(event);
4964 	}
4965 
4966 
4967 	version(win32_widgets)
4968 	override void recomputeChildLayout() {
4969 		super.recomputeChildLayout();
4970 		SCROLLINFO info;
4971 		info.cbSize = info.sizeof;
4972 		info.nPage = viewportHeight;
4973 		info.fMask = SIF_PAGE | SIF_RANGE;
4974 		info.nMin = 0;
4975 		info.nMax = contentHeight_;
4976 		SetScrollInfo(hwnd, SB_VERT, &info, true);
4977 
4978 		info.cbSize = info.sizeof;
4979 		info.nPage = viewportWidth;
4980 		info.fMask = SIF_PAGE | SIF_RANGE;
4981 		info.nMin = 0;
4982 		info.nMax = contentWidth_;
4983 		SetScrollInfo(hwnd, SB_HORZ, &info, true);
4984 	}
4985 
4986 	/*
4987 		Scrolling
4988 		------------
4989 
4990 		You are assigned a width and a height by the layout engine, which
4991 		is your viewport box. However, you may draw more than that by setting
4992 		a contentWidth and contentHeight.
4993 
4994 		If these can be contained by the viewport, no scrollbar is displayed.
4995 		If they cannot fit though, it will automatically show scroll as necessary.
4996 
4997 		If contentWidth == 0, no horizontal scrolling is performed. If contentHeight
4998 		is zero, no vertical scrolling is performed.
4999 
5000 		If scrolling is necessary, the lib will automatically work with the bars.
5001 		When you redraw, the origin and clipping info in the painter is set so if
5002 		you just draw everything, it will work, but you can be more efficient by checking
5003 		the viewportWidth, viewportHeight, and scrollOrigin members.
5004 	*/
5005 
5006 	///
5007 	final @property int viewportWidth() {
5008 		return width - (showingVerticalScroll ? scaleWithDpi(16) : 0);
5009 	}
5010 	///
5011 	final @property int viewportHeight() {
5012 		return height - (showingHorizontalScroll ? scaleWithDpi(16) : 0);
5013 	}
5014 
5015 	// FIXME property
5016 	Point scrollOrigin_;
5017 
5018 	///
5019 	final const(Point) scrollOrigin() {
5020 		return scrollOrigin_;
5021 	}
5022 
5023 	// the user sets these two
5024 	private int contentWidth_ = 0;
5025 	private int contentHeight_ = 0;
5026 
5027 	///
5028 	int contentWidth() { return contentWidth_; }
5029 	///
5030 	int contentHeight() { return contentHeight_; }
5031 
5032 	///
5033 	void setContentSize(int width, int height) {
5034 		contentWidth_ = width;
5035 		contentHeight_ = height;
5036 
5037 		version(custom_widgets) {
5038 			if(showingVerticalScroll || showingHorizontalScroll) {
5039 				outerContainer.recomputeChildLayout();
5040 			}
5041 
5042 			if(showingVerticalScroll())
5043 				outerContainer.verticalScrollBar.redraw();
5044 			if(showingHorizontalScroll())
5045 				outerContainer.horizontalScrollBar.redraw();
5046 		} else version(win32_widgets) {
5047 			recomputeChildLayout();
5048 		} else static assert(0);
5049 	}
5050 
5051 	///
5052 	void verticalScroll(int delta) {
5053 		verticalScrollTo(scrollOrigin.y + delta);
5054 	}
5055 	///
5056 	void verticalScrollTo(int pos) {
5057 		scrollOrigin_.y = pos;
5058 		if(pos == int.max || (scrollOrigin_.y + viewportHeight > contentHeight))
5059 			scrollOrigin_.y = contentHeight - viewportHeight;
5060 
5061 		if(scrollOrigin_.y < 0)
5062 			scrollOrigin_.y = 0;
5063 
5064 		version(win32_widgets) {
5065 			SCROLLINFO info;
5066 			info.cbSize = info.sizeof;
5067 			info.fMask = SIF_POS;
5068 			info.nPos = scrollOrigin_.y;
5069 			SetScrollInfo(hwnd, SB_VERT, &info, true);
5070 		} else version(custom_widgets) {
5071 			outerContainer.verticalScrollBar.setPosition(scrollOrigin_.y);
5072 		} else static assert(0);
5073 
5074 		redraw();
5075 	}
5076 
5077 	///
5078 	void horizontalScroll(int delta) {
5079 		horizontalScrollTo(scrollOrigin.x + delta);
5080 	}
5081 	///
5082 	void horizontalScrollTo(int pos) {
5083 		scrollOrigin_.x = pos;
5084 		if(pos == int.max || (scrollOrigin_.x + viewportWidth > contentWidth))
5085 			scrollOrigin_.x = contentWidth - viewportWidth;
5086 
5087 		if(scrollOrigin_.x < 0)
5088 			scrollOrigin_.x = 0;
5089 
5090 		version(win32_widgets) {
5091 			SCROLLINFO info;
5092 			info.cbSize = info.sizeof;
5093 			info.fMask = SIF_POS;
5094 			info.nPos = scrollOrigin_.x;
5095 			SetScrollInfo(hwnd, SB_HORZ, &info, true);
5096 		} else version(custom_widgets) {
5097 			outerContainer.horizontalScrollBar.setPosition(scrollOrigin_.x);
5098 		} else static assert(0);
5099 
5100 		redraw();
5101 	}
5102 	///
5103 	void scrollTo(Point p) {
5104 		verticalScrollTo(p.y);
5105 		horizontalScrollTo(p.x);
5106 	}
5107 
5108 	///
5109 	void ensureVisibleInScroll(Point p) {
5110 		auto rect = viewportRectangle();
5111 		if(rect.contains(p))
5112 			return;
5113 		if(p.x < rect.left)
5114 			horizontalScroll(p.x - rect.left);
5115 		else if(p.x > rect.right)
5116 			horizontalScroll(p.x - rect.right);
5117 
5118 		if(p.y < rect.top)
5119 			verticalScroll(p.y - rect.top);
5120 		else if(p.y > rect.bottom)
5121 			verticalScroll(p.y - rect.bottom);
5122 	}
5123 
5124 	///
5125 	void ensureVisibleInScroll(Rectangle rect) {
5126 		ensureVisibleInScroll(rect.upperLeft);
5127 		ensureVisibleInScroll(rect.lowerRight);
5128 	}
5129 
5130 	///
5131 	Rectangle viewportRectangle() {
5132 		return Rectangle(scrollOrigin, Size(viewportWidth, viewportHeight));
5133 	}
5134 
5135 	///
5136 	bool showingHorizontalScroll() {
5137 		return contentWidth > width;
5138 	}
5139 	///
5140 	bool showingVerticalScroll() {
5141 		return contentHeight > height;
5142 	}
5143 
5144 	/// This is called before the ordinary paint delegate,
5145 	/// giving you a chance to draw the window frame, etc,
5146 	/// before the scroll clip takes effect
5147 	void paintFrameAndBackground(WidgetPainter painter) {
5148 		version(win32_widgets) {
5149 			auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
5150 			auto p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
5151 			// since the pen is null, to fill the whole space, we need the +1 on both.
5152 			gdi.Rectangle(painter.impl.hdc, 0, 0, this.width + 1, this.height + 1);
5153 			SelectObject(painter.impl.hdc, p);
5154 			SelectObject(painter.impl.hdc, b);
5155 		}
5156 
5157 	}
5158 
5159 	// make space for the scroll bar, and that's it.
5160 	final override int paddingRight() { return scaleWithDpi(16); }
5161 	final override int paddingBottom() { return scaleWithDpi(16); }
5162 
5163 	/*
5164 		END SCROLLING
5165 	*/
5166 
5167 	override WidgetPainter draw() {
5168 		int x = this.x, y = this.y;
5169 		auto parent = this.parent;
5170 		while(parent) {
5171 			x += parent.x;
5172 			y += parent.y;
5173 			parent = parent.parent;
5174 		}
5175 
5176 		//version(win32_widgets) {
5177 			//auto painter = simpleWindowWrappingHwnd ? simpleWindowWrappingHwnd.draw(true) : parentWindow.win.draw(true);
5178 		//} else {
5179 			auto painter = parentWindow.win.draw(true);
5180 		//}
5181 		painter.originX = x;
5182 		painter.originY = y;
5183 
5184 		painter.originX = painter.originX - scrollOrigin.x;
5185 		painter.originY = painter.originY - scrollOrigin.y;
5186 		painter.setClipRectangle(scrollOrigin, viewportWidth(), viewportHeight());
5187 
5188 		return WidgetPainter(painter, this);
5189 	}
5190 
5191 	mixin ScrollableChildren;
5192 }
5193 
5194 // you need to have a Point scrollOrigin in the class somewhere
5195 // and a paintFrameAndBackground
5196 private mixin template ScrollableChildren() {
5197 	override protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force, bool invalidate) {
5198 		if(hidden)
5199 			return;
5200 
5201 		//version(win32_widgets)
5202 			//painter = simpleWindowWrappingHwnd ? simpleWindowWrappingHwnd.draw(true) : parentWindow.win.draw(true);
5203 
5204 		painter.originX = lox + x;
5205 		painter.originY = loy + y;
5206 
5207 		bool actuallyPainted = false;
5208 
5209 		const clip = containment.intersectionOf(Rectangle(Point(lox + x, loy + y), Size(width, height)));
5210 		if(clip == Rectangle.init)
5211 			return;
5212 
5213 		if(force || redrawRequested) {
5214 			//painter.setClipRectangle(scrollOrigin, width, height);
5215 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
5216 			paintFrameAndBackground(painter);
5217 		}
5218 
5219 		painter.originX = painter.originX - scrollOrigin.x;
5220 		painter.originY = painter.originY - scrollOrigin.y;
5221 		if(force || redrawRequested) {
5222 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY) + Point(2, 2) /* border */, clip.width - 4, clip.height - 4);
5223 			//painter.setClipRectangle(scrollOrigin + Point(2, 2) /* border */, width - 4, height - 4);
5224 
5225 			//erase(painter); // we paintFrameAndBackground above so no need
5226 			if(painter.visualTheme)
5227 				painter.visualTheme.doPaint(this, painter);
5228 			else
5229 				paint(painter);
5230 
5231 			if(invalidate) {
5232 				painter.invalidateRect(Rectangle(Point(clip.upperLeft.x - painter.originX, clip.upperRight.y - painter.originY), Size(clip.width, clip.height)));
5233 				// children are contained inside this, so no need to do extra work
5234 				invalidate = false;
5235 			}
5236 
5237 
5238 			actuallyPainted = true;
5239 			redrawRequested = false;
5240 		}
5241 		foreach(child; children) {
5242 			if(cast(FixedPosition) child)
5243 				child.privatePaint(painter, painter.originX + scrollOrigin.x, painter.originY + scrollOrigin.y, clip, actuallyPainted, invalidate);
5244 			else
5245 				child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted, invalidate);
5246 		}
5247 	}
5248 }
5249 
5250 private class InternalScrollableContainerInsideWidget : ContainerWidget {
5251 	ScrollableContainerWidget scw;
5252 
5253 	this(ScrollableContainerWidget parent) {
5254 		scw = parent;
5255 		super(parent);
5256 	}
5257 
5258 	version(custom_widgets)
5259 	override protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force, bool invalidate) {
5260 		if(hidden)
5261 			return;
5262 
5263 		bool actuallyPainted = false;
5264 
5265 		auto scrollOrigin = Point(scw.scrollX_, scw.scrollY_);
5266 
5267 		const clip = containment.intersectionOf(Rectangle(Point(lox + x, loy + y), Size(width + scw.scrollX_, height + scw.scrollY_)));
5268 		if(clip == Rectangle.init)
5269 			return;
5270 
5271 		painter.originX = lox + x - scrollOrigin.x;
5272 		painter.originY = loy + y - scrollOrigin.y;
5273 		if(force || redrawRequested) {
5274 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
5275 
5276 			erase(painter);
5277 			if(painter.visualTheme)
5278 				painter.visualTheme.doPaint(this, painter);
5279 			else
5280 				paint(painter);
5281 
5282 			if(invalidate) {
5283 				painter.invalidateRect(Rectangle(Point(clip.upperLeft.x - painter.originX, clip.upperRight.y - painter.originY), Size(clip.width, clip.height)));
5284 				// children are contained inside this, so no need to do extra work
5285 				invalidate = false;
5286 			}
5287 
5288 			actuallyPainted = true;
5289 			redrawRequested = false;
5290 		}
5291 		foreach(child; children) {
5292 			if(cast(FixedPosition) child)
5293 				child.privatePaint(painter, painter.originX + scrollOrigin.x, painter.originY + scrollOrigin.y, clip, actuallyPainted, invalidate);
5294 			else
5295 				child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted, invalidate);
5296 		}
5297 	}
5298 
5299 	version(custom_widgets)
5300 	override protected void addScrollPosition(ref int x, ref int y) {
5301 		x += scw.scrollX_;
5302 		y += scw.scrollY_;
5303 	}
5304 }
5305 
5306 /++
5307 	A widget meant to contain other widgets that may need to scroll.
5308 
5309 	Currently buggy.
5310 
5311 	History:
5312 		Added July 1, 2021 (dub v10.2)
5313 
5314 		On January 3, 2022, I tried to use it in a few other cases
5315 		and found it only worked well in the original test case. Since
5316 		it still sucks, I think I'm going to rewrite it again.
5317 +/
5318 class ScrollableContainerWidget : ContainerWidget {
5319 	///
5320 	this(Widget parent) {
5321 		super(parent);
5322 
5323 		container = new InternalScrollableContainerInsideWidget(this);
5324 		hsb = new HorizontalScrollbar(this);
5325 		vsb = new VerticalScrollbar(this);
5326 
5327 		tabStop = false;
5328 		container.tabStop = false;
5329 		magic = true;
5330 
5331 
5332 		vsb.addEventListener("scrolltonextline", () {
5333 			scrollBy(0, scaleWithDpi(16));
5334 		});
5335 		vsb.addEventListener("scrolltopreviousline", () {
5336 			scrollBy(0,scaleWithDpi( -16));
5337 		});
5338 		vsb.addEventListener("scrolltonextpage", () {
5339 			scrollBy(0, container.height);
5340 		});
5341 		vsb.addEventListener("scrolltopreviouspage", () {
5342 			scrollBy(0, -container.height);
5343 		});
5344 		vsb.addEventListener((scope ScrollToPositionEvent spe) {
5345 			scrollTo(scrollX_, spe.value);
5346 		});
5347 
5348 		this.addEventListener(delegate (scope ClickEvent e) {
5349 			if(e.button == MouseButton.wheelUp) {
5350 				if(!e.defaultPrevented)
5351 					scrollBy(0, scaleWithDpi(-16));
5352 				e.stopPropagation();
5353 			} else if(e.button == MouseButton.wheelDown) {
5354 				if(!e.defaultPrevented)
5355 					scrollBy(0, scaleWithDpi(16));
5356 				e.stopPropagation();
5357 			}
5358 		});
5359 	}
5360 
5361 	/+
5362 	override void defaultEventHandler_click(ClickEvent e) {
5363 	}
5364 	+/
5365 
5366 	override void removeAllChildren() {
5367 		container.removeAllChildren();
5368 	}
5369 
5370 	void scrollTo(int x, int y) {
5371 		scrollBy(x - scrollX_, y - scrollY_);
5372 	}
5373 
5374 	void scrollBy(int x, int y) {
5375 		auto ox = scrollX_;
5376 		auto oy = scrollY_;
5377 
5378 		auto nx = ox + x;
5379 		auto ny = oy + y;
5380 
5381 		if(nx < 0)
5382 			nx = 0;
5383 		if(ny < 0)
5384 			ny = 0;
5385 
5386 		auto maxX = hsb.max - container.width;
5387 		if(maxX < 0) maxX = 0;
5388 		auto maxY = vsb.max - container.height;
5389 		if(maxY < 0) maxY = 0;
5390 
5391 		if(nx > maxX)
5392 			nx = maxX;
5393 		if(ny > maxY)
5394 			ny = maxY;
5395 
5396 		auto dx = nx - ox;
5397 		auto dy = ny - oy;
5398 
5399 		if(dx || dy) {
5400 			version(win32_widgets)
5401 				ScrollWindowEx(container.hwnd, -dx, -dy, null, null, null, null, SW_SCROLLCHILDREN | SW_INVALIDATE | SW_ERASE);
5402 			else {
5403 				redraw();
5404 			}
5405 
5406 			hsb.setPosition = nx;
5407 			vsb.setPosition = ny;
5408 
5409 			scrollX_ = nx;
5410 			scrollY_ = ny;
5411 		}
5412 	}
5413 
5414 	private int scrollX_;
5415 	private int scrollY_;
5416 
5417 	void setTotalArea(int width, int height) {
5418 		hsb.setMax(width);
5419 		vsb.setMax(height);
5420 	}
5421 
5422 	///
5423 	void setViewableArea(int width, int height) {
5424 		hsb.setViewableArea(width);
5425 		vsb.setViewableArea(height);
5426 	}
5427 
5428 	private bool magic;
5429 	override void addChild(Widget w, int position = int.max) {
5430 		if(magic)
5431 			container.addChild(w, position);
5432 		else
5433 			super.addChild(w, position);
5434 	}
5435 
5436 	override void recomputeChildLayout() {
5437 		if(hsb is null || vsb is null || container is null) return;
5438 
5439 		/+
5440 		import std.stdio; writeln(x, " ", y , " ", width, " ", height);
5441 		writeln(this.ContainerWidget.minWidth(), "x", this.ContainerWidget.minHeight());
5442 		+/
5443 
5444 		registerMovement();
5445 
5446 		hsb.height = scaleWithDpi(16); // FIXME? are tese 16s sane?
5447 		hsb.x = 0;
5448 		hsb.y = this.height - hsb.height;
5449 		hsb.width = this.width - scaleWithDpi(16);
5450 		hsb.recomputeChildLayout();
5451 
5452 		vsb.width = scaleWithDpi(16); // FIXME?
5453 		vsb.x = this.width - vsb.width;
5454 		vsb.y = 0;
5455 		vsb.height = this.height - scaleWithDpi(16);
5456 		vsb.recomputeChildLayout();
5457 
5458 		container.x = 0;
5459 		container.y = 0;
5460 		container.width = this.width - vsb.width;
5461 		container.height = this.height - hsb.height;
5462 		container.recomputeChildLayout();
5463 
5464 		scrollX_ = 0;
5465 		scrollY_ = 0;
5466 
5467 		hsb.setPosition(0);
5468 		vsb.setPosition(0);
5469 
5470 		int mw, mh;
5471 		Widget c = container;
5472 		// FIXME: hack here to handle a layout inside...
5473 		if(c.children.length == 1 && cast(Layout) c.children[0])
5474 			c = c.children[0];
5475 		foreach(child; c.children) {
5476 			auto w = child.x + child.width;
5477 			auto h = child.y + child.height;
5478 
5479 			if(w > mw) mw = w;
5480 			if(h > mh) mh = h;
5481 		}
5482 
5483 		setTotalArea(mw, mh);
5484 		setViewableArea(width, height);
5485 	}
5486 
5487 	override int minHeight() { return scaleWithDpi(64); }
5488 
5489 	HorizontalScrollbar hsb;
5490 	VerticalScrollbar vsb;
5491 	ContainerWidget container;
5492 }
5493 
5494 
5495 version(custom_widgets)
5496 private class InternalScrollableContainerWidget : Widget {
5497 
5498 	ScrollableWidget sw;
5499 
5500 	VerticalScrollbar verticalScrollBar;
5501 	HorizontalScrollbar horizontalScrollBar;
5502 
5503 	this(ScrollableWidget sw, Widget parent) {
5504 		this.sw = sw;
5505 
5506 		this.tabStop = false;
5507 
5508 		horizontalScrollBar = new HorizontalScrollbar(this);
5509 		verticalScrollBar = new VerticalScrollbar(this);
5510 
5511 		horizontalScrollBar.showing_ = false;
5512 		verticalScrollBar.showing_ = false;
5513 
5514 		horizontalScrollBar.addEventListener("scrolltonextline", {
5515 			horizontalScrollBar.setPosition(horizontalScrollBar.position + 1);
5516 			sw.horizontalScrollTo(horizontalScrollBar.position);
5517 		});
5518 		horizontalScrollBar.addEventListener("scrolltopreviousline", {
5519 			horizontalScrollBar.setPosition(horizontalScrollBar.position - 1);
5520 			sw.horizontalScrollTo(horizontalScrollBar.position);
5521 		});
5522 		verticalScrollBar.addEventListener("scrolltonextline", {
5523 			verticalScrollBar.setPosition(verticalScrollBar.position + 1);
5524 			sw.verticalScrollTo(verticalScrollBar.position);
5525 		});
5526 		verticalScrollBar.addEventListener("scrolltopreviousline", {
5527 			verticalScrollBar.setPosition(verticalScrollBar.position - 1);
5528 			sw.verticalScrollTo(verticalScrollBar.position);
5529 		});
5530 		horizontalScrollBar.addEventListener("scrolltonextpage", {
5531 			horizontalScrollBar.setPosition(horizontalScrollBar.position + horizontalScrollBar.step_);
5532 			sw.horizontalScrollTo(horizontalScrollBar.position);
5533 		});
5534 		horizontalScrollBar.addEventListener("scrolltopreviouspage", {
5535 			horizontalScrollBar.setPosition(horizontalScrollBar.position - horizontalScrollBar.step_);
5536 			sw.horizontalScrollTo(horizontalScrollBar.position);
5537 		});
5538 		verticalScrollBar.addEventListener("scrolltonextpage", {
5539 			verticalScrollBar.setPosition(verticalScrollBar.position + verticalScrollBar.step_);
5540 			sw.verticalScrollTo(verticalScrollBar.position);
5541 		});
5542 		verticalScrollBar.addEventListener("scrolltopreviouspage", {
5543 			verticalScrollBar.setPosition(verticalScrollBar.position - verticalScrollBar.step_);
5544 			sw.verticalScrollTo(verticalScrollBar.position);
5545 		});
5546 		horizontalScrollBar.addEventListener("scrolltoposition", (Event event) {
5547 			horizontalScrollBar.setPosition(event.intValue);
5548 			sw.horizontalScrollTo(horizontalScrollBar.position);
5549 		});
5550 		verticalScrollBar.addEventListener("scrolltoposition", (Event event) {
5551 			verticalScrollBar.setPosition(event.intValue);
5552 			sw.verticalScrollTo(verticalScrollBar.position);
5553 		});
5554 		horizontalScrollBar.addEventListener("scrolltrack", (Event event) {
5555 			horizontalScrollBar.setPosition(event.intValue);
5556 			sw.horizontalScrollTo(horizontalScrollBar.position);
5557 		});
5558 		verticalScrollBar.addEventListener("scrolltrack", (Event event) {
5559 			verticalScrollBar.setPosition(event.intValue);
5560 		});
5561 
5562 		super(parent);
5563 	}
5564 
5565 	// this is supposed to be basically invisible...
5566 	override int minWidth() { return sw.minWidth; }
5567 	override int minHeight() { return sw.minHeight; }
5568 	override int maxWidth() { return sw.maxWidth; }
5569 	override int maxHeight() { return sw.maxHeight; }
5570 	override int widthStretchiness() { return sw.widthStretchiness; }
5571 	override int heightStretchiness() { return sw.heightStretchiness; }
5572 	override int marginLeft() { return sw.marginLeft; }
5573 	override int marginRight() { return sw.marginRight; }
5574 	override int marginTop() { return sw.marginTop; }
5575 	override int marginBottom() { return sw.marginBottom; }
5576 	override int paddingLeft() { return sw.paddingLeft; }
5577 	override int paddingRight() { return sw.paddingRight; }
5578 	override int paddingTop() { return sw.paddingTop; }
5579 	override int paddingBottom() { return sw.paddingBottom; }
5580 	override void focus() { sw.focus(); }
5581 
5582 
5583 	override void recomputeChildLayout() {
5584 		// The stupid thing needs to calculate if a scroll bar is needed...
5585 		recomputeChildLayoutHelper();
5586 		// then running it again will position things correctly if the bar is NOT needed
5587 		recomputeChildLayoutHelper();
5588 
5589 		// this sucks but meh it barely works
5590 	}
5591 
5592 	private void recomputeChildLayoutHelper() {
5593 		if(sw is null) return;
5594 
5595 		bool both = sw.showingVerticalScroll && sw.showingHorizontalScroll;
5596 		if(horizontalScrollBar && verticalScrollBar) {
5597 			horizontalScrollBar.width = this.width - (both ? verticalScrollBar.minWidth() : 0);
5598 			horizontalScrollBar.height = horizontalScrollBar.minHeight();
5599 			horizontalScrollBar.x = 0;
5600 			horizontalScrollBar.y = this.height - horizontalScrollBar.minHeight();
5601 
5602 			verticalScrollBar.width = verticalScrollBar.minWidth();
5603 			verticalScrollBar.height = this.height - (both ? horizontalScrollBar.minHeight() : 0) - 2 - 2;
5604 			verticalScrollBar.x = this.width - verticalScrollBar.minWidth();
5605 			verticalScrollBar.y = 0 + 2;
5606 
5607 			sw.x = 0;
5608 			sw.y = 0;
5609 			sw.width = this.width - (verticalScrollBar.showing ? verticalScrollBar.width : 0);
5610 			sw.height = this.height - (horizontalScrollBar.showing ? horizontalScrollBar.height : 0);
5611 
5612 			if(sw.contentWidth_ <= this.width)
5613 				sw.scrollOrigin_.x = 0;
5614 			if(sw.contentHeight_ <= this.height)
5615 				sw.scrollOrigin_.y = 0;
5616 
5617 			horizontalScrollBar.recomputeChildLayout();
5618 			verticalScrollBar.recomputeChildLayout();
5619 			sw.recomputeChildLayout();
5620 		}
5621 
5622 		if(sw.contentWidth_ <= this.width)
5623 			sw.scrollOrigin_.x = 0;
5624 		if(sw.contentHeight_ <= this.height)
5625 			sw.scrollOrigin_.y = 0;
5626 
5627 		if(sw.showingHorizontalScroll())
5628 			horizontalScrollBar.showing(true, false);
5629 		else
5630 			horizontalScrollBar.showing(false, false);
5631 		if(sw.showingVerticalScroll())
5632 			verticalScrollBar.showing(true, false);
5633 		else
5634 			verticalScrollBar.showing(false, false);
5635 
5636 		verticalScrollBar.setViewableArea(sw.viewportHeight());
5637 		verticalScrollBar.setMax(sw.contentHeight);
5638 		verticalScrollBar.setPosition(sw.scrollOrigin.y);
5639 
5640 		horizontalScrollBar.setViewableArea(sw.viewportWidth());
5641 		horizontalScrollBar.setMax(sw.contentWidth);
5642 		horizontalScrollBar.setPosition(sw.scrollOrigin.x);
5643 	}
5644 }
5645 
5646 /*
5647 class ScrollableClientWidget : Widget {
5648 	this(Widget parent) {
5649 		super(parent);
5650 	}
5651 	override void paint(WidgetPainter p) {
5652 		parent.paint(p);
5653 	}
5654 }
5655 */
5656 
5657 /++
5658 	A slider, also known as a trackbar control, is commonly used in applications like volume controls where you want the user to select a value between a min and a max without needing a specific value or otherwise precise input.
5659 +/
5660 abstract class Slider : Widget {
5661 	this(int min, int max, int step, Widget parent) {
5662 		min_ = min;
5663 		max_ = max;
5664 		step_ = step;
5665 		page_ = step;
5666 		super(parent);
5667 	}
5668 
5669 	private int min_;
5670 	private int max_;
5671 	private int step_;
5672 	private int position_;
5673 	private int page_;
5674 
5675 	// selection start and selection end
5676 	// tics
5677 	// tooltip?
5678 	// some way to see and just type the value
5679 	// win32 buddy controls are labels
5680 
5681 	///
5682 	void setMin(int a) {
5683 		min_ = a;
5684 		version(custom_widgets)
5685 			redraw();
5686 		version(win32_widgets)
5687 			SendMessage(hwnd, TBM_SETRANGEMIN, true, a);
5688 	}
5689 	///
5690 	int min() {
5691 		return min_;
5692 	}
5693 	///
5694 	void setMax(int a) {
5695 		max_ = a;
5696 		version(custom_widgets)
5697 			redraw();
5698 		version(win32_widgets)
5699 			SendMessage(hwnd, TBM_SETRANGEMAX, true, a);
5700 	}
5701 	///
5702 	int max() {
5703 		return max_;
5704 	}
5705 	///
5706 	void setPosition(int a) {
5707 		if(a > max)
5708 			a = max;
5709 		if(a < min)
5710 			a = min;
5711 		position_ = a;
5712 		version(custom_widgets)
5713 			setPositionCustom(a);
5714 
5715 		version(win32_widgets)
5716 			setPositionWindows(a);
5717 	}
5718 	version(win32_widgets) {
5719 		protected abstract void setPositionWindows(int a);
5720 	}
5721 
5722 	protected abstract int win32direction();
5723 
5724 	/++
5725 		Alias for [position] for better compatibility with generic code.
5726 
5727 		History:
5728 			Added October 5, 2021
5729 	+/
5730 	@property int value() {
5731 		return position;
5732 	}
5733 
5734 	///
5735 	int position() {
5736 		return position_;
5737 	}
5738 	///
5739 	void setStep(int a) {
5740 		step_ = a;
5741 		version(win32_widgets)
5742 			SendMessage(hwnd, TBM_SETLINESIZE, 0, a);
5743 	}
5744 	///
5745 	int step() {
5746 		return step_;
5747 	}
5748 	///
5749 	void setPageSize(int a) {
5750 		page_ = a;
5751 		version(win32_widgets)
5752 			SendMessage(hwnd, TBM_SETPAGESIZE, 0, a);
5753 	}
5754 	///
5755 	int pageSize() {
5756 		return page_;
5757 	}
5758 
5759 	private void notify() {
5760 		auto event = new ChangeEvent!int(this, &this.position);
5761 		event.dispatch();
5762 	}
5763 
5764 	version(win32_widgets)
5765 	void win32Setup(int style) {
5766 		createWin32Window(this, TRACKBAR_CLASS, "",
5767 			0|WS_CHILD|WS_VISIBLE|style|TBS_TOOLTIPS, 0);
5768 
5769 		// the trackbar sends the same messages as scroll, which
5770 		// our other layer sends as these... just gonna translate
5771 		// here
5772 		this.addDirectEventListener("scrolltoposition", (Event event) {
5773 			event.stopPropagation();
5774 			this.setPosition(this.win32direction > 0 ? event.intValue : max - event.intValue);
5775 			notify();
5776 		});
5777 		this.addDirectEventListener("scrolltonextline", (Event event) {
5778 			event.stopPropagation();
5779 			this.setPosition(this.position + this.step_ * this.win32direction);
5780 			notify();
5781 		});
5782 		this.addDirectEventListener("scrolltopreviousline", (Event event) {
5783 			event.stopPropagation();
5784 			this.setPosition(this.position - this.step_ * this.win32direction);
5785 			notify();
5786 		});
5787 		this.addDirectEventListener("scrolltonextpage", (Event event) {
5788 			event.stopPropagation();
5789 			this.setPosition(this.position + this.page_ * this.win32direction);
5790 			notify();
5791 		});
5792 		this.addDirectEventListener("scrolltopreviouspage", (Event event) {
5793 			event.stopPropagation();
5794 			this.setPosition(this.position - this.page_ * this.win32direction);
5795 			notify();
5796 		});
5797 
5798 		setMin(min_);
5799 		setMax(max_);
5800 		setStep(step_);
5801 		setPageSize(page_);
5802 	}
5803 
5804 	version(custom_widgets) {
5805 		protected MouseTrackingWidget thumb;
5806 
5807 		protected abstract void setPositionCustom(int a);
5808 
5809 		override void defaultEventHandler_keydown(KeyDownEvent event) {
5810 			switch(event.key) {
5811 				case Key.Up:
5812 				case Key.Right:
5813 					setPosition(position() - step() * win32direction);
5814 					changed();
5815 				break;
5816 				case Key.Down:
5817 				case Key.Left:
5818 					setPosition(position() + step() * win32direction);
5819 					changed();
5820 				break;
5821 				case Key.Home:
5822 					setPosition(win32direction > 0 ? min() : max());
5823 					changed();
5824 				break;
5825 				case Key.End:
5826 					setPosition(win32direction > 0 ? max() : min());
5827 					changed();
5828 				break;
5829 				case Key.PageUp:
5830 					setPosition(position() - pageSize() * win32direction);
5831 					changed();
5832 				break;
5833 				case Key.PageDown:
5834 					setPosition(position() + pageSize() * win32direction);
5835 					changed();
5836 				break;
5837 				default:
5838 			}
5839 			super.defaultEventHandler_keydown(event);
5840 		}
5841 
5842 		protected void changed() {
5843 			auto ev = new ChangeEvent!int(this, &position);
5844 			ev.dispatch();
5845 		}
5846 	}
5847 }
5848 
5849 /++
5850 
5851 +/
5852 class VerticalSlider : Slider {
5853 	this(int min, int max, int step, Widget parent) {
5854 		version(custom_widgets)
5855 			initialize();
5856 
5857 		super(min, max, step, parent);
5858 
5859 		version(win32_widgets)
5860 			win32Setup(TBS_VERT | 0x0200 /* TBS_REVERSED */);
5861 	}
5862 
5863 	protected override int win32direction() {
5864 		return -1;
5865 	}
5866 
5867 	version(win32_widgets)
5868 	protected override void setPositionWindows(int a) {
5869 		// the windows thing makes the top 0 and i don't like that.
5870 		SendMessage(hwnd, TBM_SETPOS, true, max - a);
5871 	}
5872 
5873 	version(custom_widgets)
5874 	private void initialize() {
5875 		thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.vertical, this);
5876 
5877 		thumb.tabStop = false;
5878 
5879 		thumb.thumbWidth = width;
5880 		thumb.thumbHeight = scaleWithDpi(16);
5881 
5882 		thumb.addEventListener(EventType.change, () {
5883 			auto sx = thumb.positionY * max() / (thumb.height - scaleWithDpi(16));
5884 			sx = max - sx;
5885 			//informProgramThatUserChangedPosition(sx);
5886 
5887 			position_ = sx;
5888 
5889 			changed();
5890 		});
5891 	}
5892 
5893 	version(custom_widgets)
5894 	override void recomputeChildLayout() {
5895 		thumb.thumbWidth = this.width;
5896 		super.recomputeChildLayout();
5897 		setPositionCustom(position_);
5898 	}
5899 
5900 	version(custom_widgets)
5901 	protected override void setPositionCustom(int a) {
5902 		if(max())
5903 			thumb.positionY = (max - a) * (thumb.height - scaleWithDpi(16)) / max();
5904 		redraw();
5905 	}
5906 }
5907 
5908 /++
5909 
5910 +/
5911 class HorizontalSlider : Slider {
5912 	this(int min, int max, int step, Widget parent) {
5913 		version(custom_widgets)
5914 			initialize();
5915 
5916 		super(min, max, step, parent);
5917 
5918 		version(win32_widgets)
5919 			win32Setup(TBS_HORZ);
5920 	}
5921 
5922 	version(win32_widgets)
5923 	protected override void setPositionWindows(int a) {
5924 		SendMessage(hwnd, TBM_SETPOS, true, a);
5925 	}
5926 
5927 	protected override int win32direction() {
5928 		return 1;
5929 	}
5930 
5931 	version(custom_widgets)
5932 	private void initialize() {
5933 		thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.horizontal, this);
5934 
5935 		thumb.tabStop = false;
5936 
5937 		thumb.thumbWidth = scaleWithDpi(16);
5938 		thumb.thumbHeight = height;
5939 
5940 		thumb.addEventListener(EventType.change, () {
5941 			auto sx = thumb.positionX * max() / (thumb.width - scaleWithDpi(16));
5942 			//informProgramThatUserChangedPosition(sx);
5943 
5944 			position_ = sx;
5945 
5946 			changed();
5947 		});
5948 	}
5949 
5950 	version(custom_widgets)
5951 	override void recomputeChildLayout() {
5952 		thumb.thumbHeight = this.height;
5953 		super.recomputeChildLayout();
5954 		setPositionCustom(position_);
5955 	}
5956 
5957 	version(custom_widgets)
5958 	protected override void setPositionCustom(int a) {
5959 		if(max())
5960 			thumb.positionX = a * (thumb.width - scaleWithDpi(16)) / max();
5961 		redraw();
5962 	}
5963 }
5964 
5965 
5966 ///
5967 abstract class ScrollbarBase : Widget {
5968 	///
5969 	this(Widget parent) {
5970 		super(parent);
5971 		tabStop = false;
5972 		step_ = scaleWithDpi(16);
5973 	}
5974 
5975 	private int viewableArea_;
5976 	private int max_;
5977 	private int step_;// = 16;
5978 	private int position_;
5979 
5980 	///
5981 	bool atEnd() {
5982 		return position_ + viewableArea_ >= max_;
5983 	}
5984 
5985 	///
5986 	bool atStart() {
5987 		return position_ == 0;
5988 	}
5989 
5990 	///
5991 	void setViewableArea(int a) {
5992 		viewableArea_ = a;
5993 		version(custom_widgets)
5994 			redraw();
5995 	}
5996 	///
5997 	void setMax(int a) {
5998 		max_ = a;
5999 		version(custom_widgets)
6000 			redraw();
6001 	}
6002 	///
6003 	int max() {
6004 		return max_;
6005 	}
6006 	///
6007 	void setPosition(int a) {
6008 		auto logicalMax = max_ - viewableArea_;
6009 		if(a == int.max)
6010 			a = logicalMax;
6011 
6012 		if(a > logicalMax)
6013 			a = logicalMax;
6014 		if(a < 0)
6015 			a = 0;
6016 
6017 		position_ = a;
6018 
6019 		version(custom_widgets)
6020 			redraw();
6021 	}
6022 	///
6023 	int position() {
6024 		return position_;
6025 	}
6026 	///
6027 	void setStep(int a) {
6028 		step_ = a;
6029 	}
6030 	///
6031 	int step() {
6032 		return step_;
6033 	}
6034 
6035 	// FIXME: remove this.... maybe
6036 	/+
6037 	protected void informProgramThatUserChangedPosition(int n) {
6038 		position_ = n;
6039 		auto evt = new Event(EventType.change, this);
6040 		evt.intValue = n;
6041 		evt.dispatch();
6042 	}
6043 	+/
6044 
6045 	version(custom_widgets) {
6046 		enum MIN_THUMB_SIZE = 8;
6047 
6048 		abstract protected int getBarDim();
6049 		int thumbSize() {
6050 			if(viewableArea_ >= max_ || max_ == 0)
6051 				return getBarDim();
6052 
6053 			int res = viewableArea_ * getBarDim() / max_;
6054 
6055 			if(res < scaleWithDpi(MIN_THUMB_SIZE))
6056 				res = scaleWithDpi(MIN_THUMB_SIZE);
6057 
6058 			return res;
6059 		}
6060 
6061 		int thumbPosition() {
6062 			/*
6063 				viewableArea_ is the viewport height/width
6064 				position_ is where we are
6065 			*/
6066 			//if(position_ + viewableArea_ >= max_)
6067 				//return getBarDim - thumbSize;
6068 
6069 			auto maximumPossibleValue = getBarDim() - thumbSize;
6070 			auto maximiumLogicalValue = max_ - viewableArea_;
6071 
6072 			auto p = (maximiumLogicalValue > 0) ? cast(int) (cast(long) position_ * maximumPossibleValue / maximiumLogicalValue) : 0;
6073 
6074 			return p;
6075 		}
6076 	}
6077 }
6078 
6079 //public import mgt;
6080 
6081 /++
6082 	A mouse tracking widget is one that follows the mouse when dragged inside it.
6083 
6084 	Concrete subclasses may include a scrollbar thumb and a volume control.
6085 +/
6086 //version(custom_widgets)
6087 class MouseTrackingWidget : Widget {
6088 
6089 	///
6090 	int positionX() { return positionX_; }
6091 	///
6092 	int positionY() { return positionY_; }
6093 
6094 	///
6095 	void positionX(int p) { positionX_ = p; }
6096 	///
6097 	void positionY(int p) { positionY_ = p; }
6098 
6099 	private int positionX_;
6100 	private int positionY_;
6101 
6102 	///
6103 	enum Orientation {
6104 		horizontal, ///
6105 		vertical, ///
6106 		twoDimensional, ///
6107 	}
6108 
6109 	private int thumbWidth_;
6110 	private int thumbHeight_;
6111 
6112 	///
6113 	int thumbWidth() { return thumbWidth_; }
6114 	///
6115 	int thumbHeight() { return thumbHeight_; }
6116 	///
6117 	int thumbWidth(int a) { return thumbWidth_ = a; }
6118 	///
6119 	int thumbHeight(int a) { return thumbHeight_ = a; }
6120 
6121 	private bool dragging;
6122 	private bool hovering;
6123 	private int startMouseX, startMouseY;
6124 
6125 	///
6126 	this(Orientation orientation, Widget parent) {
6127 		super(parent);
6128 
6129 		//assert(parentWindow !is null);
6130 
6131 		addEventListener((MouseDownEvent event) {
6132 			if(event.clientX >= positionX && event.clientX < positionX + thumbWidth && event.clientY >= positionY && event.clientY < positionY + thumbHeight) {
6133 				dragging = true;
6134 				startMouseX = event.clientX - positionX;
6135 				startMouseY = event.clientY - positionY;
6136 				parentWindow.captureMouse(this);
6137 			} else {
6138 				if(orientation == Orientation.horizontal || orientation == Orientation.twoDimensional)
6139 					positionX = event.clientX - thumbWidth / 2;
6140 				if(orientation == Orientation.vertical || orientation == Orientation.twoDimensional)
6141 					positionY = event.clientY - thumbHeight / 2;
6142 
6143 				if(positionX + thumbWidth > this.width)
6144 					positionX = this.width - thumbWidth;
6145 				if(positionY + thumbHeight > this.height)
6146 					positionY = this.height - thumbHeight;
6147 
6148 				if(positionX < 0)
6149 					positionX = 0;
6150 				if(positionY < 0)
6151 					positionY = 0;
6152 
6153 
6154 				// this.emit!(ChangeEvent!void)();
6155 				auto evt = new Event(EventType.change, this);
6156 				evt.sendDirectly();
6157 
6158 				redraw();
6159 
6160 			}
6161 		});
6162 
6163 		addEventListener(EventType.mouseup, (Event event) {
6164 			dragging = false;
6165 			parentWindow.releaseMouseCapture();
6166 		});
6167 
6168 		addEventListener(EventType.mouseout, (Event event) {
6169 			if(!hovering)
6170 				return;
6171 			hovering = false;
6172 			redraw();
6173 		});
6174 
6175 		int lpx, lpy;
6176 
6177 		addEventListener((MouseMoveEvent event) {
6178 			auto oh = hovering;
6179 			if(event.clientX >= positionX && event.clientX < positionX + thumbWidth && event.clientY >= positionY && event.clientY < positionY + thumbHeight) {
6180 				hovering = true;
6181 			} else {
6182 				hovering = false;
6183 			}
6184 			if(!dragging) {
6185 				if(hovering != oh)
6186 					redraw();
6187 				return;
6188 			}
6189 
6190 			if(orientation == Orientation.horizontal || orientation == Orientation.twoDimensional)
6191 				positionX = event.clientX - startMouseX; // FIXME: click could be in the middle of it
6192 			if(orientation == Orientation.vertical || orientation == Orientation.twoDimensional)
6193 				positionY = event.clientY - startMouseY;
6194 
6195 			if(positionX + thumbWidth > this.width)
6196 				positionX = this.width - thumbWidth;
6197 			if(positionY + thumbHeight > this.height)
6198 				positionY = this.height - thumbHeight;
6199 
6200 			if(positionX < 0)
6201 				positionX = 0;
6202 			if(positionY < 0)
6203 				positionY = 0;
6204 
6205 			if(positionX != lpx || positionY != lpy) {
6206 				lpx = positionX;
6207 				lpy = positionY;
6208 
6209 				auto evt = new Event(EventType.change, this);
6210 				evt.sendDirectly();
6211 			}
6212 
6213 			redraw();
6214 		});
6215 	}
6216 
6217 	version(custom_widgets)
6218 	override void paint(WidgetPainter painter) {
6219 		auto cs = getComputedStyle();
6220 		auto c = darken(cs.windowBackgroundColor, 0.2);
6221 		painter.outlineColor = c;
6222 		painter.fillColor = c;
6223 		painter.drawRectangle(Point(0, 0), this.width, this.height);
6224 
6225 		auto color = hovering ? cs.hoveringColor : cs.windowBackgroundColor;
6226 		draw3dFrame(positionX, positionY, thumbWidth, thumbHeight, painter, FrameStyle.risen, color);
6227 	}
6228 }
6229 
6230 //version(custom_widgets)
6231 //private
6232 class HorizontalScrollbar : ScrollbarBase {
6233 
6234 	version(custom_widgets) {
6235 		private MouseTrackingWidget thumb;
6236 
6237 		override int getBarDim() {
6238 			return thumb.width;
6239 		}
6240 	}
6241 
6242 	override void setViewableArea(int a) {
6243 		super.setViewableArea(a);
6244 
6245 		version(win32_widgets) {
6246 			SCROLLINFO info;
6247 			info.cbSize = info.sizeof;
6248 			info.nPage = a + 1;
6249 			info.fMask = SIF_PAGE;
6250 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6251 		} else version(custom_widgets) {
6252 			thumb.positionX = thumbPosition;
6253 			thumb.thumbWidth = thumbSize;
6254 			thumb.redraw();
6255 		} else static assert(0);
6256 
6257 	}
6258 
6259 	override void setMax(int a) {
6260 		super.setMax(a);
6261 		version(win32_widgets) {
6262 			SCROLLINFO info;
6263 			info.cbSize = info.sizeof;
6264 			info.nMin = 0;
6265 			info.nMax = max;
6266 			info.fMask = SIF_RANGE;
6267 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6268 		} else version(custom_widgets) {
6269 			thumb.positionX = thumbPosition;
6270 			thumb.thumbWidth = thumbSize;
6271 			thumb.redraw();
6272 		}
6273 	}
6274 
6275 	override void setPosition(int a) {
6276 		super.setPosition(a);
6277 		version(win32_widgets) {
6278 			SCROLLINFO info;
6279 			info.cbSize = info.sizeof;
6280 			info.fMask = SIF_POS;
6281 			info.nPos = position;
6282 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6283 		} else version(custom_widgets) {
6284 			thumb.positionX = thumbPosition();
6285 			thumb.thumbWidth = thumbSize;
6286 			thumb.redraw();
6287 		} else static assert(0);
6288 	}
6289 
6290 	this(Widget parent) {
6291 		super(parent);
6292 
6293 		version(win32_widgets) {
6294 			createWin32Window(this, "Scrollbar"w, "",
6295 				0|WS_CHILD|WS_VISIBLE|SBS_HORZ|SBS_BOTTOMALIGN, 0);
6296 		} else version(custom_widgets) {
6297 			auto vl = new HorizontalLayout(this);
6298 			auto leftButton = new ArrowButton(ArrowDirection.left, vl);
6299 			leftButton.setClickRepeat(scrollClickRepeatInterval);
6300 			thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.horizontal, vl);
6301 			auto rightButton = new ArrowButton(ArrowDirection.right, vl);
6302 			rightButton.setClickRepeat(scrollClickRepeatInterval);
6303 
6304 			leftButton.tabStop = false;
6305 			rightButton.tabStop = false;
6306 			thumb.tabStop = false;
6307 
6308 			leftButton.addEventListener(EventType.triggered, () {
6309 				this.emitCommand!"scrolltopreviousline"();
6310 				//informProgramThatUserChangedPosition(position - step());
6311 			});
6312 			rightButton.addEventListener(EventType.triggered, () {
6313 				this.emitCommand!"scrolltonextline"();
6314 				//informProgramThatUserChangedPosition(position + step());
6315 			});
6316 
6317 			thumb.thumbWidth = this.minWidth;
6318 			thumb.thumbHeight = scaleWithDpi(16);
6319 
6320 			thumb.addEventListener(EventType.change, () {
6321 				auto maximumPossibleValue = thumb.width - thumb.thumbWidth;
6322 				auto sx = maximumPossibleValue ? cast(int)(cast(long) thumb.positionX * (max()-viewableArea_) / maximumPossibleValue) : 0;
6323 
6324 				//informProgramThatUserChangedPosition(sx);
6325 
6326 				auto ev = new ScrollToPositionEvent(this, sx);
6327 				ev.dispatch();
6328 			});
6329 		}
6330 	}
6331 
6332 	override int minHeight() { return scaleWithDpi(16); }
6333 	override int maxHeight() { return scaleWithDpi(16); }
6334 	override int minWidth() { return scaleWithDpi(48); }
6335 }
6336 
6337 class ScrollToPositionEvent : Event {
6338 	enum EventString = "scrolltoposition";
6339 
6340 	this(Widget target, int value) {
6341 		this.value = value;
6342 		super(EventString, target);
6343 	}
6344 
6345 	immutable int value;
6346 
6347 	override @property int intValue() {
6348 		return value;
6349 	}
6350 }
6351 
6352 //version(custom_widgets)
6353 //private
6354 class VerticalScrollbar : ScrollbarBase {
6355 
6356 	version(custom_widgets) {
6357 		override int getBarDim() {
6358 			return thumb.height;
6359 		}
6360 
6361 		private MouseTrackingWidget thumb;
6362 	}
6363 
6364 	override void setViewableArea(int a) {
6365 		super.setViewableArea(a);
6366 
6367 		version(win32_widgets) {
6368 			SCROLLINFO info;
6369 			info.cbSize = info.sizeof;
6370 			info.nPage = a + 1;
6371 			info.fMask = SIF_PAGE;
6372 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6373 		} else version(custom_widgets) {
6374 			thumb.positionY = thumbPosition;
6375 			thumb.thumbHeight = thumbSize;
6376 			thumb.redraw();
6377 		} else static assert(0);
6378 
6379 	}
6380 
6381 	override void setMax(int a) {
6382 		super.setMax(a);
6383 		version(win32_widgets) {
6384 			SCROLLINFO info;
6385 			info.cbSize = info.sizeof;
6386 			info.nMin = 0;
6387 			info.nMax = max;
6388 			info.fMask = SIF_RANGE;
6389 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6390 		} else version(custom_widgets) {
6391 			thumb.positionY = thumbPosition;
6392 			thumb.thumbHeight = thumbSize;
6393 			thumb.redraw();
6394 		}
6395 	}
6396 
6397 	override void setPosition(int a) {
6398 		super.setPosition(a);
6399 		version(win32_widgets) {
6400 			SCROLLINFO info;
6401 			info.cbSize = info.sizeof;
6402 			info.fMask = SIF_POS;
6403 			info.nPos = position;
6404 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6405 		} else version(custom_widgets) {
6406 			thumb.positionY = thumbPosition;
6407 			thumb.thumbHeight = thumbSize;
6408 			thumb.redraw();
6409 		} else static assert(0);
6410 	}
6411 
6412 	this(Widget parent) {
6413 		super(parent);
6414 
6415 		version(win32_widgets) {
6416 			createWin32Window(this, "Scrollbar"w, "",
6417 				0|WS_CHILD|WS_VISIBLE|SBS_VERT|SBS_RIGHTALIGN, 0);
6418 		} else version(custom_widgets) {
6419 			auto vl = new VerticalLayout(this);
6420 			auto upButton = new ArrowButton(ArrowDirection.up, vl);
6421 			upButton.setClickRepeat(scrollClickRepeatInterval);
6422 			thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.vertical, vl);
6423 			auto downButton = new ArrowButton(ArrowDirection.down, vl);
6424 			downButton.setClickRepeat(scrollClickRepeatInterval);
6425 
6426 			upButton.addEventListener(EventType.triggered, () {
6427 				this.emitCommand!"scrolltopreviousline"();
6428 				//informProgramThatUserChangedPosition(position - step());
6429 			});
6430 			downButton.addEventListener(EventType.triggered, () {
6431 				this.emitCommand!"scrolltonextline"();
6432 				//informProgramThatUserChangedPosition(position + step());
6433 			});
6434 
6435 			thumb.thumbWidth = this.minWidth;
6436 			thumb.thumbHeight = scaleWithDpi(16);
6437 
6438 			thumb.addEventListener(EventType.change, () {
6439 				auto maximumPossibleValue = thumb.height - thumb.thumbHeight;
6440 				auto sy = maximumPossibleValue ? cast(int) (cast(long) thumb.positionY * (max()-viewableArea_) / maximumPossibleValue) : 0;
6441 
6442 				auto ev = new ScrollToPositionEvent(this, sy);
6443 				ev.dispatch();
6444 
6445 				//informProgramThatUserChangedPosition(sy);
6446 			});
6447 
6448 			upButton.tabStop = false;
6449 			downButton.tabStop = false;
6450 			thumb.tabStop = false;
6451 		}
6452 	}
6453 
6454 	override int minWidth() { return scaleWithDpi(16); }
6455 	override int maxWidth() { return scaleWithDpi(16); }
6456 	override int minHeight() { return scaleWithDpi(48); }
6457 }
6458 
6459 
6460 /++
6461 	EXPERIMENTAL
6462 
6463 	A widget specialized for being a container for other widgets.
6464 
6465 	History:
6466 		Added May 29, 2021. Not stabilized at this time.
6467 +/
6468 class WidgetContainer : Widget {
6469 	this(Widget parent) {
6470 		tabStop = false;
6471 		super(parent);
6472 	}
6473 
6474 	override int maxHeight() {
6475 		if(this.children.length == 1) {
6476 			return saturatedSum(this.children[0].maxHeight, this.children[0].marginTop, this.children[0].marginBottom);
6477 		} else {
6478 			return int.max;
6479 		}
6480 	}
6481 
6482 	override int maxWidth() {
6483 		if(this.children.length == 1) {
6484 			return saturatedSum(this.children[0].maxWidth, this.children[0].marginLeft, this.children[0].marginRight);
6485 		} else {
6486 			return int.max;
6487 		}
6488 	}
6489 
6490 	/+
6491 
6492 	override int minHeight() {
6493 		int largest = 0;
6494 		int margins = 0;
6495 		int lastMargin = 0;
6496 		foreach(child; children) {
6497 			auto mh = child.minHeight();
6498 			if(mh > largest)
6499 				largest = mh;
6500 			margins += mymax(lastMargin, child.marginTop());
6501 			lastMargin = child.marginBottom();
6502 		}
6503 		return largest + margins;
6504 	}
6505 
6506 	override int maxHeight() {
6507 		int largest = 0;
6508 		int margins = 0;
6509 		int lastMargin = 0;
6510 		foreach(child; children) {
6511 			auto mh = child.maxHeight();
6512 			if(mh == int.max)
6513 				return int.max;
6514 			if(mh > largest)
6515 				largest = mh;
6516 			margins += mymax(lastMargin, child.marginTop());
6517 			lastMargin = child.marginBottom();
6518 		}
6519 		return largest + margins;
6520 	}
6521 
6522 	override int minWidth() {
6523 		int min;
6524 		foreach(child; children) {
6525 			auto cm = child.minWidth;
6526 			if(cm > min)
6527 				min = cm;
6528 		}
6529 		return min + paddingLeft + paddingRight;
6530 	}
6531 
6532 	override int minHeight() {
6533 		int min;
6534 		foreach(child; children) {
6535 			auto cm = child.minHeight;
6536 			if(cm > min)
6537 				min = cm;
6538 		}
6539 		return min + paddingTop + paddingBottom;
6540 	}
6541 
6542 	override int maxHeight() {
6543 		int largest = 0;
6544 		int margins = 0;
6545 		int lastMargin = 0;
6546 		foreach(child; children) {
6547 			auto mh = child.maxHeight();
6548 			if(mh == int.max)
6549 				return int.max;
6550 			if(mh > largest)
6551 				largest = mh;
6552 			margins += mymax(lastMargin, child.marginTop());
6553 			lastMargin = child.marginBottom();
6554 		}
6555 		return largest + margins;
6556 	}
6557 
6558 	override int heightStretchiness() {
6559 		int max;
6560 		foreach(child; children) {
6561 			auto c = child.heightStretchiness;
6562 			if(c > max)
6563 				max = c;
6564 		}
6565 		return max;
6566 	}
6567 
6568 	override int marginTop() {
6569 		if(this.children.length)
6570 			return this.children[0].marginTop;
6571 		return 0;
6572 	}
6573 	+/
6574 }
6575 
6576 ///
6577 abstract class Layout : Widget {
6578 	this(Widget parent) {
6579 		tabStop = false;
6580 		super(parent);
6581 	}
6582 }
6583 
6584 /++
6585 	Makes all children minimum width and height, placing them down
6586 	left to right, top to bottom.
6587 
6588 	Useful if you want to make a list of buttons that automatically
6589 	wrap to a new line when necessary.
6590 +/
6591 class InlineBlockLayout : Layout {
6592 	///
6593 	this(Widget parent) { super(parent); }
6594 
6595 	override void recomputeChildLayout() {
6596 		registerMovement();
6597 
6598 		int x = this.paddingLeft, y = this.paddingTop;
6599 
6600 		int lineHeight;
6601 		int previousMargin = 0;
6602 		int previousMarginBottom = 0;
6603 
6604 		foreach(child; children) {
6605 			if(child.hidden)
6606 				continue;
6607 			if(cast(FixedPosition) child) {
6608 				child.recomputeChildLayout();
6609 				continue;
6610 			}
6611 			child.width = child.flexBasisWidth();
6612 			if(child.width == 0)
6613 				child.width = child.minWidth();
6614 			if(child.width == 0)
6615 				child.width = 32;
6616 
6617 			child.height = child.flexBasisHeight();
6618 			if(child.height == 0)
6619 				child.height = child.minHeight();
6620 			if(child.height == 0)
6621 				child.height = 32;
6622 
6623 			if(x + child.width + paddingRight > this.width) {
6624 				x = this.paddingLeft;
6625 				y += lineHeight;
6626 				lineHeight = 0;
6627 				previousMargin = 0;
6628 				previousMarginBottom = 0;
6629 			}
6630 
6631 			auto margin = child.marginLeft;
6632 			if(previousMargin > margin)
6633 				margin = previousMargin;
6634 
6635 			x += margin;
6636 
6637 			child.x = x;
6638 			child.y = y;
6639 
6640 			int marginTopApplied;
6641 			if(child.marginTop > previousMarginBottom) {
6642 				child.y += child.marginTop;
6643 				marginTopApplied = child.marginTop;
6644 			}
6645 
6646 			x += child.width;
6647 			previousMargin = child.marginRight;
6648 
6649 			if(child.marginBottom > previousMarginBottom)
6650 				previousMarginBottom = child.marginBottom;
6651 
6652 			auto h = child.height + previousMarginBottom + marginTopApplied;
6653 			if(h > lineHeight)
6654 				lineHeight = h;
6655 
6656 			child.recomputeChildLayout();
6657 		}
6658 
6659 	}
6660 
6661 	override int minWidth() {
6662 		int min;
6663 		foreach(child; children) {
6664 			auto cm = child.minWidth;
6665 			if(cm > min)
6666 				min = cm;
6667 		}
6668 		return min + paddingLeft + paddingRight;
6669 	}
6670 
6671 	override int minHeight() {
6672 		int min;
6673 		foreach(child; children) {
6674 			auto cm = child.minHeight;
6675 			if(cm > min)
6676 				min = cm;
6677 		}
6678 		return min + paddingTop + paddingBottom;
6679 	}
6680 }
6681 
6682 /++
6683 	A TabMessageWidget is a clickable row of tabs followed by a content area, very similar
6684 	to the [TabWidget]. The difference is the TabMessageWidget only sends messages, whereas
6685 	the [TabWidget] will automatically change pages of child widgets.
6686 
6687 	This allows you to react to it however you see fit rather than having to
6688 	be tied to just the new sets of child widgets.
6689 
6690 	It sends the message in the form of `this.emitCommand!"changetab"();`.
6691 
6692 	History:
6693 		Added December 24, 2021 (dub v10.5)
6694 +/
6695 class TabMessageWidget : Widget {
6696 
6697 	protected void tabIndexClicked(int item) {
6698 		this.emitCommand!"changetab"();
6699 	}
6700 
6701 	/++
6702 		Adds the a new tab to the control with the given title.
6703 
6704 		Returns:
6705 			The index of the newly added tab. You will need to know
6706 			this index to refer to it later and to know which tab to
6707 			change to when you get a changetab message.
6708 	+/
6709 	int addTab(string title, int pos = int.max) {
6710 		version(win32_widgets) {
6711 			TCITEM item;
6712 			item.mask = TCIF_TEXT;
6713 			WCharzBuffer buf = WCharzBuffer(title);
6714 			item.pszText = buf.ptr;
6715 			return cast(int) SendMessage(hwnd, TCM_INSERTITEM, pos, cast(LPARAM) &item);
6716 		} else version(custom_widgets) {
6717 			if(pos >= tabs.length) {
6718 				tabs ~= title;
6719 				redraw();
6720 				return cast(int) tabs.length - 1;
6721 			} else if(pos <= 0) {
6722 				tabs = title ~ tabs;
6723 				redraw();
6724 				return 0;
6725 			} else {
6726 				tabs = tabs[0 .. pos] ~ title ~ title[pos .. $];
6727 				redraw();
6728 				return pos;
6729 			}
6730 		}
6731 	}
6732 
6733 	override void addChild(Widget child, int pos = int.max) {
6734 		if(container)
6735 			container.addChild(child, pos);
6736 		else
6737 			super.addChild(child, pos);
6738 	}
6739 
6740 	protected Widget makeContainer() {
6741 		return new Widget(this);
6742 	}
6743 
6744 	private Widget container;
6745 
6746 	override void recomputeChildLayout() {
6747 		version(win32_widgets) {
6748 			this.registerMovement();
6749 
6750 			RECT rect;
6751 			GetWindowRect(hwnd, &rect);
6752 
6753 			auto left = rect.left;
6754 			auto top = rect.top;
6755 
6756 			TabCtrl_AdjustRect(hwnd, false, &rect);
6757 			foreach(child; children) {
6758 				if(!child.showing) continue;
6759 				child.x = rect.left - left;
6760 				child.y = rect.top - top;
6761 				child.width = rect.right - rect.left;
6762 				child.height = rect.bottom - rect.top;
6763 				child.recomputeChildLayout();
6764 			}
6765 		} else version(custom_widgets) {
6766 			this.registerMovement();
6767 			foreach(child; children) {
6768 				if(!child.showing) continue;
6769 				child.x = 2;
6770 				child.y = tabBarHeight + 2; // for the border
6771 				child.width = width - 4; // for the border
6772 				child.height = height - tabBarHeight - 2 - 2; // for the border
6773 				child.recomputeChildLayout();
6774 			}
6775 		} else static assert(0);
6776 	}
6777 
6778 	version(custom_widgets)
6779 		string[] tabs;
6780 
6781 	this(Widget parent) {
6782 		super(parent);
6783 
6784 		tabStop = false;
6785 
6786 		version(win32_widgets) {
6787 			createWin32Window(this, WC_TABCONTROL, "", 0);
6788 		} else version(custom_widgets) {
6789 			addEventListener((ClickEvent event) {
6790 				if(event.target !is this && this.container !is null && event.target !is this.container) return;
6791 				if(event.clientY < tabBarHeight) {
6792 					auto t = (event.clientX / tabWidth);
6793 					if(t >= 0 && t < tabs.length) {
6794 						currentTab_ = t;
6795 						tabIndexClicked(t);
6796 						redraw();
6797 					}
6798 				}
6799 			});
6800 		} else static assert(0);
6801 
6802 		this.container = makeContainer();
6803 	}
6804 
6805 	override int marginTop() { return 4; }
6806 	override int paddingBottom() { return 4; }
6807 
6808 	override int minHeight() {
6809 		int max = 0;
6810 		foreach(child; children)
6811 			max = mymax(child.minHeight, max);
6812 
6813 
6814 		version(win32_widgets) {
6815 			RECT rect;
6816 			rect.right = this.width;
6817 			rect.bottom = max;
6818 			TabCtrl_AdjustRect(hwnd, true, &rect);
6819 
6820 			max = rect.bottom;
6821 		} else {
6822 			max += defaultLineHeight + 4;
6823 		}
6824 
6825 
6826 		return max;
6827 	}
6828 
6829 	version(win32_widgets)
6830 	override int handleWmNotify(NMHDR* hdr, int code, out int mustReturn) {
6831 		switch(code) {
6832 			case TCN_SELCHANGE:
6833 				auto sel = TabCtrl_GetCurSel(hwnd);
6834 				tabIndexClicked(sel);
6835 			break;
6836 			default:
6837 		}
6838 		return 0;
6839 	}
6840 
6841 	version(custom_widgets) {
6842 		private int currentTab_;
6843 		private int tabBarHeight() { return defaultLineHeight; }
6844 		int tabWidth = 80;
6845 	}
6846 
6847 	version(win32_widgets)
6848 	override void paint(WidgetPainter painter) {}
6849 
6850 	version(custom_widgets)
6851 	override void paint(WidgetPainter painter) {
6852 		auto cs = getComputedStyle();
6853 
6854 		draw3dFrame(0, tabBarHeight - 2, width, height - tabBarHeight + 2, painter, FrameStyle.risen, cs.background.color);
6855 
6856 		int posX = 0;
6857 		foreach(idx, title; tabs) {
6858 			auto isCurrent = idx == getCurrentTab();
6859 
6860 			painter.setClipRectangle(Point(posX, 0), tabWidth, tabBarHeight);
6861 
6862 			draw3dFrame(posX, 0, tabWidth, tabBarHeight, painter, isCurrent ? FrameStyle.risen : FrameStyle.sunk, isCurrent ? cs.windowBackgroundColor : darken(cs.windowBackgroundColor, 0.1));
6863 			painter.outlineColor = cs.foregroundColor;
6864 			painter.drawText(Point(posX + 4, 2), title, Point(posX + tabWidth, tabBarHeight - 2), TextAlignment.VerticalCenter);
6865 
6866 			if(isCurrent) {
6867 				painter.outlineColor = cs.windowBackgroundColor;
6868 				painter.fillColor = Color.transparent;
6869 				painter.drawLine(Point(posX + 2, tabBarHeight - 1), Point(posX + tabWidth, tabBarHeight - 1));
6870 				painter.drawLine(Point(posX + 2, tabBarHeight - 2), Point(posX + tabWidth, tabBarHeight - 2));
6871 
6872 				painter.outlineColor = Color.white;
6873 				painter.drawPixel(Point(posX + 1, tabBarHeight - 1));
6874 				painter.drawPixel(Point(posX + 1, tabBarHeight - 2));
6875 				painter.outlineColor = cs.activeTabColor;
6876 				painter.drawPixel(Point(posX, tabBarHeight - 1));
6877 			}
6878 
6879 			posX += tabWidth - 2;
6880 		}
6881 	}
6882 
6883 	///
6884 	@scriptable
6885 	void setCurrentTab(int item) {
6886 		version(win32_widgets)
6887 			TabCtrl_SetCurSel(hwnd, item);
6888 		else version(custom_widgets)
6889 			currentTab_ = item;
6890 		else static assert(0);
6891 
6892 		tabIndexClicked(item);
6893 	}
6894 
6895 	///
6896 	@scriptable
6897 	int getCurrentTab() {
6898 		version(win32_widgets)
6899 			return TabCtrl_GetCurSel(hwnd);
6900 		else version(custom_widgets)
6901 			return currentTab_; // FIXME
6902 		else static assert(0);
6903 	}
6904 
6905 	///
6906 	@scriptable
6907 	void removeTab(int item) {
6908 		if(item && item == getCurrentTab())
6909 			setCurrentTab(item - 1);
6910 
6911 		version(win32_widgets) {
6912 			TabCtrl_DeleteItem(hwnd, item);
6913 		}
6914 
6915 		for(int a = item; a < children.length - 1; a++)
6916 			this._children[a] = this._children[a + 1];
6917 		this._children = this._children[0 .. $-1];
6918 	}
6919 
6920 }
6921 
6922 
6923 /++
6924 	A tab widget is a set of clickable tab buttons followed by a content area.
6925 
6926 
6927 	Tabs can change existing content or can be new pages.
6928 
6929 	When the user picks a different tab, a `change` message is generated.
6930 +/
6931 class TabWidget : TabMessageWidget {
6932 	this(Widget parent) {
6933 		super(parent);
6934 	}
6935 
6936 	override protected Widget makeContainer() {
6937 		return null;
6938 	}
6939 
6940 	override void addChild(Widget child, int pos = int.max) {
6941 		if(auto twp = cast(TabWidgetPage) child) {
6942 			Widget.addChild(child, pos);
6943 			if(pos == int.max)
6944 				pos = cast(int) this.children.length - 1;
6945 
6946 			super.addTab(twp.title, pos); // need to bypass the override here which would get into a loop...
6947 
6948 			if(pos != getCurrentTab) {
6949 				child.showing = false;
6950 			}
6951 		} else {
6952 			assert(0, "Don't add children directly to a tab widget, instead add them to a page (see addPage)");
6953 		}
6954 	}
6955 
6956 	// FIXME: add tab icons at some point, Windows supports them
6957 	/++
6958 		Adds a page and its associated tab with the given label to the widget.
6959 
6960 		Returns:
6961 			The added page object, to which you can add other widgets.
6962 	+/
6963 	@scriptable
6964 	TabWidgetPage addPage(string title) {
6965 		return new TabWidgetPage(title, this);
6966 	}
6967 
6968 	/++
6969 		Gets the page at the given tab index, or `null` if the index is bad.
6970 
6971 		History:
6972 			Added December 24, 2021.
6973 	+/
6974 	TabWidgetPage getPage(int index) {
6975 		if(index < this.children.length)
6976 			return null;
6977 		return cast(TabWidgetPage) this.children[index];
6978 	}
6979 
6980 	/++
6981 		While you can still use the addTab from the parent class,
6982 		*strongly* recommend you use [addPage] insteaad.
6983 
6984 		History:
6985 			Added December 24, 2021 to fulful the interface
6986 			requirement that came from adding [TabMessageWidget].
6987 
6988 			You should not use it though since the [addPage] function
6989 			is much easier to use here.
6990 	+/
6991 	override int addTab(string title, int pos = int.max) {
6992 		auto p = addPage(title);
6993 		foreach(idx, child; this.children)
6994 			if(child is p)
6995 				return cast(int) idx;
6996 		return -1;
6997 	}
6998 
6999 	protected override void tabIndexClicked(int item) {
7000 		foreach(idx, child; children) {
7001 			child.showing(false, false); // batch the recalculates for the end
7002 		}
7003 
7004 		foreach(idx, child; children) {
7005 			if(idx == item) {
7006 				child.showing(true, false);
7007 				if(parentWindow) {
7008 					auto f = parentWindow.getFirstFocusable(child);
7009 					if(f)
7010 						f.focus();
7011 				}
7012 				recomputeChildLayout();
7013 			}
7014 		}
7015 
7016 		version(win32_widgets) {
7017 			InvalidateRect(hwnd, null, true);
7018 		} else version(custom_widgets) {
7019 			this.redraw();
7020 		}
7021 	}
7022 
7023 }
7024 
7025 /++
7026 	A page widget is basically a tab widget with hidden tabs. It is also sometimes called a "StackWidget".
7027 
7028 	You add [TabWidgetPage]s to it.
7029 +/
7030 class PageWidget : Widget {
7031 	this(Widget parent) {
7032 		super(parent);
7033 	}
7034 
7035 	override int minHeight() {
7036 		int max = 0;
7037 		foreach(child; children)
7038 			max = mymax(child.minHeight, max);
7039 
7040 		return max;
7041 	}
7042 
7043 
7044 	override void addChild(Widget child, int pos = int.max) {
7045 		if(auto twp = cast(TabWidgetPage) child) {
7046 			super.addChild(child, pos);
7047 			if(pos == int.max)
7048 				pos = cast(int) this.children.length - 1;
7049 
7050 			if(pos != getCurrentTab) {
7051 				child.showing = false;
7052 			}
7053 		} else {
7054 			assert(0, "Don't add children directly to a page widget, instead add them to a page (see addPage)");
7055 		}
7056 	}
7057 
7058 	override void recomputeChildLayout() {
7059 		this.registerMovement();
7060 		foreach(child; children) {
7061 			child.x = 0;
7062 			child.y = 0;
7063 			child.width = width;
7064 			child.height = height;
7065 			child.recomputeChildLayout();
7066 		}
7067 	}
7068 
7069 	private int currentTab_;
7070 
7071 	///
7072 	@scriptable
7073 	void setCurrentTab(int item) {
7074 		currentTab_ = item;
7075 
7076 		showOnly(item);
7077 	}
7078 
7079 	///
7080 	@scriptable
7081 	int getCurrentTab() {
7082 		return currentTab_;
7083 	}
7084 
7085 	///
7086 	@scriptable
7087 	void removeTab(int item) {
7088 		if(item && item == getCurrentTab())
7089 			setCurrentTab(item - 1);
7090 
7091 		for(int a = item; a < children.length - 1; a++)
7092 			this._children[a] = this._children[a + 1];
7093 		this._children = this._children[0 .. $-1];
7094 	}
7095 
7096 	///
7097 	@scriptable
7098 	TabWidgetPage addPage(string title) {
7099 		return new TabWidgetPage(title, this);
7100 	}
7101 
7102 	private void showOnly(int item) {
7103 		foreach(idx, child; children)
7104 			if(idx == item) {
7105 				child.show();
7106 				child.recomputeChildLayout();
7107 			} else {
7108 				child.hide();
7109 			}
7110 	}
7111 
7112 }
7113 
7114 /++
7115 
7116 +/
7117 class TabWidgetPage : Widget {
7118 	string title;
7119 	this(string title, Widget parent) {
7120 		this.title = title;
7121 		this.tabStop = false;
7122 		super(parent);
7123 
7124 		///*
7125 		version(win32_widgets) {
7126 			createWin32Window(this, Win32Class!"arsd_minigui_TabWidgetPage"w, "", 0);
7127 		}
7128 		//*/
7129 	}
7130 
7131 	override int minHeight() {
7132 		int sum = 0;
7133 		foreach(child; children)
7134 			sum += child.minHeight();
7135 		return sum;
7136 	}
7137 }
7138 
7139 version(none)
7140 /++
7141 	A collapsable sidebar is a container that shows if its assigned width is greater than its minimum and otherwise shows as a button.
7142 
7143 	I think I need to modify the layout algorithms to support this.
7144 +/
7145 class CollapsableSidebar : Widget {
7146 
7147 }
7148 
7149 /// Stacks the widgets vertically, taking all the available width for each child.
7150 class VerticalLayout : Layout {
7151 	// most of this is intentionally blank - widget's default is vertical layout right now
7152 	///
7153 	this(Widget parent) { super(parent); }
7154 
7155 	/++
7156 		Sets a max width for the layout so you don't have to subclass. The max width
7157 		is in device-independent pixels, meaning pixels at 96 dpi that are auto-scaled.
7158 
7159 		History:
7160 			Added November 29, 2021 (dub v10.5)
7161 	+/
7162 	this(int maxWidth, Widget parent) {
7163 		this.mw = maxWidth;
7164 		super(parent);
7165 	}
7166 
7167 	private int mw = int.max;
7168 
7169 	override int maxWidth() { return scaleWithDpi(mw); }
7170 }
7171 
7172 /// Stacks the widgets horizontally, taking all the available height for each child.
7173 class HorizontalLayout : Layout {
7174 	///
7175 	this(Widget parent) { super(parent); }
7176 
7177 	/++
7178 		Sets a max height for the layout so you don't have to subclass. The max height
7179 		is in device-independent pixels, meaning pixels at 96 dpi that are auto-scaled.
7180 
7181 		History:
7182 			Added November 29, 2021 (dub v10.5)
7183 	+/
7184 	this(int maxHeight, Widget parent) {
7185 		this.mh = maxHeight;
7186 		super(parent);
7187 	}
7188 
7189 	private int mh = 0;
7190 
7191 
7192 
7193 	override void recomputeChildLayout() {
7194 		.recomputeChildLayout!"width"(this);
7195 	}
7196 
7197 	override int minHeight() {
7198 		int largest = 0;
7199 		int margins = 0;
7200 		int lastMargin = 0;
7201 		foreach(child; children) {
7202 			auto mh = child.minHeight();
7203 			if(mh > largest)
7204 				largest = mh;
7205 			margins += mymax(lastMargin, child.marginTop());
7206 			lastMargin = child.marginBottom();
7207 		}
7208 		return largest + margins;
7209 	}
7210 
7211 	override int maxHeight() {
7212 		if(mh != 0)
7213 			return mymax(minHeight, scaleWithDpi(mh));
7214 
7215 		int largest = 0;
7216 		int margins = 0;
7217 		int lastMargin = 0;
7218 		foreach(child; children) {
7219 			auto mh = child.maxHeight();
7220 			if(mh == int.max)
7221 				return int.max;
7222 			if(mh > largest)
7223 				largest = mh;
7224 			margins += mymax(lastMargin, child.marginTop());
7225 			lastMargin = child.marginBottom();
7226 		}
7227 		return largest + margins;
7228 	}
7229 
7230 	override int heightStretchiness() {
7231 		int max;
7232 		foreach(child; children) {
7233 			auto c = child.heightStretchiness;
7234 			if(c > max)
7235 				max = c;
7236 		}
7237 		return max;
7238 	}
7239 
7240 }
7241 
7242 version(win32_widgets)
7243 private
7244 extern(Windows)
7245 LRESULT DoubleBufferWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) nothrow {
7246 	Widget* pwin = hwnd in Widget.nativeMapping;
7247 	if(pwin is null)
7248 		return DefWindowProc(hwnd, message, wparam, lparam);
7249 	SimpleWindow win = pwin.simpleWindowWrappingHwnd;
7250 	if(win is null)
7251 		return DefWindowProc(hwnd, message, wparam, lparam);
7252 
7253 	switch(message) {
7254 		case WM_SIZE:
7255 			auto width = LOWORD(lparam);
7256 			auto height = HIWORD(lparam);
7257 
7258 			auto hdc = GetDC(hwnd);
7259 			auto hdcBmp = CreateCompatibleDC(hdc);
7260 
7261 			// FIXME: could this be more efficient? it never relinquishes a large bitmap
7262 			if(width > win.bmpWidth || height > win.bmpHeight) {
7263 				auto oldBuffer = win.buffer;
7264 				win.buffer = CreateCompatibleBitmap(hdc, width, height);
7265 
7266 				if(oldBuffer)
7267 					DeleteObject(oldBuffer);
7268 
7269 				win.bmpWidth = width;
7270 				win.bmpHeight = height;
7271 			}
7272 
7273 			// just always erase it upon resizing so minigui can draw over with a clean slate
7274 			auto oldBmp = SelectObject(hdcBmp, win.buffer);
7275 
7276 			auto brush = GetSysColorBrush(COLOR_3DFACE);
7277 			RECT r;
7278 			r.left = 0;
7279 			r.top = 0;
7280 			r.right = width;
7281 			r.bottom = height;
7282 			FillRect(hdcBmp, &r, brush);
7283 
7284 			SelectObject(hdcBmp, oldBmp);
7285 			DeleteDC(hdcBmp);
7286 			ReleaseDC(hwnd, hdc);
7287 		break;
7288 		case WM_PAINT:
7289 			if(win.buffer is null)
7290 				goto default;
7291 
7292 			BITMAP bm;
7293 			PAINTSTRUCT ps;
7294 
7295 			HDC hdc = BeginPaint(hwnd, &ps);
7296 
7297 			HDC hdcMem = CreateCompatibleDC(hdc);
7298 			HBITMAP hbmOld = SelectObject(hdcMem, win.buffer);
7299 
7300 			GetObject(win.buffer, bm.sizeof, &bm);
7301 
7302 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
7303 
7304 			SelectObject(hdcMem, hbmOld);
7305 			DeleteDC(hdcMem);
7306 			EndPaint(hwnd, &ps);
7307 		break;
7308 		default:
7309 			return DefWindowProc(hwnd, message, wparam, lparam);
7310 	}
7311 
7312 	return 0;
7313 }
7314 
7315 private wstring Win32Class(wstring name)() {
7316 	static bool classRegistered;
7317 	if(!classRegistered) {
7318 		HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
7319 		WNDCLASSEX wc;
7320 		wc.cbSize = wc.sizeof;
7321 		wc.hInstance = hInstance;
7322 		wc.hbrBackground = cast(HBRUSH) (COLOR_3DFACE+1); // GetStockObject(WHITE_BRUSH);
7323 		wc.lpfnWndProc = &DoubleBufferWndProc;
7324 		wc.lpszClassName = name.ptr;
7325 		if(!RegisterClassExW(&wc))
7326 			throw new Exception("RegisterClass ");// ~ to!string(GetLastError()));
7327 		classRegistered = true;
7328 	}
7329 
7330 		return name;
7331 }
7332 
7333 /+
7334 version(win32_widgets)
7335 extern(Windows)
7336 private
7337 LRESULT CustomDrawWindowProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
7338 	switch(iMessage) {
7339 		case WM_PAINT:
7340 			if(auto te = hWnd in Widget.nativeMapping) {
7341 				try {
7342 					//te.redraw();
7343 					import std.stdio; writeln(te, " drawing");
7344 				} catch(Exception) {}
7345 			}
7346 			return DefWindowProc(hWnd, iMessage, wParam, lParam);
7347 		default:
7348 			return DefWindowProc(hWnd, iMessage, wParam, lParam);
7349 	}
7350 }
7351 +/
7352 
7353 
7354 /++
7355 	A widget specifically designed to hold other widgets.
7356 
7357 	History:
7358 		Added July 1, 2021
7359 +/
7360 class ContainerWidget : Widget {
7361 	this(Widget parent) {
7362 		super(parent);
7363 		this.tabStop = false;
7364 
7365 		version(win32_widgets) {
7366 			createWin32Window(this, Win32Class!"arsd_minigui_ContainerWidget"w, "", 0);
7367 		}
7368 	}
7369 }
7370 
7371 /++
7372 	A widget that takes your widget, puts scroll bars around it, and sends
7373 	messages to it when the user scrolls. Unlike [ScrollableWidget], it makes
7374 	no effort to automatically scroll or clip its child widgets - it just sends
7375 	the messages.
7376 
7377 
7378 	A ScrollMessageWidget notifies you with a [ScrollEvent] that it has changed.
7379 	The scroll coordinates are all given in a unit you interpret as you wish. One
7380 	of these units is moved on each press of the arrow buttons and represents the
7381 	smallest amount the user can scroll. The intention is for this to be one line,
7382 	one item in a list, one row in a table, etc. Whatever makes sense for your widget
7383 	in each direction that the user might be interested in.
7384 
7385 	You can set a "page size" with the [step] property. (Yes, I regret the name...)
7386 	This is the amount it jumps when the user pressed page up and page down, or clicks
7387 	in the exposed part of the scroll bar.
7388 
7389 	You should add child content to the ScrollMessageWidget. However, it is important to
7390 	note that the coordinates are always independent of the scroll position! It is YOUR
7391 	responsibility to do any necessary transforms, clipping, etc., while drawing the
7392 	content and interpreting mouse events if they are supposed to change with the scroll.
7393 	This is in contrast to the (likely to be deprecated) [ScrollableWidget], which tries
7394 	to maintain the illusion that there's an infinite space. The [ScrollMessageWidget] gives
7395 	you more control (which can be considerably more efficient and adapted to your actual data)
7396 	at the expense of you also needing to be aware of its reality.
7397 
7398 	Please note that it does NOT react to mouse wheel events or various keyboard events as of
7399 	version 10.3. Maybe this will change in the future.... but for now you must call
7400 	[addDefaultKeyboardListeners] and/or [addDefaultWheelListeners] or set something up yourself.
7401 +/
7402 class ScrollMessageWidget : Widget {
7403 	this(Widget parent) {
7404 		super(parent);
7405 
7406 		container = new Widget(this);
7407 		hsb = new HorizontalScrollbar(this);
7408 		vsb = new VerticalScrollbar(this);
7409 
7410 		hsb.addEventListener("scrolltonextline", {
7411 			hsb.setPosition(hsb.position + movementPerButtonClickH_);
7412 			notify();
7413 		});
7414 		hsb.addEventListener("scrolltopreviousline", {
7415 			hsb.setPosition(hsb.position - movementPerButtonClickH_);
7416 			notify();
7417 		});
7418 		vsb.addEventListener("scrolltonextline", {
7419 			vsb.setPosition(vsb.position + movementPerButtonClickV_);
7420 			notify();
7421 		});
7422 		vsb.addEventListener("scrolltopreviousline", {
7423 			vsb.setPosition(vsb.position - movementPerButtonClickV_);
7424 			notify();
7425 		});
7426 		hsb.addEventListener("scrolltonextpage", {
7427 			hsb.setPosition(hsb.position + hsb.step_);
7428 			notify();
7429 		});
7430 		hsb.addEventListener("scrolltopreviouspage", {
7431 			hsb.setPosition(hsb.position - hsb.step_);
7432 			notify();
7433 		});
7434 		vsb.addEventListener("scrolltonextpage", {
7435 			vsb.setPosition(vsb.position + vsb.step_);
7436 			notify();
7437 		});
7438 		vsb.addEventListener("scrolltopreviouspage", {
7439 			vsb.setPosition(vsb.position - vsb.step_);
7440 			notify();
7441 		});
7442 		hsb.addEventListener("scrolltoposition", (Event event) {
7443 			hsb.setPosition(event.intValue);
7444 			notify();
7445 		});
7446 		vsb.addEventListener("scrolltoposition", (Event event) {
7447 			vsb.setPosition(event.intValue);
7448 			notify();
7449 		});
7450 
7451 
7452 		tabStop = false;
7453 		container.tabStop = false;
7454 		magic = true;
7455 	}
7456 
7457 	private int movementPerButtonClickH_ = 1;
7458 	private int movementPerButtonClickV_ = 1;
7459 	public void movementPerButtonClick(int h, int v) {
7460 		movementPerButtonClickH_ = h;
7461 		movementPerButtonClickV_ = v;
7462 	}
7463 
7464 	/++
7465 		Add default event listeners for keyboard and mouse wheel scrolling shortcuts.
7466 
7467 
7468 		The defaults for [addDefaultWheelListeners] are:
7469 
7470 			$(LIST
7471 				* Mouse wheel scrolls vertically
7472 				* Alt key + mouse wheel scrolls horiontally
7473 				* Shift + mouse wheel scrolls faster.
7474 				* Any mouse click or wheel event will focus the inner widget if it has `tabStop = true`
7475 			)
7476 
7477 		The defaults for [addDefaultKeyboardListeners] are:
7478 
7479 			$(LIST
7480 				* Arrow keys scroll by the given amounts
7481 				* Shift+arrow keys scroll by the given amounts times the given shiftMultiplier
7482 				* Page up and down scroll by the vertical viewable area
7483 				* Home and end scroll to the start and end of the verticle viewable area.
7484 				* Alt + page up / page down / home / end will horizonally scroll instead of vertical.
7485 			)
7486 
7487 		My recommendation is to change the scroll amounts if you are scrolling by pixels, but otherwise keep them at one line.
7488 
7489 		Params:
7490 			horizontalArrowScrollAmount =
7491 			verticalArrowScrollAmount =
7492 			verticalWheelScrollAmount = how much should be scrolled vertically on each tick of the mouse wheel
7493 			horizontalWheelScrollAmount = how much should be scrolled horizontally when alt is held on each tick of the mouse wheel
7494 			shiftMultiplier = multiplies the scroll amount by this when shift is held
7495 	+/
7496 	void addDefaultKeyboardListeners(int verticalArrowScrollAmount = 1, int horizontalArrowScrollAmount = 1, int shiftMultiplier = 3) {
7497 		auto _this = this;
7498 
7499 		container.addEventListener((scope KeyDownEvent ke) {
7500 			switch(ke.key) {
7501 				case Key.Left:
7502 					_this.scrollLeft(horizontalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7503 				break;
7504 				case Key.Right:
7505 					_this.scrollRight(horizontalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7506 				break;
7507 				case Key.Up:
7508 					_this.scrollUp(verticalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7509 				break;
7510 				case Key.Down:
7511 					_this.scrollDown(verticalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7512 				break;
7513 				case Key.PageUp:
7514 					if(ke.altKey)
7515 						_this.scrollLeft(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7516 					else
7517 						_this.scrollUp(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7518 				break;
7519 				case Key.PageDown:
7520 					if(ke.altKey)
7521 						_this.scrollRight(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7522 					else
7523 						_this.scrollDown(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7524 				break;
7525 				case Key.Home:
7526 					if(ke.altKey)
7527 						_this.scrollLeft(short.max * 16);
7528 					else
7529 						_this.scrollUp(short.max * 16);
7530 				break;
7531 				case Key.End:
7532 					if(ke.altKey)
7533 						_this.scrollRight(short.max * 16);
7534 					else
7535 						_this.scrollDown(short.max * 16);
7536 				break;
7537 
7538 				default:
7539 					// ignore, not for us.
7540 			}
7541 
7542 		});
7543 	}
7544 
7545 	/// ditto
7546 	void addDefaultWheelListeners(int verticalWheelScrollAmount = 1, int horizontalWheelScrollAmount = 1, int shiftMultiplier = 3) {
7547 		auto _this = this;
7548 		container.addEventListener((scope ClickEvent ce) {
7549 
7550 			if(ce.target && ce.target.tabStop)
7551 				ce.target.focus();
7552 
7553 			// ctrl is reserved for the application
7554 			if(ce.ctrlKey)
7555 				return;
7556 
7557 			if(horizontalWheelScrollAmount == 0 && ce.altKey)
7558 				return;
7559 
7560 			if(shiftMultiplier == 0 && ce.shiftKey)
7561 				return;
7562 
7563 			if(ce.button == MouseButton.wheelDown) {
7564 				if(ce.altKey)
7565 					_this.scrollRight(horizontalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7566 				else
7567 					_this.scrollDown(verticalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7568 			} else if(ce.button == MouseButton.wheelUp) {
7569 				if(ce.altKey)
7570 					_this.scrollLeft(horizontalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7571 				else
7572 					_this.scrollUp(verticalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7573 			}
7574 		});
7575 	}
7576 
7577 	/++
7578 		Scrolls the given amount.
7579 
7580 		History:
7581 			The scroll up and down functions was here in the initial release of the class, but the `amount` parameter and left/right functions were added on September 28, 2021.
7582 	+/
7583 	void scrollUp(int amount = 1) {
7584 		vsb.setPosition(vsb.position - amount);
7585 		notify();
7586 	}
7587 	/// ditto
7588 	void scrollDown(int amount = 1) {
7589 		vsb.setPosition(vsb.position + amount);
7590 		notify();
7591 	}
7592 	/// ditto
7593 	void scrollLeft(int amount = 1) {
7594 		hsb.setPosition(hsb.position - amount);
7595 		notify();
7596 	}
7597 	/// ditto
7598 	void scrollRight(int amount = 1) {
7599 		hsb.setPosition(hsb.position + amount);
7600 		notify();
7601 	}
7602 
7603 	///
7604 	VerticalScrollbar verticalScrollBar() { return vsb; }
7605 	///
7606 	HorizontalScrollbar horizontalScrollBar() { return hsb; }
7607 
7608 	void notify() {
7609 		static bool insideNotify;
7610 
7611 		if(insideNotify)
7612 			return; // avoid the recursive call, even if it isn't strictly correct
7613 
7614 		insideNotify = true;
7615 		scope(exit) insideNotify = false;
7616 
7617 		this.emit!ScrollEvent();
7618 	}
7619 
7620 	mixin Emits!ScrollEvent;
7621 
7622 	///
7623 	Point position() {
7624 		return Point(hsb.position, vsb.position);
7625 	}
7626 
7627 	///
7628 	void setPosition(int x, int y) {
7629 		hsb.setPosition(x);
7630 		vsb.setPosition(y);
7631 	}
7632 
7633 	///
7634 	void setPageSize(int unitsX, int unitsY) {
7635 		hsb.setStep(unitsX);
7636 		vsb.setStep(unitsY);
7637 	}
7638 
7639 	/// Always call this BEFORE setViewableArea
7640 	void setTotalArea(int width, int height) {
7641 		hsb.setMax(width);
7642 		vsb.setMax(height);
7643 	}
7644 
7645 	/++
7646 		Always set the viewable area AFTER setitng the total area if you are going to change both.
7647 		NEVER call this from inside a scroll event. This includes through recomputeChildLayout.
7648 		If you need to do that, use [queueRecomputeChildLayout].
7649 	+/
7650 	void setViewableArea(int width, int height) {
7651 
7652 		// actually there IS A need to dothis cuz the max might have changed since then
7653 		//if(width == hsb.viewableArea_ && height == vsb.viewableArea_)
7654 			//return; // no need to do what is already done
7655 		hsb.setViewableArea(width);
7656 		vsb.setViewableArea(height);
7657 
7658 		bool needsNotify = false;
7659 
7660 		// FIXME: if at any point the rhs is outside the scrollbar, we need
7661 		// to reset to 0. but it should remember the old position in case the
7662 		// window resizes again, so it can kinda return ot where it was.
7663 		//
7664 		// so there's an inner position and a exposed position. the exposed one is always in bounds and thus may be (0,0)
7665 		if(width >= hsb.max) {
7666 			// there's plenty of room to display it all so we need to reset to zero
7667 			// FIXME: adjust so it matches the note above
7668 			hsb.setPosition(0);
7669 			needsNotify = true;
7670 		}
7671 		if(height >= vsb.max) {
7672 			// there's plenty of room to display it all so we need to reset to zero
7673 			// FIXME: adjust so it matches the note above
7674 			vsb.setPosition(0);
7675 			needsNotify = true;
7676 		}
7677 		if(needsNotify)
7678 			notify();
7679 	}
7680 
7681 	private bool magic;
7682 	override void addChild(Widget w, int position = int.max) {
7683 		if(magic)
7684 			container.addChild(w, position);
7685 		else
7686 			super.addChild(w, position);
7687 	}
7688 
7689 	override void recomputeChildLayout() {
7690 		if(hsb is null || vsb is null || container is null) return;
7691 
7692 		registerMovement();
7693 
7694 		enum BUTTON_SIZE = 16;
7695 
7696 		hsb.height = scaleWithDpi(BUTTON_SIZE); // FIXME? are tese 16s sane?
7697 		hsb.x = 0;
7698 		hsb.y = this.height - hsb.height;
7699 
7700 		vsb.width = scaleWithDpi(BUTTON_SIZE); // FIXME?
7701 		vsb.x = this.width - vsb.width;
7702 		vsb.y = 0;
7703 
7704 		auto vsb_width = vsb.showing ? vsb.width : 0;
7705 		auto hsb_height = hsb.showing ? hsb.height : 0;
7706 
7707 		hsb.width = this.width - vsb_width;
7708 		vsb.height = this.height - hsb_height;
7709 
7710 		hsb.recomputeChildLayout();
7711 		vsb.recomputeChildLayout();
7712 
7713 		if(this.header is null) {
7714 			container.x = 0;
7715 			container.y = 0;
7716 			container.width = this.width - vsb_width;
7717 			container.height = this.height - hsb_height;
7718 			container.recomputeChildLayout();
7719 		} else {
7720 			header.x = 0;
7721 			header.y = 0;
7722 			header.width = this.width - vsb_width;
7723 			header.height = scaleWithDpi(BUTTON_SIZE); // size of the button
7724 			header.recomputeChildLayout();
7725 
7726 			container.x = 0;
7727 			container.y = scaleWithDpi(BUTTON_SIZE);
7728 			container.width = this.width - vsb_width;
7729 			container.height = this.height - hsb_height - scaleWithDpi(BUTTON_SIZE);
7730 			container.recomputeChildLayout();
7731 		}
7732 	}
7733 
7734 	private HorizontalScrollbar hsb;
7735 	private VerticalScrollbar vsb;
7736 	Widget container;
7737 	private Widget header;
7738 
7739 	/++
7740 		Adds a fixed-size "header" widget. This will be positioned to align with the scroll up button.
7741 
7742 		History:
7743 			Added September 27, 2021 (dub v10.3)
7744 	+/
7745 	Widget getHeader() {
7746 		if(this.header is null) {
7747 			magic = false;
7748 			scope(exit) magic = true;
7749 			this.header = new Widget(this);
7750 			recomputeChildLayout();
7751 		}
7752 		return this.header;
7753 	}
7754 
7755 	/++
7756 		Makes an effort to ensure as much of `rect` is visible as possible, scrolling if necessary.
7757 
7758 		History:
7759 			Added January 3, 2023 (dub v11.0)
7760 	+/
7761 	void scrollIntoView(Rectangle rect) {
7762 		Rectangle viewRectangle = Rectangle(position, Size(hsb.viewableArea_, vsb.viewableArea_));
7763 
7764 		// import std.stdio; writeln(viewRectangle, " ", rect, " ", viewRectangle.contains(rect.lowerRight));
7765 
7766 		if(!viewRectangle.contains(rect.lowerRight))
7767 			setPosition(rect.upperLeft.tupleof);
7768 
7769 	}
7770 
7771 	override int minHeight() {
7772 		int min = container ? container.minHeight : 0;
7773 		if(header !is null)
7774 			min += header.minHeight;
7775 		if(horizontalScrollBar.showing)
7776 			min += horizontalScrollBar.minHeight;
7777 		return min;
7778 	}
7779 
7780 	override int maxHeight() {
7781 		int max = container ? container.maxHeight : int.max;
7782 		if(max == int.max)
7783 			return max;
7784 		if(horizontalScrollBar.showing)
7785 			max += horizontalScrollBar.minHeight;
7786 		return max;
7787 	}
7788 }
7789 
7790 /++
7791 	$(IMG //arsdnet.net/minigui-screenshots/windows/ScrollMessageWidget.png, A box saying "baby will" with three round buttons inside it for the options of "eat", "cry", and "sleep")
7792 	$(IMG //arsdnet.net/minigui-screenshots/linux/ScrollMessageWidget.png, Same thing, but in the default Linux theme.)
7793 +/
7794 version(minigui_screenshots)
7795 @Screenshot("ScrollMessageWidget")
7796 unittest {
7797 	auto window = new Window("ScrollMessageWidget");
7798 
7799 	auto smw = new ScrollMessageWidget(window);
7800 	smw.addDefaultKeyboardListeners();
7801 	smw.addDefaultWheelListeners();
7802 
7803 	window.loop();
7804 }
7805 
7806 /++
7807 	Bypasses automatic layout for its children, using manual positioning and sizing only.
7808 	While you need to manually position them, you must ensure they are inside the StaticLayout's
7809 	bounding box to avoid undefined behavior.
7810 
7811 	You should almost never use this.
7812 +/
7813 class StaticLayout : Layout {
7814 	///
7815 	this(Widget parent) { super(parent); }
7816 	override void recomputeChildLayout() {
7817 		registerMovement();
7818 		foreach(child; children)
7819 			child.recomputeChildLayout();
7820 	}
7821 }
7822 
7823 /++
7824 	Bypasses automatic positioning when being laid out. It is your responsibility to make
7825 	room for this widget in the parent layout.
7826 
7827 	Its children are laid out normally, unless there is exactly one, in which case it takes
7828 	on the full size of the `StaticPosition` object (if you plan to put stuff on the edge, you
7829 	can do that with `padding`).
7830 +/
7831 class StaticPosition : Layout {
7832 	///
7833 	this(Widget parent) { super(parent); }
7834 
7835 	override void recomputeChildLayout() {
7836 		registerMovement();
7837 		if(this.children.length == 1) {
7838 			auto child = children[0];
7839 			child.x = 0;
7840 			child.y = 0;
7841 			child.width = this.width;
7842 			child.height = this.height;
7843 			child.recomputeChildLayout();
7844 		} else
7845 		foreach(child; children)
7846 			child.recomputeChildLayout();
7847 	}
7848 
7849 	alias width = typeof(super).width;
7850 	alias height = typeof(super).height;
7851 
7852 	@property int width(int w) @nogc pure @safe nothrow {
7853 		return this._width = w;
7854 	}
7855 
7856 	@property int height(int w) @nogc pure @safe nothrow {
7857 		return this._height = w;
7858 	}
7859 
7860 }
7861 
7862 /++
7863 	FixedPosition is like [StaticPosition], but its coordinates
7864 	are always relative to the viewport, meaning they do not scroll with
7865 	the parent content.
7866 +/
7867 class FixedPosition : StaticPosition {
7868 	///
7869 	this(Widget parent) { super(parent); }
7870 }
7871 
7872 version(win32_widgets)
7873 int processWmCommand(HWND parentWindow, HWND handle, ushort cmd, ushort idm) {
7874 	if(true) {
7875 		// cmd == 0 = menu, cmd == 1 = accelerator
7876 		if(auto item = idm in Action.mapping) {
7877 			foreach(handler; (*item).triggered)
7878 				handler();
7879 		/*
7880 			auto event = new Event("triggered", *item);
7881 			event.button = idm;
7882 			event.dispatch();
7883 		*/
7884 			return 0;
7885 		}
7886 	}
7887 	if(handle)
7888 	if(auto widgetp = handle in Widget.nativeMapping) {
7889 		(*widgetp).handleWmCommand(cmd, idm);
7890 		return 0;
7891 	}
7892 	return 1;
7893 }
7894 
7895 
7896 ///
7897 class Window : Widget {
7898 	int mouseCaptureCount = 0;
7899 	Widget mouseCapturedBy;
7900 	void captureMouse(Widget byWhom) {
7901 		assert(mouseCapturedBy is null || byWhom is mouseCapturedBy);
7902 		mouseCaptureCount++;
7903 		mouseCapturedBy = byWhom;
7904 		win.grabInput();
7905 	}
7906 	void releaseMouseCapture() {
7907 		mouseCaptureCount--;
7908 		mouseCapturedBy = null;
7909 		win.releaseInputGrab();
7910 	}
7911 
7912 	/++
7913 		Sets the window icon which is often seen in title bars and taskbars.
7914 
7915 		History:
7916 			Added April 5, 2022 (dub v10.8)
7917 	+/
7918 	@property void icon(MemoryImage icon) {
7919 		if(win && icon)
7920 			win.icon = icon;
7921 	}
7922 
7923 	///
7924 	@scriptable
7925 	@property bool focused() {
7926 		return win.focused;
7927 	}
7928 
7929 	static class Style : Widget.Style {
7930 		override WidgetBackground background() {
7931 			version(custom_widgets)
7932 				return WidgetBackground(WidgetPainter.visualTheme.windowBackgroundColor);
7933 			else version(win32_widgets)
7934 				return WidgetBackground(Color.transparent);
7935 			else static assert(0);
7936 		}
7937 	}
7938 	mixin OverrideStyle!Style;
7939 
7940 	/++
7941 		Gives the height of a line according to the default font. You should try to use your computed font instead of this, but until May 8, 2021, this was the only real option.
7942 	+/
7943 	static int lineHeight() {
7944 		OperatingSystemFont font;
7945 		if(auto vt = WidgetPainter.visualTheme) {
7946 			font = vt.defaultFontCached();
7947 		}
7948 
7949 		if(font is null) {
7950 			static int defaultHeightCache;
7951 			if(defaultHeightCache == 0) {
7952 				font = new OperatingSystemFont;
7953 				font.loadDefault;
7954 				defaultHeightCache = font.height();// * 5 / 4;
7955 			}
7956 			return defaultHeightCache;
7957 		}
7958 
7959 		return font.height();// * 5 / 4;
7960 	}
7961 
7962 	Widget focusedWidget;
7963 
7964 	private SimpleWindow win_;
7965 
7966 	@property {
7967 		/++
7968 			Provides access to the underlying [SimpleWindow]. Note that changing properties on this window may disconnect minigui's event dispatchers.
7969 
7970 			History:
7971 				Prior to June 21, 2021, it was a public (but undocumented) member. Now it a semi-protected property.
7972 		+/
7973 		public SimpleWindow win() {
7974 			return win_;
7975 		}
7976 		///
7977 		protected void win(SimpleWindow w) {
7978 			win_ = w;
7979 		}
7980 	}
7981 
7982 	/// YOU ALMOST CERTAINLY SHOULD NOT USE THIS. This is really only for special purposes like pseudowindows or popup windows doing their own thing.
7983 	this(Widget p) {
7984 		tabStop = false;
7985 		super(p);
7986 	}
7987 
7988 	private void actualRedraw() {
7989 		if(recomputeChildLayoutRequired)
7990 			recomputeChildLayoutEntry();
7991 		if(!showing) return;
7992 
7993 		assert(parentWindow !is null);
7994 
7995 		auto w = drawableWindow;
7996 		if(w is null)
7997 			w = parentWindow.win;
7998 
7999 		if(w.closed())
8000 			return;
8001 
8002 		auto ugh = this.parent;
8003 		int lox, loy;
8004 		while(ugh) {
8005 			lox += ugh.x;
8006 			loy += ugh.y;
8007 			ugh = ugh.parent;
8008 		}
8009 		auto painter = w.draw(true);
8010 		privatePaint(WidgetPainter(painter, this), lox, loy, Rectangle(0, 0, int.max, int.max), false, willDraw());
8011 		// RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
8012 	}
8013 
8014 
8015 	private bool skipNextChar = false;
8016 
8017 	/++
8018 		Creates a window from an existing [SimpleWindow]. This constructor attaches various event handlers to the SimpleWindow object which may overwrite your existing handlers.
8019 
8020 		This constructor is intended primarily for internal use and may be changed to `protected` later.
8021 	+/
8022 	this(SimpleWindow win) {
8023 
8024 		static if(UsingSimpledisplayX11) {
8025 			win.discardAdditionalConnectionState = &discardXConnectionState;
8026 			win.recreateAdditionalConnectionState = &recreateXConnectionState;
8027 		}
8028 
8029 		tabStop = false;
8030 		super(null);
8031 		this.win = win;
8032 
8033 		win.addEventListener((Widget.RedrawEvent) {
8034 			if(win.eventQueued!RecomputeEvent) {
8035 				// import std.stdio; writeln("skipping");
8036 				return; // let the recompute event do the actual redraw
8037 			}
8038 			this.actualRedraw();
8039 		});
8040 
8041 		win.addEventListener((Widget.RecomputeEvent) {
8042 			recomputeChildLayoutEntry();
8043 			if(win.eventQueued!RedrawEvent)
8044 				return; // let the queued one do it
8045 			else {
8046 				// import std.stdio; writeln("drawing");
8047 				this.actualRedraw(); // if not queued, it needs to be done now anyway
8048 			}
8049 		});
8050 
8051 		this.width = win.width;
8052 		this.height = win.height;
8053 		this.parentWindow = this;
8054 
8055 		win.closeQuery = () {
8056 			if(this.emit!ClosingEvent())
8057 				win.close();
8058 		};
8059 		win.onClosing = () {
8060 			this.emit!ClosedEvent();
8061 		};
8062 
8063 		win.windowResized = (int w, int h) {
8064 			this.width = w;
8065 			this.height = h;
8066 			recomputeChildLayout();
8067 			// this causes a HUGE performance problem for no apparent benefit, hence the commenting
8068 			//version(win32_widgets)
8069 				//InvalidateRect(hwnd, null, true);
8070 			redraw();
8071 		};
8072 
8073 		win.onFocusChange = (bool getting) {
8074 			if(this.focusedWidget) {
8075 				if(getting) {
8076 					this.focusedWidget.emit!FocusEvent();
8077 					this.focusedWidget.emit!FocusInEvent();
8078 				} else {
8079 					this.focusedWidget.emit!BlurEvent();
8080 					this.focusedWidget.emit!FocusOutEvent();
8081 				}
8082 			}
8083 
8084 			if(getting) {
8085 				this.emit!FocusEvent();
8086 				this.emit!FocusInEvent();
8087 			} else {
8088 				this.emit!BlurEvent();
8089 				this.emit!FocusOutEvent();
8090 			}
8091 		};
8092 
8093 		win.onDpiChanged = {
8094 			this.queueRecomputeChildLayout();
8095 			auto event = new DpiChangedEvent(this);
8096 			event.sendDirectly();
8097 
8098 			privateDpiChanged();
8099 		};
8100 
8101 		win.setEventHandlers(
8102 			(MouseEvent e) {
8103 				dispatchMouseEvent(e);
8104 			},
8105 			(KeyEvent e) {
8106 				//import std.stdio;
8107 				//writefln("%x   %s", cast(uint) e.key, e.key);
8108 				dispatchKeyEvent(e);
8109 			},
8110 			(dchar e) {
8111 				if(e == 13) e = 10; // hack?
8112 				if(e == 127) return; // linux sends this, windows doesn't. we don't want it.
8113 				dispatchCharEvent(e);
8114 			},
8115 		);
8116 
8117 		addEventListener("char", (Widget, Event ev) {
8118 			if(skipNextChar) {
8119 				ev.preventDefault();
8120 				skipNextChar = false;
8121 			}
8122 		});
8123 
8124 		version(win32_widgets)
8125 		win.handleNativeEvent = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
8126 			if(hwnd !is this.win.impl.hwnd)
8127 				return 1; // we don't care... pass it on
8128 			auto ret = WindowProcedureHelper(this, hwnd, msg, wParam, lParam, mustReturn);
8129 			if(mustReturn)
8130 				return ret;
8131 			return 1; // pass it on
8132 		};
8133 
8134 		if(Window.newWindowCreated)
8135 			Window.newWindowCreated(this);
8136 	}
8137 
8138 	version(custom_widgets)
8139 	override void defaultEventHandler_click(ClickEvent event) {
8140 		if(event.target && event.target.tabStop)
8141 			event.target.focus();
8142 	}
8143 
8144 	private static void delegate(Window) newWindowCreated;
8145 
8146 	version(win32_widgets)
8147 	override void paint(WidgetPainter painter) {
8148 		/*
8149 		RECT rect;
8150 		rect.right = this.width;
8151 		rect.bottom = this.height;
8152 		DrawThemeBackground(theme, painter.impl.hdc, 4, 1, &rect, null);
8153 		*/
8154 		// 3dface is used as window backgrounds by Windows too, so that's why I'm using it here
8155 		auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
8156 		auto p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
8157 		// since the pen is null, to fill the whole space, we need the +1 on both.
8158 		gdi.Rectangle(painter.impl.hdc, 0, 0, this.width + 1, this.height + 1);
8159 		SelectObject(painter.impl.hdc, p);
8160 		SelectObject(painter.impl.hdc, b);
8161 	}
8162 	version(custom_widgets)
8163 	override void paint(WidgetPainter painter) {
8164 		auto cs = getComputedStyle();
8165 		painter.fillColor = cs.windowBackgroundColor;
8166 		painter.outlineColor = cs.windowBackgroundColor;
8167 		painter.drawRectangle(Point(0, 0), this.width, this.height);
8168 	}
8169 
8170 
8171 	override void defaultEventHandler_keydown(KeyDownEvent event) {
8172 		Widget _this = event.target;
8173 
8174 		if(event.key == Key.Tab) {
8175 			/* Window tab ordering is a recursive thingy with each group */
8176 
8177 			// FIXME inefficient
8178 			Widget[] helper(Widget p) {
8179 				if(p.hidden)
8180 					return null;
8181 				Widget[] childOrdering;
8182 
8183 				auto children = p.children.dup;
8184 
8185 				while(true) {
8186 					// UIs should be generally small, so gonna brute force it a little
8187 					// note that it must be a stable sort here; if all are index 0, it should be in order of declaration
8188 
8189 					Widget smallestTab;
8190 					foreach(ref c; children) {
8191 						if(c is null) continue;
8192 						if(smallestTab is null || c.tabOrder < smallestTab.tabOrder) {
8193 							smallestTab = c;
8194 							c = null;
8195 						}
8196 					}
8197 					if(smallestTab !is null) {
8198 						if(smallestTab.tabStop && !smallestTab.hidden)
8199 							childOrdering ~= smallestTab;
8200 						if(!smallestTab.hidden)
8201 							childOrdering ~= helper(smallestTab);
8202 					} else
8203 						break;
8204 
8205 				}
8206 
8207 				return childOrdering;
8208 			}
8209 
8210 			Widget[] tabOrdering = helper(this);
8211 
8212 			Widget recipient;
8213 
8214 			if(tabOrdering.length) {
8215 				bool seenThis = false;
8216 				Widget previous;
8217 				foreach(idx, child; tabOrdering) {
8218 					if(child is focusedWidget) {
8219 
8220 						if(event.shiftKey) {
8221 							if(idx == 0)
8222 								recipient = tabOrdering[$-1];
8223 							else
8224 								recipient = tabOrdering[idx - 1];
8225 							break;
8226 						}
8227 
8228 						seenThis = true;
8229 						if(idx + 1 == tabOrdering.length) {
8230 							// we're at the end, either move to the next group
8231 							// or start back over
8232 							recipient = tabOrdering[0];
8233 						}
8234 						continue;
8235 					}
8236 					if(seenThis) {
8237 						recipient = child;
8238 						break;
8239 					}
8240 					previous = child;
8241 				}
8242 			}
8243 
8244 			if(recipient !is null) {
8245 				// import std.stdio; writeln(typeid(recipient));
8246 				recipient.focus();
8247 
8248 				skipNextChar = true;
8249 			}
8250 		}
8251 
8252 		debug if(event.key == Key.F12) {
8253 			if(devTools) {
8254 				devTools.close();
8255 				devTools = null;
8256 			} else {
8257 				devTools = new DevToolWindow(this);
8258 				devTools.show();
8259 			}
8260 		}
8261 	}
8262 
8263 	debug DevToolWindow devTools;
8264 
8265 
8266 	/++
8267 		Creates a window. Please note windows are created in a hidden state, so you must call [show] or [loop] to get it to display.
8268 
8269 		History:
8270 			Prior to May 12, 2021, the default title was "D Application" (simpledisplay.d's default). After that, the default is `Runtime.args[0]` instead.
8271 
8272 			The width and height arguments were added to the overload that takes `string` first on June 21, 2021.
8273 	+/
8274 	this(int width = 500, int height = 500, string title = null) {
8275 		if(title is null) {
8276 			import core.runtime;
8277 			if(Runtime.args.length)
8278 				title = Runtime.args[0];
8279 		}
8280 		win = new SimpleWindow(width, height, title, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.normal, WindowFlags.dontAutoShow | WindowFlags.managesChildWindowFocus);
8281 
8282 		static if(UsingSimpledisplayX11) {
8283 		///+
8284 		// for input proxy
8285 		auto display = XDisplayConnection.get;
8286 		auto inputProxy = XCreateSimpleWindow(display, win.window, -1, -1, 1, 1, 0, 0, 0);
8287 		XSelectInput(display, inputProxy, EventMask.KeyPressMask | EventMask.KeyReleaseMask | EventMask.FocusChangeMask);
8288 		XMapWindow(display, inputProxy);
8289 		//import std.stdio; writefln("input proxy: 0x%0x", inputProxy);
8290 		this.inputProxy = new SimpleWindow(inputProxy);
8291 
8292 		XEvent lastEvent;
8293 		this.inputProxy.handleNativeEvent = (XEvent ev) {
8294 			lastEvent = ev;
8295 			return 1;
8296 		};
8297 		this.inputProxy.setEventHandlers(
8298 			(MouseEvent e) {
8299 				dispatchMouseEvent(e);
8300 			},
8301 			(KeyEvent e) {
8302 				//import std.stdio;
8303 				//writefln("%x   %s", cast(uint) e.key, e.key);
8304 				if(dispatchKeyEvent(e)) {
8305 					// FIXME: i should trap error
8306 					if(auto nw = cast(NestedChildWindowWidget) focusedWidget) {
8307 						auto thing = nw.focusableWindow();
8308 						if(thing && thing.window) {
8309 							lastEvent.xkey.window = thing.window;
8310 							// import std.stdio; writeln("sending event ", lastEvent.xkey);
8311 							trapXErrors( {
8312 								XSendEvent(XDisplayConnection.get, thing.window, false, 0, &lastEvent);
8313 							});
8314 						}
8315 					}
8316 				}
8317 			},
8318 			(dchar e) {
8319 				if(e == 13) e = 10; // hack?
8320 				if(e == 127) return; // linux sends this, windows doesn't. we don't want it.
8321 				dispatchCharEvent(e);
8322 			},
8323 		);
8324 
8325 		this.inputProxy.populateXic();
8326 		// done
8327 		//+/
8328 		}
8329 
8330 
8331 
8332 		win.setRequestedInputFocus = &this.setRequestedInputFocus;
8333 
8334 		this(win);
8335 	}
8336 
8337 	SimpleWindow inputProxy;
8338 
8339 	private SimpleWindow setRequestedInputFocus() {
8340 		return inputProxy;
8341 	}
8342 
8343 	/// ditto
8344 	this(string title, int width = 500, int height = 500) {
8345 		this(width, height, title);
8346 	}
8347 
8348 	///
8349 	@property string title() { return parentWindow.win.title; }
8350 	///
8351 	@property void title(string title) { parentWindow.win.title = title; }
8352 
8353 	///
8354 	@scriptable
8355 	void close() {
8356 		win.close();
8357 		// I synchronize here upon window closing to ensure all child windows
8358 		// get updated too before the event loop. This avoids some random X errors.
8359 		static if(UsingSimpledisplayX11) {
8360 			runInGuiThread( {
8361 				XSync(XDisplayConnection.get, false);
8362 			});
8363 		}
8364 	}
8365 
8366 	bool dispatchKeyEvent(KeyEvent ev) {
8367 		auto wid = focusedWidget;
8368 		if(wid is null)
8369 			wid = this;
8370 		KeyEventBase event = ev.pressed ? new KeyDownEvent(wid) : new KeyUpEvent(wid);
8371 		event.originalKeyEvent = ev;
8372 		event.key = ev.key;
8373 		event.state = ev.modifierState;
8374 		event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false;
8375 		event.altKey = (ev.modifierState & ModifierState.alt) ? true : false;
8376 		event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false;
8377 		event.dispatch();
8378 
8379 		return !event.propagationStopped;
8380 	}
8381 
8382 	// returns true if propagation should continue into nested things.... prolly not a great thing to do.
8383 	bool dispatchCharEvent(dchar ch) {
8384 		if(focusedWidget) {
8385 			auto event = new CharEvent(focusedWidget, ch);
8386 			event.dispatch();
8387 			return !event.propagationStopped;
8388 		}
8389 		return true;
8390 	}
8391 
8392 	Widget mouseLastOver;
8393 	Widget mouseLastDownOn;
8394 	bool lastWasDoubleClick;
8395 	bool dispatchMouseEvent(MouseEvent ev) {
8396 		auto eleR = widgetAtPoint(this, ev.x, ev.y);
8397 		auto ele = eleR.widget;
8398 
8399 		auto captureEle = ele;
8400 
8401 		if(mouseCapturedBy !is null) {
8402 			if(ele !is mouseCapturedBy && !mouseCapturedBy.isAParentOf(ele))
8403 				captureEle = mouseCapturedBy;
8404 		}
8405 
8406 		// a hack to get it relative to the widget.
8407 		eleR.x = ev.x;
8408 		eleR.y = ev.y;
8409 		auto pain = captureEle;
8410 		while(pain) {
8411 			eleR.x -= pain.x;
8412 			eleR.y -= pain.y;
8413 			pain.addScrollPosition(eleR.x, eleR.y);
8414 			pain = pain.parent;
8415 		}
8416 
8417 		void populateMouseEventBase(MouseEventBase event) {
8418 			event.button = ev.button;
8419 			event.buttonLinear = ev.buttonLinear;
8420 			event.state = ev.modifierState;
8421 			event.clientX = eleR.x;
8422 			event.clientY = eleR.y;
8423 
8424 			event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false;
8425 			event.altKey = (ev.modifierState & ModifierState.alt) ? true : false;
8426 			event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false;
8427 		}
8428 
8429 		if(ev.type == MouseEventType.buttonPressed) {
8430 			{
8431 				auto event = new MouseDownEvent(captureEle);
8432 				populateMouseEventBase(event);
8433 				event.dispatch();
8434 			}
8435 
8436 			if(ev.button != MouseButton.wheelDown && ev.button != MouseButton.wheelUp && mouseLastDownOn is ele && ev.doubleClick) {
8437 				auto event = new DoubleClickEvent(captureEle);
8438 				populateMouseEventBase(event);
8439 				event.dispatch();
8440 				lastWasDoubleClick = ev.doubleClick;
8441 			} else {
8442 				lastWasDoubleClick = false;
8443 			}
8444 
8445 			mouseLastDownOn = ele;
8446 		} else if(ev.type == MouseEventType.buttonReleased) {
8447 			{
8448 				auto event = new MouseUpEvent(captureEle);
8449 				populateMouseEventBase(event);
8450 				event.dispatch();
8451 			}
8452 			if(!lastWasDoubleClick && mouseLastDownOn is ele) {
8453 				auto event = new ClickEvent(captureEle);
8454 				populateMouseEventBase(event);
8455 				event.dispatch();
8456 			}
8457 		} else if(ev.type == MouseEventType.motion) {
8458 			// motion
8459 			{
8460 				auto event = new MouseMoveEvent(captureEle);
8461 				populateMouseEventBase(event); // fills in button which is meaningless but meh
8462 				event.dispatch();
8463 			}
8464 
8465 			if(mouseLastOver !is ele) {
8466 				if(ele !is null) {
8467 					if(!isAParentOf(ele, mouseLastOver)) {
8468 						ele.setDynamicState(DynamicState.hover, true);
8469 						auto event = new MouseEnterEvent(ele);
8470 						event.relatedTarget = mouseLastOver;
8471 						event.sendDirectly();
8472 
8473 						ele.useStyleProperties((scope Widget.Style s) {
8474 							ele.parentWindow.win.cursor = s.cursor;
8475 						});
8476 					}
8477 				}
8478 
8479 				if(mouseLastOver !is null) {
8480 					if(!isAParentOf(mouseLastOver, ele)) {
8481 						mouseLastOver.setDynamicState(DynamicState.hover, false);
8482 						auto event = new MouseLeaveEvent(mouseLastOver);
8483 						event.relatedTarget = ele;
8484 						event.sendDirectly();
8485 					}
8486 				}
8487 
8488 				if(ele !is null) {
8489 					auto event = new MouseOverEvent(ele);
8490 					event.relatedTarget = mouseLastOver;
8491 					event.dispatch();
8492 				}
8493 
8494 				if(mouseLastOver !is null) {
8495 					auto event = new MouseOutEvent(mouseLastOver);
8496 					event.relatedTarget = ele;
8497 					event.dispatch();
8498 				}
8499 
8500 				mouseLastOver = ele;
8501 			}
8502 		}
8503 
8504 		return true; // FIXME: the event default prevented?
8505 	}
8506 
8507 	/++
8508 		Shows the window and runs the application event loop.
8509 
8510 		Blocks until this window is closed.
8511 
8512 		Bugs:
8513 
8514 		$(PITFALL
8515 			You should always have one event loop live for your application.
8516 			If you make two windows in sequence, the second call to loop (or
8517 			simpledisplay's [SimpleWindow.eventLoop], upon which this is built)
8518 			might fail:
8519 
8520 			---
8521 			// don't do this!
8522 			auto window = new Window();
8523 			window.loop();
8524 
8525 			// or new Window or new MainWindow, all the same
8526 			auto window2 = new SimpleWindow();
8527 			window2.eventLoop(0); // problematic! might crash
8528 			---
8529 
8530 			simpledisplay's current implementation assumes that final cleanup is
8531 			done when the event loop refcount reaches zero. So after the first
8532 			eventLoop returns, when there isn't already another one active, it assumes
8533 			the program will exit soon and cleans up.
8534 
8535 			This is arguably a bug that it doesn't reinitialize, and I'll probably change
8536 			it eventually, but in the mean time, there's an easy solution:
8537 
8538 			---
8539 			// do this
8540 			EventLoop mainEventLoop = EventLoop.get; // just add this line
8541 
8542 			auto window = new Window();
8543 			window.loop();
8544 
8545 			// or any other type of Window etc.
8546 			auto window2 = new Window();
8547 			window2.loop(); // perfectly fine since mainEventLoop still alive
8548 			---
8549 
8550 			By adding a top-level reference to the event loop, it ensures the final cleanup
8551 			is not performed until it goes out of scope too, letting the individual window loops
8552 			work without trouble despite the bug.
8553 		)
8554 
8555 		History:
8556 			The [BlockingMode] parameter was added on December 8, 2021.
8557 			The default behavior is to block until the application quits
8558 			(so all windows have been closed), unless another minigui or
8559 			simpledisplay event loop is already running, in which case it
8560 			will block until this window closes specifically.
8561 	+/
8562 	@scriptable
8563 	void loop(BlockingMode bm = BlockingMode.automatic) {
8564 		if(win.closed)
8565 			return; // otherwise show will throw
8566 		show();
8567 		win.eventLoopWithBlockingMode(bm, 0);
8568 	}
8569 
8570 	private bool firstShow = true;
8571 
8572 	@scriptable
8573 	override void show() {
8574 		bool rd = false;
8575 		if(firstShow) {
8576 			firstShow = false;
8577 			recomputeChildLayout();
8578 			auto f = getFirstFocusable(this); // FIXME: autofocus?
8579 			if(f)
8580 				f.focus();
8581 			redraw();
8582 		}
8583 		win.show();
8584 		super.show();
8585 	}
8586 	@scriptable
8587 	override void hide() {
8588 		win.hide();
8589 		super.hide();
8590 	}
8591 
8592 	static Widget getFirstFocusable(Widget start) {
8593 		if(start is null)
8594 			return null;
8595 
8596 		foreach(widget; &start.focusableWidgets) {
8597 			return widget;
8598 		}
8599 
8600 		return null;
8601 	}
8602 
8603 	static Widget getLastFocusable(Widget start) {
8604 		if(start is null)
8605 			return null;
8606 
8607 		Widget last;
8608 		foreach(widget; &start.focusableWidgets) {
8609 			last = widget;
8610 		}
8611 
8612 		return last;
8613 	}
8614 
8615 
8616 	mixin Emits!ClosingEvent;
8617 	mixin Emits!ClosedEvent;
8618 }
8619 
8620 /++
8621 	History:
8622 		Added January 12, 2022
8623 +/
8624 class DpiChangedEvent : Event {
8625 	enum EventString = "dpichanged";
8626 
8627 	this(Widget target) {
8628 		super(EventString, target);
8629 	}
8630 }
8631 
8632 debug private class DevToolWindow : Window {
8633 	Window p;
8634 
8635 	TextEdit parentList;
8636 	TextEdit logWindow;
8637 	TextLabel clickX, clickY;
8638 
8639 	this(Window p) {
8640 		this.p = p;
8641 		super(400, 300, "Developer Toolbox");
8642 
8643 		logWindow = new TextEdit(this);
8644 		parentList = new TextEdit(this);
8645 
8646 		auto hl = new HorizontalLayout(this);
8647 		clickX = new TextLabel("", TextAlignment.Right, hl);
8648 		clickY = new TextLabel("", TextAlignment.Right, hl);
8649 
8650 		parentListeners ~= p.addEventListener("*", (Event ev) {
8651 			log(typeid(ev.source).name, " emitted ", typeid(ev).name);
8652 		});
8653 
8654 		parentListeners ~= p.addEventListener((ClickEvent ev) {
8655 			auto s = ev.srcElement;
8656 
8657 			string list;
8658 
8659 			void addInfo(Widget s) {
8660 				list ~= s.toString();
8661 				list ~= "\n\tminHeight: " ~ toInternal!string(s.minHeight);
8662 				list ~= "\n\tmaxHeight: " ~ toInternal!string(s.maxHeight);
8663 				list ~= "\n\theightStretchiness: " ~ toInternal!string(s.heightStretchiness);
8664 				list ~= "\n\theight: " ~ toInternal!string(s.height);
8665 				list ~= "\n\tmarginTop: " ~ toInternal!string(s.marginTop);
8666 				list ~= "\n\tmarginBottom: " ~ toInternal!string(s.marginBottom);
8667 			}
8668 
8669 			addInfo(s);
8670 
8671 			s = s.parent;
8672 			while(s) {
8673 				list ~= "\n";
8674 				addInfo(s);
8675 				s = s.parent;
8676 			}
8677 			parentList.content = list;
8678 
8679 			clickX.label = toInternal!string(ev.clientX);
8680 			clickY.label = toInternal!string(ev.clientY);
8681 		});
8682 	}
8683 
8684 	EventListener[] parentListeners;
8685 
8686 	override void close() {
8687 		assert(p !is null);
8688 		foreach(p; parentListeners)
8689 			p.disconnect();
8690 		parentListeners = null;
8691 		p.devTools = null;
8692 		p = null;
8693 		super.close();
8694 	}
8695 
8696 	override void defaultEventHandler_keydown(KeyDownEvent ev) {
8697 		if(ev.key == Key.F12) {
8698 			this.close();
8699 			if(p)
8700 				p.devTools = null;
8701 		} else {
8702 			super.defaultEventHandler_keydown(ev);
8703 		}
8704 	}
8705 
8706 	void log(T...)(T t) {
8707 		string str;
8708 		import std.conv;
8709 		foreach(i; t)
8710 			str ~= to!string(i);
8711 		str ~= "\n";
8712 		logWindow.addText(str);
8713 
8714 		//version(custom_widgets)
8715 		//logWindow.ensureVisibleInScroll(logWindow.textLayout.caretBoundingBox());
8716 	}
8717 }
8718 
8719 /++
8720 	A dialog is a transient window that intends to get information from
8721 	the user before being dismissed.
8722 +/
8723 abstract class Dialog : Window {
8724 	///
8725 	this(int width, int height, string title = null) {
8726 		super(width, height, title);
8727 	}
8728 
8729 	///
8730 	abstract void OK();
8731 
8732 	///
8733 	void Cancel() {
8734 		this.close();
8735 	}
8736 }
8737 
8738 /++
8739 	A custom widget similar to the HTML5 <details> tag.
8740 +/
8741 version(none)
8742 class DetailsView : Widget {
8743 
8744 }
8745 
8746 // FIXME: maybe i should expose the other list views Windows offers too
8747 
8748 /++
8749 	A TableView is a widget made to display a table of data strings.
8750 
8751 
8752 	Future_Directions:
8753 		Each item should be able to take an icon too and maybe I'll allow more of the view modes Windows offers.
8754 
8755 		I will add a selection changed event at some point, as well as item clicked events.
8756 	History:
8757 		Added September 24, 2021. Initial api stabilized in dub v10.4, but it isn't completely feature complete yet.
8758 	See_Also:
8759 		[ListWidget] which displays a list of strings without additional columns.
8760 +/
8761 class TableView : Widget {
8762 	/++
8763 
8764 	+/
8765 	this(Widget parent) {
8766 		super(parent);
8767 
8768 		version(win32_widgets) {
8769 			createWin32Window(this, WC_LISTVIEW, "", LVS_REPORT | LVS_OWNERDATA);//| LVS_OWNERDRAWFIXED);
8770 		} else version(custom_widgets) {
8771 			auto smw = new ScrollMessageWidget(this);
8772 			smw.addDefaultKeyboardListeners();
8773 			smw.addDefaultWheelListeners(1, scaleWithDpi(16));
8774 			tvwi = new TableViewWidgetInner(this, smw);
8775 		}
8776 	}
8777 
8778 	// FIXME: auto-size columns on double click of header thing like in Windows
8779 	// it need only make the currently displayed things fit well.
8780 
8781 
8782 	private ColumnInfo[] columns;
8783 	private int itemCount;
8784 
8785 	version(custom_widgets) private {
8786 		TableViewWidgetInner tvwi;
8787 	}
8788 
8789 	/// Passed to [setColumnInfo]
8790 	static struct ColumnInfo {
8791 		const(char)[] name; /// the name displayed in the header
8792 		/++
8793 			The default width, in pixels. As a special case, you can set this to -1
8794 			if you want the system to try to automatically size the width to fit visible
8795 			content. If it can't, it will try to pick a sensible default size.
8796 
8797 			Any other negative value is not allowed and may lead to unpredictable results.
8798 
8799 			History:
8800 				The -1 behavior was specified on December 3, 2021. It actually worked before
8801 				anyway on Win32 but now it is a formal feature with partial Linux support.
8802 
8803 			Bugs:
8804 				It doesn't actually attempt to calculate a best-fit width on Linux as of
8805 				December 3, 2021. I do plan to fix this in the future, but Windows is the
8806 				priority right now. At least it doesn't break things when you use it now.
8807 		+/
8808 		int width;
8809 
8810 		/++
8811 			Alignment of the text in the cell. Applies to the header as well as all data in this
8812 			column.
8813 
8814 			Bugs:
8815 				On Windows, the first column ignores this member and is always left aligned.
8816 				You can work around this by inserting a dummy first column with width = 0
8817 				then putting your actual data in the second column, which does respect the
8818 				alignment.
8819 
8820 				This is a quirk of the operating system's implementation going back a very
8821 				long time and is unlikely to ever be fixed.
8822 		+/
8823 		TextAlignment alignment;
8824 
8825 		/++
8826 			After all the pixel widths have been assigned, any left over
8827 			space is divided up among all columns and distributed to according
8828 			to the widthPercent field.
8829 
8830 
8831 			For example, if you have two fields, both with width 50 and one with
8832 			widthPercent of 25 and the other with widthPercent of 75, and the
8833 			container is 200 pixels wide, first both get their width of 50.
8834 			then the 100 remaining pixels are split up, so the one gets a total
8835 			of 75 pixels and the other gets a total of 125.
8836 
8837 			This is automatically applied as the window is resized.
8838 
8839 			If there is not enough space - that is, when a horizontal scrollbar
8840 			needs to appear - there are 0 pixels divided up, and thus everyone
8841 			gets 0. This can cause a column to shrink out of proportion when
8842 			passing the scroll threshold.
8843 
8844 			It is important to still set a fixed width (that is, to populate the
8845 			`width` field) even if you use the percents because that will be the
8846 			default minimum in the event of a scroll bar appearing.
8847 
8848 			The percents total in the column can never exceed 100 or be less than 0.
8849 			Doing this will trigger an assert error.
8850 
8851 			Implementation note:
8852 
8853 			Please note that percentages are only recalculated 1) upon original
8854 			construction and 2) upon resizing the control. If the user adjusts the
8855 			width of a column, the percentage items will not be updated.
8856 
8857 			On the other hand, if the user adjusts the width of a percentage column
8858 			then resizes the window, it is recalculated, meaning their hand adjustment
8859 			is discarded. This specific behavior may change in the future as it is
8860 			arguably a bug, but I'm not certain yet.
8861 
8862 			History:
8863 				Added November 10, 2021 (dub v10.4)
8864 		+/
8865 		int widthPercent;
8866 
8867 
8868 		private int calculatedWidth;
8869 	}
8870 	/++
8871 		Sets the number of columns along with information about the headers.
8872 
8873 		Please note: on Windows, the first column ignores your alignment preference
8874 		and is always left aligned.
8875 	+/
8876 	void setColumnInfo(ColumnInfo[] columns...) {
8877 
8878 		foreach(ref c; columns) {
8879 			c.name = c.name.idup;
8880 		}
8881 		this.columns = columns.dup;
8882 
8883 		updateCalculatedWidth(false);
8884 
8885 		version(custom_widgets) {
8886 			tvwi.header.updateHeaders();
8887 			tvwi.updateScrolls();
8888 		} else version(win32_widgets)
8889 		foreach(i, column; this.columns) {
8890 			LVCOLUMN lvColumn;
8891 			lvColumn.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
8892 			lvColumn.cx = column.width == -1 ? -1 : column.calculatedWidth;
8893 
8894 			auto bfr = WCharzBuffer(column.name);
8895 			lvColumn.pszText = bfr.ptr;
8896 
8897 			if(column.alignment & TextAlignment.Center)
8898 				lvColumn.fmt = LVCFMT_CENTER;
8899 			else if(column.alignment & TextAlignment.Right)
8900 				lvColumn.fmt = LVCFMT_RIGHT;
8901 			else
8902 				lvColumn.fmt = LVCFMT_LEFT;
8903 
8904 			if(SendMessage(hwnd, LVM_INSERTCOLUMN, cast(WPARAM) i, cast(LPARAM) &lvColumn) == -1)
8905 				throw new WindowsApiException("Insert Column Fail", GetLastError());
8906 		}
8907 	}
8908 
8909 	private int getActualSetSize(size_t i, bool askWindows) {
8910 		version(win32_widgets)
8911 			if(askWindows)
8912 				return cast(int) SendMessage(hwnd, LVM_GETCOLUMNWIDTH, cast(WPARAM) i, 0);
8913 		auto w = columns[i].width;
8914 		if(w == -1)
8915 			return 50; // idk, just give it some space so the percents aren't COMPLETELY off FIXME
8916 		return w;
8917 	}
8918 
8919 	private void updateCalculatedWidth(bool informWindows) {
8920 		int padding;
8921 		version(win32_widgets)
8922 			padding = 4;
8923 		int remaining = this.width;
8924 		foreach(i, column; columns)
8925 			remaining -= this.getActualSetSize(i, informWindows && column.widthPercent == 0) + padding;
8926 		remaining -= padding;
8927 		if(remaining < 0)
8928 			remaining = 0;
8929 
8930 		int percentTotal;
8931 		foreach(i, ref column; columns) {
8932 			percentTotal += column.widthPercent;
8933 
8934 			auto c = this.getActualSetSize(i, informWindows && column.widthPercent == 0) + (remaining * column.widthPercent) / 100;
8935 
8936 			column.calculatedWidth = c;
8937 
8938 			version(win32_widgets)
8939 			if(informWindows)
8940 				SendMessage(hwnd, LVM_SETCOLUMNWIDTH, i, c); // LVSCW_AUTOSIZE or LVSCW_AUTOSIZE_USEHEADER are amazing omg
8941 		}
8942 
8943 		assert(percentTotal >= 0, "The total percents in your column definitions were negative. They must add up to something between 0 and 100.");
8944 		assert(percentTotal <= 100, "The total percents in your column definitions exceeded 100. They must add up to no more than 100 (can be less though).");
8945 
8946 
8947 	}
8948 
8949 	override void registerMovement() {
8950 		super.registerMovement();
8951 
8952 		updateCalculatedWidth(true);
8953 	}
8954 
8955 	/++
8956 		Tells the view how many items are in it. It uses this to set the scroll bar, but the items are not added per se; it calls [getData] as-needed.
8957 	+/
8958 	void setItemCount(int count) {
8959 		this.itemCount = count;
8960 		version(custom_widgets) {
8961 			tvwi.updateScrolls();
8962 			redraw();
8963 		} else version(win32_widgets) {
8964 			SendMessage(hwnd, LVM_SETITEMCOUNT, count, 0);
8965 		}
8966 	}
8967 
8968 	/++
8969 		Clears all items;
8970 	+/
8971 	void clear() {
8972 		this.itemCount = 0;
8973 		this.columns = null;
8974 		version(custom_widgets) {
8975 			tvwi.header.updateHeaders();
8976 			tvwi.updateScrolls();
8977 			redraw();
8978 		} else version(win32_widgets) {
8979 			SendMessage(hwnd, LVM_DELETEALLITEMS, 0, 0);
8980 		}
8981 	}
8982 
8983 	/+
8984 	version(win32_widgets)
8985 	override int handleWmDrawItem(DRAWITEMSTRUCT* dis)
8986 		auto itemId = dis.itemID;
8987 		auto hdc = dis.hDC;
8988 		auto rect = dis.rcItem;
8989 		switch(dis.itemAction) {
8990 			case ODA_DRAWENTIRE:
8991 
8992 				// FIXME: do other items
8993 				// FIXME: do the focus rectangle i guess
8994 				// FIXME: alignment
8995 				// FIXME: column width
8996 				// FIXME: padding left
8997 				// FIXME: check dpi scaling
8998 				// FIXME: don't owner draw unless it is necessary.
8999 
9000 				auto padding = GetSystemMetrics(SM_CXEDGE); // FIXME: for dpi
9001 				RECT itemRect;
9002 				itemRect.top = 1; // subitem idx, 1-based
9003 				itemRect.left = LVIR_BOUNDS;
9004 
9005 				SendMessage(hwnd, LVM_GETSUBITEMRECT, itemId, cast(LPARAM) &itemRect);
9006 				itemRect.left += padding;
9007 
9008 				getData(itemId, 0, (in char[] data) {
9009 					auto wdata = WCharzBuffer(data);
9010 					DrawTextW(hdc, wdata.ptr, wdata.length, &itemRect, DT_RIGHT| DT_END_ELLIPSIS);
9011 
9012 				});
9013 			goto case;
9014 			case ODA_FOCUS:
9015 				if(dis.itemState & ODS_FOCUS)
9016 					DrawFocusRect(hdc, &rect);
9017 			break;
9018 			case ODA_SELECT:
9019 				// itemState & ODS_SELECTED
9020 			break;
9021 			default:
9022 		}
9023 		return 1;
9024 	}
9025 	+/
9026 
9027 	version(win32_widgets) {
9028 		CellStyle last;
9029 		COLORREF defaultColor;
9030 		COLORREF defaultBackground;
9031 	}
9032 
9033 	version(win32_widgets)
9034 	override int handleWmNotify(NMHDR* hdr, int code, out int mustReturn) {
9035 		switch(code) {
9036 			case NM_CUSTOMDRAW:
9037 				auto s = cast(NMLVCUSTOMDRAW*) hdr;
9038 				switch(s.nmcd.dwDrawStage) {
9039 					case CDDS_PREPAINT:
9040 						if(getCellStyle is null)
9041 							return 0;
9042 
9043 						mustReturn = true;
9044 						return CDRF_NOTIFYITEMDRAW;
9045 					case CDDS_ITEMPREPAINT:
9046 						mustReturn = true;
9047 						return CDRF_NOTIFYSUBITEMDRAW;
9048 					case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
9049 						mustReturn = true;
9050 
9051 						if(getCellStyle is null) // this SHOULD never happen...
9052 							return 0;
9053 
9054 						if(s.iSubItem == 0) {
9055 							// Windows resets it per row so we'll use item 0 as a chance
9056 							// to capture these for later
9057 							defaultColor = s.clrText;
9058 							defaultBackground = s.clrTextBk;
9059 						}
9060 
9061 						auto style = getCellStyle(cast(int) s.nmcd.dwItemSpec, cast(int) s.iSubItem);
9062 						// if no special style and no reset needed...
9063 						if(style == CellStyle.init && (s.iSubItem == 0 || last == CellStyle.init))
9064 							return 0; // allow default processing to continue
9065 
9066 						last = style;
9067 
9068 						// might still need to reset or use the preference.
9069 
9070 						if(style.flags & CellStyle.Flags.textColorSet)
9071 							s.clrText = style.textColor.asWindowsColorRef;
9072 						else
9073 							s.clrText = defaultColor; // reset in case it was set from last iteration not a fan
9074 						if(style.flags & CellStyle.Flags.backgroundColorSet)
9075 							s.clrTextBk = style.backgroundColor.asWindowsColorRef;
9076 						else
9077 							s.clrTextBk = defaultBackground; // need to reset it... not a fan of this
9078 
9079 						return CDRF_NEWFONT;
9080 					default:
9081 						return 0;
9082 
9083 				}
9084 			case NM_RETURN: // no need since i subclass keydown
9085 			break;
9086 			case LVN_COLUMNCLICK:
9087 				auto info = cast(LPNMLISTVIEW) hdr;
9088 				this.emit!HeaderClickedEvent(info.iSubItem);
9089 			break;
9090 			case NM_CLICK:
9091 			case NM_DBLCLK:
9092 			case NM_RCLICK:
9093 			case NM_RDBLCLK:
9094 				// the item/subitem is set here and that can be a useful notification
9095 				// even beyond the normal click notification
9096 			break;
9097 			case LVN_GETDISPINFO:
9098 				LV_DISPINFO* info = cast(LV_DISPINFO*) hdr;
9099 				if(info.item.mask & LVIF_TEXT) {
9100 					if(getData) {
9101 						getData(info.item.iItem, info.item.iSubItem, (in char[] dataReceived) {
9102 							auto bfr = WCharzBuffer(dataReceived);
9103 							auto len = info.item.cchTextMax;
9104 							if(bfr.length < len)
9105 								len = cast(typeof(len)) bfr.length;
9106 							info.item.pszText[0 .. len] = bfr.ptr[0 .. len];
9107 							info.item.pszText[len] = 0;
9108 						});
9109 					} else {
9110 						info.item.pszText[0] = 0;
9111 					}
9112 					//info.item.iItem
9113 					//if(info.item.iSubItem)
9114 				}
9115 			break;
9116 			default:
9117 		}
9118 		return 0;
9119 	}
9120 
9121 	override bool encapsulatedChildren() {
9122 		return true;
9123 	}
9124 
9125 	/++
9126 		Informs the control that content has changed.
9127 
9128 		History:
9129 			Added November 10, 2021 (dub v10.4)
9130 	+/
9131 	void update() {
9132 		version(custom_widgets)
9133 			redraw();
9134 		else {
9135 			SendMessage(hwnd, LVM_REDRAWITEMS, 0, SendMessage(hwnd, LVM_GETITEMCOUNT, 0, 0));
9136 			UpdateWindow(hwnd);
9137 		}
9138 
9139 
9140 	}
9141 
9142 	/++
9143 		Called by the system to request the text content of an individual cell. You
9144 		should pass the text into the provided `sink` delegate. This function will be
9145 		called for each visible cell as-needed when drawing.
9146 	+/
9147 	void delegate(int row, int column, scope void delegate(in char[]) sink) getData;
9148 
9149 	/++
9150 		Available per-cell style customization options. Use one of the constructors
9151 		provided to set the values conveniently, or default construct it and set individual
9152 		values yourself. Just remember to set the `flags` so your values are actually used.
9153 		If the flag isn't set, the field is ignored and the system default is used instead.
9154 
9155 		This is returned by the [getCellStyle] delegate.
9156 
9157 		Examples:
9158 			---
9159 			// assumes you have a variables called `my_data` which is an array of arrays of numbers
9160 			auto table = new TableView(window);
9161 			// snip: you would set up columns here
9162 
9163 			// this is how you provide data to the table view class
9164 			table.getData = delegate(int row, int column, scope void delegate(in char[]) sink) {
9165 				import std.conv;
9166 				sink(to!string(my_data[row][column]));
9167 			};
9168 
9169 			// and this is how you customize the colors
9170 			table.getCellStyle = delegate(int row, int column) {
9171 				return (my_data[row][column] < 0) ?
9172 					TableView.CellStyle(Color.red); // make negative numbers red
9173 					: TableView.CellStyle.init; // leave the rest alone
9174 			};
9175 			// snip: you would call table.setItemCount here then continue with the rest of your window setup work
9176 			---
9177 
9178 		History:
9179 			Added November 27, 2021 (dub v10.4)
9180 	+/
9181 	struct CellStyle {
9182 		/// Sets just a custom text color, leaving the background as the default. Use caution with certain colors as it may have illeglible contrast on the (unknown to you) background color.
9183 		this(Color textColor) {
9184 			this.textColor = textColor;
9185 			this.flags |= Flags.textColorSet;
9186 		}
9187 		/// Sets a custom text and background color.
9188 		this(Color textColor, Color backgroundColor) {
9189 			this.textColor = textColor;
9190 			this.backgroundColor = backgroundColor;
9191 			this.flags |= Flags.textColorSet | Flags.backgroundColorSet;
9192 		}
9193 
9194 		Color textColor;
9195 		Color backgroundColor;
9196 		int flags; /// bitmask of [Flags]
9197 		/// available options to combine into [flags]
9198 		enum Flags {
9199 			textColorSet = 1 << 0,
9200 			backgroundColorSet = 1 << 1,
9201 		}
9202 	}
9203 	/++
9204 		Companion delegate to [getData] that allows you to custom style each
9205 		cell of the table.
9206 
9207 		Returns:
9208 			A [CellStyle] structure that describes the desired style for the
9209 			given cell. `return CellStyle.init` if you want the default style.
9210 
9211 		History:
9212 			Added November 27, 2021 (dub v10.4)
9213 	+/
9214 	CellStyle delegate(int row, int column) getCellStyle;
9215 
9216 	// i want to be able to do things like draw little colored things to show red for negative numbers
9217 	// or background color indicators or even in-cell charts
9218 	// void delegate(int row, int column, WidgetPainter painter, int width, int height, in char[] text) drawCell;
9219 
9220 	/++
9221 		When the user clicks on a header, this event is emitted. It has a meber to identify which header (by index) was clicked.
9222 	+/
9223 	mixin Emits!HeaderClickedEvent;
9224 }
9225 
9226 /++
9227 	This is emitted by the [TableView] when a user clicks on a column header.
9228 
9229 	Its member `columnIndex` has the zero-based index of the column that was clicked.
9230 
9231 	The default behavior of this event is to do nothing, so `preventDefault` has no effect.
9232 
9233 	History:
9234 		Added November 27, 2021 (dub v10.4)
9235 +/
9236 class HeaderClickedEvent : Event {
9237 	enum EventString = "HeaderClicked";
9238 	this(Widget target, int columnIndex) {
9239 		this.columnIndex = columnIndex;
9240 		super(EventString, target);
9241 	}
9242 
9243 	/// The index of the column
9244 	int columnIndex;
9245 
9246 	///
9247 	override @property int intValue() {
9248 		return columnIndex;
9249 	}
9250 }
9251 
9252 version(custom_widgets)
9253 private class TableViewWidgetInner : Widget {
9254 
9255 // wrap this thing in a ScrollMessageWidget
9256 
9257 	TableView tvw;
9258 	ScrollMessageWidget smw;
9259 	HeaderWidget header;
9260 
9261 	this(TableView tvw, ScrollMessageWidget smw) {
9262 		this.tvw = tvw;
9263 		this.smw = smw;
9264 		super(smw);
9265 
9266 		this.tabStop = true;
9267 
9268 		header = new HeaderWidget(this, smw.getHeader());
9269 
9270 		smw.addEventListener("scroll", () {
9271 			this.redraw();
9272 			header.redraw();
9273 		});
9274 
9275 
9276 		// I need headers outside the scroll area but rendered on the same line as the up arrow
9277 		// FIXME: add a fixed header to the SMW
9278 	}
9279 
9280 	enum padding = 3;
9281 
9282 	void updateScrolls() {
9283 		int w;
9284 		foreach(idx, column; tvw.columns) {
9285 			if(column.width == 0) continue;
9286 			w += tvw.getActualSetSize(idx, false);// + padding;
9287 		}
9288 		smw.setTotalArea(w, tvw.itemCount);
9289 		columnsWidth = w;
9290 	}
9291 
9292 	private int columnsWidth;
9293 
9294 	private int lh() { return scaleWithDpi(16); } // FIXME lineHeight
9295 
9296 	override void registerMovement() {
9297 		super.registerMovement();
9298 		// FIXME: actual column width. it might need to be done per-pixel instead of per-colum
9299 		smw.setViewableArea(this.width, this.height / lh);
9300 	}
9301 
9302 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
9303 		int x;
9304 		int y;
9305 
9306 		int row = smw.position.y;
9307 
9308 		foreach(lol; 0 .. this.height / lh) {
9309 			if(row >= tvw.itemCount)
9310 				break;
9311 			x = 0;
9312 			foreach(columnNumber, column; tvw.columns) {
9313 				auto x2 = x + column.calculatedWidth;
9314 				auto smwx = smw.position.x;
9315 
9316 				if(x2 > smwx /* if right side of it is visible at all */ || (x >= smwx && x < smwx + this.width) /* left side is visible at all*/) {
9317 					auto startX = x;
9318 					auto endX = x + column.calculatedWidth;
9319 					switch (column.alignment & (TextAlignment.Left | TextAlignment.Center | TextAlignment.Right)) {
9320 						case TextAlignment.Left: startX += padding; break;
9321 						case TextAlignment.Center: startX += padding; endX -= padding; break;
9322 						case TextAlignment.Right: endX -= padding; break;
9323 						default: /* broken */ break;
9324 					}
9325 					if(column.width != 0) // no point drawing an invisible column
9326 					tvw.getData(row, cast(int) columnNumber, (in char[] info) {
9327 						// auto clip = painter.setClipRectangle(
9328 
9329 						void dotext(WidgetPainter painter) {
9330 							painter.drawText(Point(startX - smw.position.x, y), info, Point(endX - smw.position.x, y + lh), column.alignment);
9331 						}
9332 
9333 						if(tvw.getCellStyle !is null) {
9334 							auto style = tvw.getCellStyle(row, cast(int) columnNumber);
9335 
9336 							if(style.flags & TableView.CellStyle.Flags.backgroundColorSet) {
9337 								auto tempPainter = painter;
9338 								tempPainter.fillColor = style.backgroundColor;
9339 								tempPainter.outlineColor = style.backgroundColor;
9340 
9341 								tempPainter.drawRectangle(Point(startX - smw.position.x, y),
9342 									Point(endX - smw.position.x, y + lh));
9343 							}
9344 							auto tempPainter = painter;
9345 							if(style.flags & TableView.CellStyle.Flags.textColorSet)
9346 								tempPainter.outlineColor = style.textColor;
9347 
9348 							dotext(tempPainter);
9349 						} else {
9350 							dotext(painter);
9351 						}
9352 					});
9353 				}
9354 
9355 				x += column.calculatedWidth;
9356 			}
9357 			row++;
9358 			y += lh;
9359 		}
9360 		return bounds;
9361 	}
9362 
9363 	static class Style : Widget.Style {
9364 		override WidgetBackground background() {
9365 			return WidgetBackground(WidgetPainter.visualTheme.widgetBackgroundColor);
9366 		}
9367 	}
9368 	mixin OverrideStyle!Style;
9369 
9370 	private static class HeaderWidget : Widget {
9371 		this(TableViewWidgetInner tvw, Widget parent) {
9372 			super(parent);
9373 			this.tvw = tvw;
9374 
9375 			this.remainder = new Button("", this);
9376 
9377 			this.addEventListener((scope ClickEvent ev) {
9378 				int header = -1;
9379 				foreach(idx, child; this.children[1 .. $]) {
9380 					if(child is ev.target) {
9381 						header = cast(int) idx;
9382 						break;
9383 					}
9384 				}
9385 
9386 				if(header != -1) {
9387 					auto hce = new HeaderClickedEvent(tvw.tvw, header);
9388 					hce.dispatch();
9389 				}
9390 
9391 			});
9392 		}
9393 
9394 		void updateHeaders() {
9395 			foreach(child; children[1 .. $])
9396 				child.removeWidget();
9397 
9398 			foreach(column; tvw.tvw.columns) {
9399 				// the cast is ok because I dup it above, just the type is never changed.
9400 				// all this is private so it should never get messed up.
9401 				new Button(ImageLabel(cast(string) column.name, column.alignment), this);
9402 			}
9403 		}
9404 
9405 		Button remainder;
9406 		TableViewWidgetInner tvw;
9407 
9408 		override void recomputeChildLayout() {
9409 			registerMovement();
9410 			int pos;
9411 			foreach(idx, child; children[1 .. $]) {
9412 				if(idx >= tvw.tvw.columns.length)
9413 					continue;
9414 				child.x = pos;
9415 				child.y = 0;
9416 				child.width = tvw.tvw.columns[idx].calculatedWidth;
9417 				child.height = scaleWithDpi(16);// this.height;
9418 				pos += child.width;
9419 
9420 				child.recomputeChildLayout();
9421 			}
9422 
9423 			if(remainder is null)
9424 				return;
9425 
9426 			remainder.x = pos;
9427 			remainder.y = 0;
9428 			if(pos < this.width)
9429 				remainder.width = this.width - pos;// + 4;
9430 			else
9431 				remainder.width = 0;
9432 			remainder.height = scaleWithDpi(16);
9433 
9434 			remainder.recomputeChildLayout();
9435 		}
9436 
9437 		// for the scrollable children mixin
9438 		Point scrollOrigin() {
9439 			return Point(tvw.smw.position.x, 0);
9440 		}
9441 		void paintFrameAndBackground(WidgetPainter painter) { }
9442 
9443 		mixin ScrollableChildren;
9444 	}
9445 }
9446 
9447 /+
9448 
9449 // given struct / array / number / string / etc, make it viewable and editable
9450 class DataViewerWidget : Widget {
9451 
9452 }
9453 +/
9454 
9455 /++
9456 	A line edit box with an associated label.
9457 
9458 	History:
9459 		On May 17, 2021, the default internal layout was changed from horizontal to vertical.
9460 
9461 		```
9462 		Old: ________
9463 
9464 		New:
9465 		____________
9466 		```
9467 
9468 		To restore the old behavior, use `new LabeledLineEdit("label", TextAlignment.Right, parent);`
9469 
9470 		You can also use `new LabeledLineEdit("label", TextAlignment.Left, parent);` if you want a
9471 		horizontal label but left aligned. You may also consider a [GridLayout].
9472 +/
9473 alias LabeledLineEdit = Labeled!LineEdit;
9474 
9475 /++
9476 	History:
9477 		Added May 19, 2021
9478 +/
9479 class Labeled(T) : Widget {
9480 	///
9481 	this(string label, Widget parent) {
9482 		super(parent);
9483 		initialize!VerticalLayout(label, TextAlignment.Left, parent);
9484 	}
9485 
9486 	/++
9487 		History:
9488 			The alignment parameter was added May 17, 2021
9489 	+/
9490 	this(string label, TextAlignment alignment, Widget parent) {
9491 		super(parent);
9492 		initialize!HorizontalLayout(label, alignment, parent);
9493 	}
9494 
9495 	private void initialize(L)(string label, TextAlignment alignment, Widget parent) {
9496 		tabStop = false;
9497 		horizontal = is(L == HorizontalLayout);
9498 		auto hl = new L(this);
9499 		this.label = new TextLabel(label, alignment, hl);
9500 		this.lineEdit = new T(hl);
9501 
9502 		this.label.labelFor = this.lineEdit;
9503 	}
9504 
9505 	private bool horizontal;
9506 
9507 	TextLabel label; ///
9508 	T lineEdit; ///
9509 
9510 	override int flexBasisWidth() { return 250; }
9511 
9512 	override int minHeight() {
9513 		return this.children[0].minHeight;
9514 	}
9515 	override int maxHeight() { return minHeight(); }
9516 	override int marginTop() { return 4; }
9517 	override int marginBottom() { return 4; }
9518 
9519 	// FIXME: i should prolly call it value as well as content tbh
9520 
9521 	///
9522 	@property string content() {
9523 		return lineEdit.content;
9524 	}
9525 	///
9526 	@property void content(string c) {
9527 		return lineEdit.content(c);
9528 	}
9529 
9530 	///
9531 	void selectAll() {
9532 		lineEdit.selectAll();
9533 	}
9534 
9535 	override void focus() {
9536 		lineEdit.focus();
9537 	}
9538 }
9539 
9540 /++
9541 	A labeled password edit.
9542 
9543 	History:
9544 		Added as a class on January 25, 2021, changed into an alias of the new [Labeled] template on May 19, 2021
9545 
9546 		The default parameters for the constructors were also removed on May 19, 2021
9547 +/
9548 alias LabeledPasswordEdit = Labeled!PasswordEdit;
9549 
9550 private string toMenuLabel(string s) {
9551 	string n;
9552 	n.reserve(s.length);
9553 	foreach(c; s)
9554 		if(c == '_')
9555 			n ~= ' ';
9556 		else
9557 			n ~= c;
9558 	return n;
9559 }
9560 
9561 private void autoExceptionHandler(Exception e) {
9562 	messageBox(e.msg);
9563 }
9564 
9565 private void delegate() makeAutomaticHandler(alias fn, T)(T t) {
9566 	static if(is(T : void delegate())) {
9567 		return () {
9568 			try
9569 				t();
9570 			catch(Exception e)
9571 				autoExceptionHandler(e);
9572 		};
9573 	} else static if(is(typeof(fn) Params == __parameters)) {
9574 		static if(Params.length == 1 && is(Params[0] == FileName!(member, filters, type), alias member, string[] filters, FileDialogType type)) {
9575 			return () {
9576 				void onOK(string s) {
9577 					member = s;
9578 					try
9579 						t(Params[0](s));
9580 					catch(Exception e)
9581 						autoExceptionHandler(e);
9582 				}
9583 
9584 				if(
9585 					(type == FileDialogType.Automatic && (__traits(identifier, fn).startsWith("Save") || __traits(identifier, fn).startsWith("Export")))
9586 					|| type == FileDialogType.Save)
9587 				{
9588 					getSaveFileName(&onOK, member, filters, null);
9589 				} else
9590 					getOpenFileName(&onOK, member, filters, null);
9591 			};
9592 		} else {
9593 			struct S {
9594 				static if(!__traits(compiles, mixin(`{ static foreach(i; 1..4) {} }`))) {
9595 					pragma(msg, "warning: automatic handler of params not yet implemented on your compiler");
9596 				} else mixin(q{
9597 				static foreach(idx, ignore; Params) {
9598 					mixin("Params[idx] " ~ __traits(identifier, Params[idx .. idx + 1]) ~ ";");
9599 				}
9600 				});
9601 			}
9602 			return () {
9603 				dialog((S s) {
9604 					try {
9605 						static if(is(typeof(t) Ret == return)) {
9606 							static if(is(Ret == void)) {
9607 								t(s.tupleof);
9608 							} else {
9609 								auto ret = t(s.tupleof);
9610 								import std.conv;
9611 								messageBox(to!string(ret), "Returned Value");
9612 							}
9613 						}
9614 					} catch(Exception e)
9615 						autoExceptionHandler(e);
9616 				}, null, __traits(identifier, fn));
9617 			};
9618 		}
9619 	}
9620 }
9621 
9622 private template hasAnyRelevantAnnotations(a...) {
9623 	bool helper() {
9624 		bool any;
9625 		foreach(attr; a) {
9626 			static if(is(typeof(attr) == .menu))
9627 				any = true;
9628 			else static if(is(typeof(attr) == .toolbar))
9629 				any = true;
9630 			else static if(is(attr == .separator))
9631 				any = true;
9632 			else static if(is(typeof(attr) == .accelerator))
9633 				any = true;
9634 			else static if(is(typeof(attr) == .hotkey))
9635 				any = true;
9636 			else static if(is(typeof(attr) == .icon))
9637 				any = true;
9638 			else static if(is(typeof(attr) == .label))
9639 				any = true;
9640 			else static if(is(typeof(attr) == .tip))
9641 				any = true;
9642 		}
9643 		return any;
9644 	}
9645 
9646 	enum bool hasAnyRelevantAnnotations = helper();
9647 }
9648 
9649 /++
9650 	A `MainWindow` is a window that includes turnkey support for a menu bar, tool bar, and status bar automatically positioned around a client area where you put your widgets.
9651 +/
9652 class MainWindow : Window {
9653 	///
9654 	this(string title = null, int initialWidth = 500, int initialHeight = 500) {
9655 		super(initialWidth, initialHeight, title);
9656 
9657 		_clientArea = new ClientAreaWidget();
9658 		_clientArea.x = 0;
9659 		_clientArea.y = 0;
9660 		_clientArea.width = this.width;
9661 		_clientArea.height = this.height;
9662 		_clientArea.tabStop = false;
9663 
9664 		super.addChild(_clientArea);
9665 
9666 		statusBar = new StatusBar(this);
9667 	}
9668 
9669 	/++
9670 		Adds a menu and toolbar from annotated functions.
9671 
9672 	---
9673         struct Commands {
9674                 @menu("File") {
9675                         void New() {}
9676                         void Open() {}
9677                         void Save() {}
9678                         @separator
9679                         void Exit() @accelerator("Alt+F4") @hotkey('x') {
9680                                 window.close();
9681                         }
9682                 }
9683 
9684                 @menu("Edit") {
9685                         void Undo() {
9686                                 undo();
9687                         }
9688                         @separator
9689                         void Cut() {}
9690                         void Copy() {}
9691                         void Paste() {}
9692                 }
9693 
9694                 @menu("Help") {
9695                         void About() {}
9696                 }
9697         }
9698 
9699         Commands commands;
9700 
9701         window.setMenuAndToolbarFromAnnotatedCode(commands);
9702 	---
9703 
9704 	Note that you can call this function multiple times and it will add the items in order to the given items.
9705 
9706 	+/
9707 	void setMenuAndToolbarFromAnnotatedCode(T)(ref T t) if(!is(T == class) && !is(T == interface)) {
9708 		setMenuAndToolbarFromAnnotatedCode_internal(t);
9709 	}
9710 	void setMenuAndToolbarFromAnnotatedCode(T)(T t) if(is(T == class) || is(T == interface)) {
9711 		setMenuAndToolbarFromAnnotatedCode_internal(t);
9712 	}
9713 	void setMenuAndToolbarFromAnnotatedCode_internal(T)(ref T t) {
9714 		Action[] toolbarActions;
9715 		auto menuBar = this.menuBar is null ? new MenuBar() : this.menuBar;
9716 		Menu[string] mcs;
9717 
9718 		foreach(menu; menuBar.subMenus) {
9719 			mcs[menu.label] = menu;
9720 		}
9721 
9722 		foreach(memberName; __traits(derivedMembers, T)) {
9723 			static if(memberName != "this")
9724 			static if(hasAnyRelevantAnnotations!(__traits(getAttributes, __traits(getMember, T, memberName)))) {
9725 				.menu menu;
9726 				.toolbar toolbar;
9727 				bool separator;
9728 				.accelerator accelerator;
9729 				.hotkey hotkey;
9730 				.icon icon;
9731 				string label;
9732 				string tip;
9733 				foreach(attr; __traits(getAttributes, __traits(getMember, T, memberName))) {
9734 					static if(is(typeof(attr) == .menu))
9735 						menu = attr;
9736 					else static if(is(typeof(attr) == .toolbar))
9737 						toolbar = attr;
9738 					else static if(is(attr == .separator))
9739 						separator = true;
9740 					else static if(is(typeof(attr) == .accelerator))
9741 						accelerator = attr;
9742 					else static if(is(typeof(attr) == .hotkey))
9743 						hotkey = attr;
9744 					else static if(is(typeof(attr) == .icon))
9745 						icon = attr;
9746 					else static if(is(typeof(attr) == .label))
9747 						label = attr.label;
9748 					else static if(is(typeof(attr) == .tip))
9749 						tip = attr.tip;
9750 				}
9751 
9752 				if(menu !is .menu.init || toolbar !is .toolbar.init) {
9753 					ushort correctIcon = icon.id; // FIXME
9754 					if(label.length == 0)
9755 						label = memberName.toMenuLabel;
9756 
9757 					auto handler = makeAutomaticHandler!(__traits(getMember, T, memberName))(&__traits(getMember, t, memberName));
9758 
9759 					auto action = new Action(label, correctIcon, handler);
9760 
9761 					if(accelerator.keyString.length) {
9762 						auto ke = KeyEvent.parse(accelerator.keyString);
9763 						action.accelerator = ke;
9764 						accelerators[ke.toStr] = handler;
9765 					}
9766 
9767 					if(toolbar !is .toolbar.init)
9768 						toolbarActions ~= action;
9769 					if(menu !is .menu.init) {
9770 						Menu mc;
9771 						if(menu.name in mcs) {
9772 							mc = mcs[menu.name];
9773 						} else {
9774 							mc = new Menu(menu.name, this);
9775 							menuBar.addItem(mc);
9776 							mcs[menu.name] = mc;
9777 						}
9778 
9779 						if(separator)
9780 							mc.addSeparator();
9781 						mc.addItem(new MenuItem(action));
9782 					}
9783 				}
9784 			}
9785 		}
9786 
9787 		this.menuBar = menuBar;
9788 
9789 		if(toolbarActions.length) {
9790 			auto tb = new ToolBar(toolbarActions, this);
9791 		}
9792 	}
9793 
9794 	void delegate()[string] accelerators;
9795 
9796 	override void defaultEventHandler_keydown(KeyDownEvent event) {
9797 		auto str = event.originalKeyEvent.toStr;
9798 		if(auto acl = str in accelerators)
9799 			(*acl)();
9800 		super.defaultEventHandler_keydown(event);
9801 	}
9802 
9803 	override void defaultEventHandler_mouseover(MouseOverEvent event) {
9804 		super.defaultEventHandler_mouseover(event);
9805 		if(this.statusBar !is null && event.target.statusTip.length)
9806 			this.statusBar.parts[0].content = event.target.statusTip;
9807 		else if(this.statusBar !is null && this.statusTip.length)
9808 			this.statusBar.parts[0].content = this.statusTip; // ~ " " ~ event.target.toString();
9809 	}
9810 
9811 	override void addChild(Widget c, int position = int.max) {
9812 		if(auto tb = cast(ToolBar) c)
9813 			version(win32_widgets)
9814 				super.addChild(c, 0);
9815 			else version(custom_widgets)
9816 				super.addChild(c, menuBar ? 1 : 0);
9817 			else static assert(0);
9818 		else
9819 			clientArea.addChild(c, position);
9820 	}
9821 
9822 	ToolBar _toolBar;
9823 	///
9824 	ToolBar toolBar() { return _toolBar; }
9825 	///
9826 	ToolBar toolBar(ToolBar t) {
9827 		_toolBar = t;
9828 		foreach(child; this.children)
9829 			if(child is t)
9830 				return t;
9831 		version(win32_widgets)
9832 			super.addChild(t, 0);
9833 		else version(custom_widgets)
9834 			super.addChild(t, menuBar ? 1 : 0);
9835 		else static assert(0);
9836 		return t;
9837 	}
9838 
9839 	MenuBar _menu;
9840 	///
9841 	MenuBar menuBar() { return _menu; }
9842 	///
9843 	MenuBar menuBar(MenuBar m) {
9844 		if(m is _menu) {
9845 			version(custom_widgets)
9846 				recomputeChildLayout();
9847 			return m;
9848 		}
9849 
9850 		if(_menu !is null) {
9851 			// make sure it is sanely removed
9852 			// FIXME
9853 		}
9854 
9855 		_menu = m;
9856 
9857 		version(win32_widgets) {
9858 			SetMenu(parentWindow.win.impl.hwnd, m.handle);
9859 		} else version(custom_widgets) {
9860 			super.addChild(m, 0);
9861 
9862 		//	clientArea.y = menu.height;
9863 		//	clientArea.height = this.height - menu.height;
9864 
9865 			recomputeChildLayout();
9866 		} else static assert(false);
9867 
9868 		return _menu;
9869 	}
9870 	private Widget _clientArea;
9871 	///
9872 	@property Widget clientArea() { return _clientArea; }
9873 	protected @property void clientArea(Widget wid) {
9874 		_clientArea = wid;
9875 	}
9876 
9877 	private StatusBar _statusBar;
9878 	/++
9879 		Returns the window's [StatusBar]. Be warned it may be `null`.
9880 	+/
9881 	@property StatusBar statusBar() { return _statusBar; }
9882 	/// ditto
9883 	@property void statusBar(StatusBar bar) {
9884 		if(_statusBar !is null)
9885 			_statusBar.removeWidget();
9886 		_statusBar = bar;
9887 		if(bar !is null)
9888 			super.addChild(_statusBar);
9889 	}
9890 }
9891 
9892 /+
9893 	This is really an implementation detail of [MainWindow]
9894 +/
9895 private class ClientAreaWidget : Widget {
9896 	this() {
9897 		this.tabStop = false;
9898 		super(null);
9899 		//sa = new ScrollableWidget(this);
9900 	}
9901 	/*
9902 	ScrollableWidget sa;
9903 	override void addChild(Widget w, int position) {
9904 		if(sa is null)
9905 			super.addChild(w, position);
9906 		else {
9907 			sa.addChild(w, position);
9908 			sa.setContentSize(this.minWidth + 1, this.minHeight);
9909 			import std.stdio; writeln(sa.contentWidth, "x", sa.contentHeight);
9910 		}
9911 	}
9912 	*/
9913 }
9914 
9915 /**
9916 	Toolbars are lists of buttons (typically icons) that appear under the menu.
9917 	Each button ought to correspond to a menu item, represented by [Action] objects.
9918 */
9919 class ToolBar : Widget {
9920 	version(win32_widgets) {
9921 		private const int idealHeight;
9922 		override int minHeight() { return idealHeight; }
9923 		override int maxHeight() { return idealHeight; }
9924 	} else version(custom_widgets) {
9925 		override int minHeight() { return toolbarIconSize; }// defaultLineHeight * 3/2; }
9926 		override int maxHeight() { return toolbarIconSize; } //defaultLineHeight * 3/2; }
9927 	} else static assert(false);
9928 	override int heightStretchiness() { return 0; }
9929 
9930 	version(win32_widgets)
9931 		HIMAGELIST imageList;
9932 
9933 	this(Widget parent) {
9934 		this(null, parent);
9935 	}
9936 
9937 	///
9938 	this(Action[] actions, Widget parent) {
9939 		super(parent);
9940 
9941 		tabStop = false;
9942 
9943 		version(win32_widgets) {
9944 			// so i like how the flat thing looks on windows, but not on wine
9945 			// and eh, with windows visual styles enabled it looks cool anyway soooo gonna
9946 			// leave it commented
9947 			createWin32Window(this, "ToolbarWindow32"w, "", TBSTYLE_LIST|/*TBSTYLE_FLAT|*/TBSTYLE_TOOLTIPS);
9948 
9949 			SendMessageW(hwnd, TB_SETEXTENDEDSTYLE, 0, 8/*TBSTYLE_EX_MIXEDBUTTONS*/);
9950 
9951 			imageList = ImageList_Create(
9952 				// width, height
9953 				16, 16,
9954 				ILC_COLOR16 | ILC_MASK,
9955 				16 /*numberOfButtons*/, 0);
9956 
9957 			SendMessageW(hwnd, TB_SETIMAGELIST, cast(WPARAM) 0, cast(LPARAM) imageList);
9958 			SendMessageW(hwnd, TB_LOADIMAGES, cast(WPARAM) IDB_STD_SMALL_COLOR, cast(LPARAM) HINST_COMMCTRL);
9959 			SendMessageW(hwnd, TB_SETMAXTEXTROWS, 0, 0);
9960 			SendMessageW(hwnd, TB_AUTOSIZE, 0, 0);
9961 
9962 			TBBUTTON[] buttons;
9963 
9964 			// FIXME: I_IMAGENONE is if here is no icon
9965 			foreach(action; actions)
9966 				buttons ~= TBBUTTON(
9967 					MAKELONG(cast(ushort)(action.iconId ? (action.iconId - 1) : -2 /* I_IMAGENONE */), 0),
9968 					action.id,
9969 					TBSTATE_ENABLED, // state
9970 					0, // style
9971 					0, // reserved array, just zero it out
9972 					0, // dwData
9973 					cast(size_t) toWstringzInternal(action.label) // INT_PTR
9974 				);
9975 
9976 			SendMessageW(hwnd, TB_BUTTONSTRUCTSIZE, cast(WPARAM)TBBUTTON.sizeof, 0);
9977 			SendMessageW(hwnd, TB_ADDBUTTONSW, cast(WPARAM) buttons.length, cast(LPARAM)buttons.ptr);
9978 
9979 			SIZE size;
9980 			import core.sys.windows.commctrl;
9981 			SendMessageW(hwnd, TB_GETMAXSIZE, 0, cast(LPARAM) &size);
9982 			idealHeight = size.cy + 4; // the plus 4 is a hack
9983 
9984 			/*
9985 			RECT rect;
9986 			GetWindowRect(hwnd, &rect);
9987 			idealHeight = rect.bottom - rect.top + 10; // the +10 is a hack since the size right now doesn't look right on a real Windows XP box
9988 			*/
9989 
9990 			assert(idealHeight);
9991 		} else version(custom_widgets) {
9992 			foreach(action; actions)
9993 				new ToolButton(action, this);
9994 		} else static assert(false);
9995 	}
9996 
9997 	override void recomputeChildLayout() {
9998 		.recomputeChildLayout!"width"(this);
9999 	}
10000 }
10001 
10002 enum toolbarIconSize = 24;
10003 
10004 /// An implementation helper for [ToolBar]. Generally, you shouldn't create these yourself and instead just pass [Action]s to [ToolBar]'s constructor and let it create the buttons for you.
10005 class ToolButton : Button {
10006 	///
10007 	this(string label, Widget parent) {
10008 		super(label, parent);
10009 		tabStop = false;
10010 	}
10011 	///
10012 	this(Action action, Widget parent) {
10013 		super(action.label, parent);
10014 		tabStop = false;
10015 		this.action = action;
10016 	}
10017 
10018 	version(custom_widgets)
10019 	override void defaultEventHandler_click(ClickEvent event) {
10020 		foreach(handler; action.triggered)
10021 			handler();
10022 	}
10023 
10024 	Action action;
10025 
10026 	override int maxWidth() { return toolbarIconSize; }
10027 	override int minWidth() { return toolbarIconSize; }
10028 	override int maxHeight() { return toolbarIconSize; }
10029 	override int minHeight() { return toolbarIconSize; }
10030 
10031 	version(custom_widgets)
10032 	override void paint(WidgetPainter painter) {
10033 	painter.drawThemed(delegate Rectangle (const Rectangle bounds) {
10034 		painter.outlineColor = Color.black;
10035 
10036 		// I want to get from 16 to 24. that's * 3 / 2
10037 		static assert(toolbarIconSize >= 16);
10038 		enum multiplier = toolbarIconSize / 8;
10039 		enum divisor = 2 + ((toolbarIconSize % 8) ? 1 : 0);
10040 		switch(action.iconId) {
10041 			case GenericIcons.New:
10042 				painter.fillColor = Color.white;
10043 				painter.drawPolygon(
10044 					Point(3, 2) * multiplier / divisor, Point(3, 13) * multiplier / divisor, Point(12, 13) * multiplier / divisor, Point(12, 6) * multiplier / divisor,
10045 					Point(8, 2) * multiplier / divisor, Point(8, 6) * multiplier / divisor, Point(12, 6) * multiplier / divisor, Point(8, 2) * multiplier / divisor,
10046 					Point(3, 2) * multiplier / divisor, Point(3, 13) * multiplier / divisor
10047 				);
10048 			break;
10049 			case GenericIcons.Save:
10050 				painter.fillColor = Color.white;
10051 				painter.outlineColor = Color.black;
10052 				painter.drawRectangle(Point(2, 2) * multiplier / divisor, Point(13, 13) * multiplier / divisor);
10053 
10054 				// the label
10055 				painter.drawRectangle(Point(4, 8) * multiplier / divisor, Point(11, 13) * multiplier / divisor);
10056 
10057 				// the slider
10058 				painter.fillColor = Color.black;
10059 				painter.outlineColor = Color.black;
10060 				painter.drawRectangle(Point(4, 3) * multiplier / divisor, Point(10, 6) * multiplier / divisor);
10061 
10062 				painter.fillColor = Color.white;
10063 				painter.outlineColor = Color.white;
10064 				// the disc window
10065 				painter.drawRectangle(Point(5, 3) * multiplier / divisor, Point(6, 5) * multiplier / divisor);
10066 			break;
10067 			case GenericIcons.Open:
10068 				painter.fillColor = Color.white;
10069 				painter.drawPolygon(
10070 					Point(4, 4) * multiplier / divisor, Point(4, 12) * multiplier / divisor, Point(13, 12) * multiplier / divisor, Point(13, 3) * multiplier / divisor,
10071 					Point(9, 3) * multiplier / divisor, Point(9, 4) * multiplier / divisor, Point(4, 4) * multiplier / divisor);
10072 				painter.drawPolygon(
10073 					Point(2, 6) * multiplier / divisor, Point(11, 6) * multiplier / divisor,
10074 					Point(12, 12) * multiplier / divisor, Point(4, 12) * multiplier / divisor,
10075 					Point(2, 6) * multiplier / divisor);
10076 				//painter.drawLine(Point(9, 6) * multiplier / divisor, Point(13, 7) * multiplier / divisor);
10077 			break;
10078 			case GenericIcons.Copy:
10079 				painter.fillColor = Color.white;
10080 				painter.drawRectangle(Point(3, 2) * multiplier / divisor, Point(9, 10) * multiplier / divisor);
10081 				painter.drawRectangle(Point(6, 5) * multiplier / divisor, Point(12, 13) * multiplier / divisor);
10082 			break;
10083 			case GenericIcons.Cut:
10084 				painter.fillColor = Color.transparent;
10085 				painter.outlineColor = getComputedStyle.foregroundColor();
10086 				painter.drawLine(Point(3, 2) * multiplier / divisor, Point(10, 9) * multiplier / divisor);
10087 				painter.drawLine(Point(4, 9) * multiplier / divisor, Point(11, 2) * multiplier / divisor);
10088 				painter.drawRectangle(Point(3, 9) * multiplier / divisor, Point(5, 13) * multiplier / divisor);
10089 				painter.drawRectangle(Point(9, 9) * multiplier / divisor, Point(11, 12) * multiplier / divisor);
10090 			break;
10091 			case GenericIcons.Paste:
10092 				painter.fillColor = Color.white;
10093 				painter.drawRectangle(Point(2, 3) * multiplier / divisor, Point(11, 11) * multiplier / divisor);
10094 				painter.drawRectangle(Point(6, 8) * multiplier / divisor, Point(13, 13) * multiplier / divisor);
10095 				painter.drawLine(Point(6, 2) * multiplier / divisor, Point(4, 5) * multiplier / divisor);
10096 				painter.drawLine(Point(6, 2) * multiplier / divisor, Point(9, 5) * multiplier / divisor);
10097 				painter.fillColor = Color.black;
10098 				painter.drawRectangle(Point(4, 5) * multiplier / divisor, Point(9, 6) * multiplier / divisor);
10099 			break;
10100 			case GenericIcons.Help:
10101 				painter.outlineColor = getComputedStyle.foregroundColor();
10102 				painter.drawText(Point(0, 0), "?", Point(width, height), TextAlignment.Center | TextAlignment.VerticalCenter);
10103 			break;
10104 			case GenericIcons.Undo:
10105 				painter.fillColor = Color.transparent;
10106 				painter.drawArc(Point(3, 4) * multiplier / divisor, 9 * multiplier / divisor, 9 * multiplier / divisor, 0, 360 * 64);
10107 				painter.outlineColor = Color.black;
10108 				painter.fillColor = Color.black;
10109 				painter.drawPolygon(
10110 					Point(4, 4) * multiplier / divisor,
10111 					Point(8, 2) * multiplier / divisor,
10112 					Point(8, 6) * multiplier / divisor,
10113 					Point(4, 4) * multiplier / divisor,
10114 				);
10115 			break;
10116 			case GenericIcons.Redo:
10117 				painter.fillColor = Color.transparent;
10118 				painter.drawArc(Point(3, 4) * multiplier / divisor, 9 * multiplier / divisor, 9 * multiplier / divisor, 0, 360 * 64);
10119 				painter.outlineColor = Color.black;
10120 				painter.fillColor = Color.black;
10121 				painter.drawPolygon(
10122 					Point(10, 4) * multiplier / divisor,
10123 					Point(6, 2) * multiplier / divisor,
10124 					Point(6, 6) * multiplier / divisor,
10125 					Point(10, 4) * multiplier / divisor,
10126 				);
10127 			break;
10128 			default:
10129 				painter.drawText(Point(0, 0), action.label, Point(width, height), TextAlignment.Center | TextAlignment.VerticalCenter);
10130 		}
10131 		return bounds;
10132 		});
10133 	}
10134 
10135 }
10136 
10137 
10138 ///
10139 class MenuBar : Widget {
10140 	MenuItem[] items;
10141 	Menu[] subMenus;
10142 
10143 	version(win32_widgets) {
10144 		HMENU handle;
10145 		///
10146 		this(Widget parent = null) {
10147 			super(parent);
10148 
10149 			handle = CreateMenu();
10150 			tabStop = false;
10151 		}
10152 	} else version(custom_widgets) {
10153 		///
10154 		this(Widget parent = null) {
10155 			tabStop = false; // these are selected some other way
10156 			super(parent);
10157 		}
10158 
10159 		mixin Padding!q{2};
10160 	} else static assert(false);
10161 
10162 	version(custom_widgets)
10163 	override void paint(WidgetPainter painter) {
10164 		draw3dFrame(this, painter, FrameStyle.risen, getComputedStyle().background.color);
10165 	}
10166 
10167 	///
10168 	MenuItem addItem(MenuItem item) {
10169 		this.addChild(item);
10170 		items ~= item;
10171 		version(win32_widgets) {
10172 			AppendMenuW(handle, MF_STRING, item.action is null ? 9000 : item.action.id, toWstringzInternal(item.label));
10173 		}
10174 		return item;
10175 	}
10176 
10177 
10178 	///
10179 	Menu addItem(Menu item) {
10180 
10181 		subMenus ~= item;
10182 
10183 		auto mbItem = new MenuItem(item.label, null);// this.parentWindow); // I'ma add the child down below so hopefully this isn't too insane
10184 
10185 		addChild(mbItem);
10186 		items ~= mbItem;
10187 
10188 		version(win32_widgets) {
10189 			AppendMenuW(handle, MF_STRING | MF_POPUP, cast(UINT) item.handle, toWstringzInternal(item.label));
10190 		} else version(custom_widgets) {
10191 			mbItem.defaultEventHandlers["mousedown"] = (Widget e, Event ev) {
10192 				item.popup(mbItem);
10193 			};
10194 		} else static assert(false);
10195 
10196 		return item;
10197 	}
10198 
10199 	override void recomputeChildLayout() {
10200 		.recomputeChildLayout!"width"(this);
10201 	}
10202 
10203 	override int maxHeight() { return defaultLineHeight + 4; }
10204 	override int minHeight() { return defaultLineHeight + 4; }
10205 }
10206 
10207 
10208 /**
10209 	Status bars appear at the bottom of a MainWindow.
10210 	They are made out of Parts, with a width and content.
10211 
10212 	They can have multiple parts or be in simple mode. FIXME: implement simple mode.
10213 
10214 
10215 	sb.parts[0].content = "Status bar text!";
10216 */
10217 class StatusBar : Widget {
10218 	private Part[] partsArray;
10219 	///
10220 	struct Parts {
10221 		@disable this();
10222 		this(StatusBar owner) { this.owner = owner; }
10223 		//@disable this(this);
10224 		///
10225 		@property int length() { return cast(int) owner.partsArray.length; }
10226 		private StatusBar owner;
10227 		private this(StatusBar owner, Part[] parts) {
10228 			this.owner.partsArray = parts;
10229 			this.owner = owner;
10230 		}
10231 		///
10232 		Part opIndex(int p) {
10233 			if(owner.partsArray.length == 0)
10234 				this ~= new StatusBar.Part(300);
10235 			return owner.partsArray[p];
10236 		}
10237 
10238 		///
10239 		Part opOpAssign(string op : "~" )(Part p) {
10240 			assert(owner.partsArray.length < 255);
10241 			p.owner = this.owner;
10242 			p.idx = cast(int) owner.partsArray.length;
10243 			owner.partsArray ~= p;
10244 			version(win32_widgets) {
10245 				int[256] pos;
10246 				int cpos = 0;
10247 				foreach(idx, part; owner.partsArray) {
10248 					if(part.width)
10249 						cpos += part.width;
10250 					else
10251 						cpos += 100;
10252 
10253 					if(idx + 1 == owner.partsArray.length)
10254 						pos[idx] = -1;
10255 					else
10256 						pos[idx] = cpos;
10257 				}
10258 				SendMessageW(owner.hwnd, WM_USER + 4 /*SB_SETPARTS*/, owner.partsArray.length, cast(size_t) pos.ptr);
10259 			} else version(custom_widgets) {
10260 				owner.redraw();
10261 			} else static assert(false);
10262 
10263 			return p;
10264 		}
10265 	}
10266 
10267 	private Parts _parts;
10268 	///
10269 	final @property Parts parts() {
10270 		return _parts;
10271 	}
10272 
10273 	///
10274 	static class Part {
10275 		int width;
10276 		StatusBar owner;
10277 
10278 		///
10279 		this(int w = 100) { width = w; }
10280 
10281 		private int idx;
10282 		private string _content;
10283 		///
10284 		@property string content() { return _content; }
10285 		///
10286 		@property void content(string s) {
10287 			version(win32_widgets) {
10288 				_content = s;
10289 				WCharzBuffer bfr = WCharzBuffer(s);
10290 				SendMessageW(owner.hwnd, SB_SETTEXT, idx, cast(LPARAM) bfr.ptr);
10291 			} else version(custom_widgets) {
10292 				if(_content != s) {
10293 					_content = s;
10294 					owner.redraw();
10295 				}
10296 			} else static assert(false);
10297 		}
10298 	}
10299 	string simpleModeContent;
10300 	bool inSimpleMode;
10301 
10302 
10303 	///
10304 	this(Widget parent) {
10305 		super(null); // FIXME
10306 		_parts = Parts(this);
10307 		tabStop = false;
10308 		version(win32_widgets) {
10309 			parentWindow = parent.parentWindow;
10310 			createWin32Window(this, "msctls_statusbar32"w, "", 0);
10311 
10312 			RECT rect;
10313 			GetWindowRect(hwnd, &rect);
10314 			idealHeight = rect.bottom - rect.top;
10315 			assert(idealHeight);
10316 		} else version(custom_widgets) {
10317 		} else static assert(false);
10318 	}
10319 
10320 	version(win32_widgets)
10321 	override protected void dpiChanged() {
10322 		RECT rect;
10323 		GetWindowRect(hwnd, &rect);
10324 		idealHeight = rect.bottom - rect.top;
10325 		assert(idealHeight);
10326 	}
10327 
10328 	version(custom_widgets)
10329 	override void paint(WidgetPainter painter) {
10330 		auto cs = getComputedStyle();
10331 		this.draw3dFrame(painter, FrameStyle.sunk, cs.background.color);
10332 		int cpos = 0;
10333 		int remainingLength = this.width;
10334 		foreach(idx, part; this.partsArray) {
10335 			auto partWidth = part.width ? part.width : ((idx + 1 == this.partsArray.length) ? remainingLength : 100);
10336 			painter.setClipRectangle(Point(cpos, 0), partWidth, height);
10337 			draw3dFrame(cpos, 0, partWidth, height, painter, FrameStyle.sunk, cs.background.color);
10338 			painter.setClipRectangle(Point(cpos + 2, 2), partWidth - 4, height - 4);
10339 
10340 			painter.outlineColor = cs.foregroundColor();
10341 			painter.fillColor = cs.foregroundColor();
10342 
10343 			painter.drawText(Point(cpos + 4, 0), part.content, Point(width, height), TextAlignment.VerticalCenter);
10344 			cpos += partWidth;
10345 			remainingLength -= partWidth;
10346 		}
10347 	}
10348 
10349 
10350 	version(win32_widgets) {
10351 		private int idealHeight;
10352 		override int maxHeight() { return idealHeight; }
10353 		override int minHeight() { return idealHeight; }
10354 	} else version(custom_widgets) {
10355 		override int maxHeight() { return defaultLineHeight + 4; }
10356 		override int minHeight() { return defaultLineHeight + 4; }
10357 	} else static assert(false);
10358 }
10359 
10360 /// Displays an in-progress indicator without known values
10361 version(none)
10362 class IndefiniteProgressBar : Widget {
10363 	version(win32_widgets)
10364 	this(Widget parent) {
10365 		super(parent);
10366 		createWin32Window(this, "msctls_progress32"w, "", 8 /* PBS_MARQUEE */);
10367 		tabStop = false;
10368 	}
10369 	override int minHeight() { return 10; }
10370 }
10371 
10372 /// A progress bar with a known endpoint and completion amount
10373 class ProgressBar : Widget {
10374 	/++
10375 		History:
10376 			Added March 16, 2022 (dub v10.7)
10377 	+/
10378 	this(int min, int max, Widget parent) {
10379 		this(parent);
10380 		setRange(cast(ushort) min, cast(ushort) max); // FIXME
10381 	}
10382 	this(Widget parent) {
10383 		version(win32_widgets) {
10384 			super(parent);
10385 			createWin32Window(this, "msctls_progress32"w, "", 0);
10386 			tabStop = false;
10387 		} else version(custom_widgets) {
10388 			super(parent);
10389 			max = 100;
10390 			step = 10;
10391 			tabStop = false;
10392 		} else static assert(0);
10393 	}
10394 
10395 	version(custom_widgets)
10396 	override void paint(WidgetPainter painter) {
10397 		auto cs = getComputedStyle();
10398 		this.draw3dFrame(painter, FrameStyle.sunk, cs.background.color);
10399 		painter.fillColor = cs.progressBarColor;
10400 		painter.drawRectangle(Point(0, 0), width * current / max, height);
10401 	}
10402 
10403 
10404 	version(custom_widgets) {
10405 		int current;
10406 		int max;
10407 		int step;
10408 	}
10409 
10410 	///
10411 	void advanceOneStep() {
10412 		version(win32_widgets)
10413 			SendMessageW(hwnd, PBM_STEPIT, 0, 0);
10414 		else version(custom_widgets)
10415 			addToPosition(step);
10416 		else static assert(false);
10417 	}
10418 
10419 	///
10420 	void setStepIncrement(int increment) {
10421 		version(win32_widgets)
10422 			SendMessageW(hwnd, PBM_SETSTEP, increment, 0);
10423 		else version(custom_widgets)
10424 			step = increment;
10425 		else static assert(false);
10426 	}
10427 
10428 	///
10429 	void addToPosition(int amount) {
10430 		version(win32_widgets)
10431 			SendMessageW(hwnd, PBM_DELTAPOS, amount, 0);
10432 		else version(custom_widgets)
10433 			setPosition(current + amount);
10434 		else static assert(false);
10435 	}
10436 
10437 	///
10438 	void setPosition(int pos) {
10439 		version(win32_widgets)
10440 			SendMessageW(hwnd, PBM_SETPOS, pos, 0);
10441 		else version(custom_widgets) {
10442 			current = pos;
10443 			if(current > max)
10444 				current = max;
10445 			redraw();
10446 		}
10447 		else static assert(false);
10448 	}
10449 
10450 	///
10451 	void setRange(ushort min, ushort max) {
10452 		version(win32_widgets)
10453 			SendMessageW(hwnd, PBM_SETRANGE, 0, MAKELONG(min, max));
10454 		else version(custom_widgets) {
10455 			this.max = max;
10456 		}
10457 		else static assert(false);
10458 	}
10459 
10460 	override int minHeight() { return 10; }
10461 }
10462 
10463 version(custom_widgets)
10464 private void extractWindowsStyleLabel(scope const char[] label, out string thisLabel, out dchar thisAccelerator) {
10465 	thisLabel.reserve(label.length);
10466 	bool justSawAmpersand;
10467 	foreach(ch; label) {
10468 		if(justSawAmpersand) {
10469 			justSawAmpersand = false;
10470 			if(ch == '&') {
10471 				goto plain;
10472 			}
10473 			thisAccelerator = ch;
10474 		} else {
10475 			if(ch == '&') {
10476 				justSawAmpersand = true;
10477 				continue;
10478 			}
10479 			plain:
10480 			thisLabel ~= ch;
10481 		}
10482 	}
10483 }
10484 
10485 /++
10486 	Creates the fieldset (also known as a group box) with the given label. A fieldset is generally used a container for mutually exclusive [Radiobox]s.
10487 
10488 
10489 	Please note that the ampersand (&) character gets special treatment as described on this page https://docs.microsoft.com/en-us/windows/win32/menurc/common-control-parameters?redirectedfrom=MSDN
10490 
10491 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
10492 
10493 	History:
10494 		The ampersand behavior was always the case on Windows, but it wasn't until June 15, 2021 when Linux was changed to match it and the documentation updated to reflect it.
10495 +/
10496 class Fieldset : Widget {
10497 	// FIXME: on Windows,it doesn't draw the background on the label
10498 	// on X, it doesn't fix the clipping rectangle for it
10499 	version(win32_widgets)
10500 		override int paddingTop() { return defaultLineHeight; }
10501 	else version(custom_widgets)
10502 		override int paddingTop() { return defaultLineHeight + 2; }
10503 	else static assert(false);
10504 	override int paddingBottom() { return 6; }
10505 	override int paddingLeft() { return 6; }
10506 	override int paddingRight() { return 6; }
10507 
10508 	override int marginLeft() { return 6; }
10509 	override int marginRight() { return 6; }
10510 	override int marginTop() { return 2; }
10511 	override int marginBottom() { return 2; }
10512 
10513 	string legend;
10514 
10515 	version(custom_widgets) private dchar accelerator;
10516 
10517 	this(string legend, Widget parent) {
10518 		version(win32_widgets) {
10519 			super(parent);
10520 			this.legend = legend;
10521 			createWin32Window(this, "button"w, legend, BS_GROUPBOX);
10522 			tabStop = false;
10523 		} else version(custom_widgets) {
10524 			super(parent);
10525 			tabStop = false;
10526 
10527 			legend.extractWindowsStyleLabel(this.legend, this.accelerator);
10528 		} else static assert(0);
10529 	}
10530 
10531 	version(custom_widgets)
10532 	override void paint(WidgetPainter painter) {
10533 		painter.fillColor = Color.transparent;
10534 		auto cs = getComputedStyle();
10535 		painter.pen = Pen(cs.foregroundColor, 1);
10536 		painter.drawRectangle(Point(0, defaultLineHeight / 2), width, height - defaultLineHeight / 2);
10537 
10538 		auto tx = painter.textSize(legend);
10539 		painter.outlineColor = Color.transparent;
10540 
10541 		static if(UsingSimpledisplayX11) {
10542 			painter.fillColor = getComputedStyle().windowBackgroundColor;
10543 			painter.drawRectangle(Point(8, 0), tx.width, tx.height);
10544 		} else version(Windows) {
10545 			auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
10546 			painter.drawRectangle(Point(8, -tx.height/2), tx.width, tx.height);
10547 			SelectObject(painter.impl.hdc, b);
10548 		} else static assert(0);
10549 		painter.outlineColor = cs.foregroundColor;
10550 		painter.drawText(Point(8, 0), legend);
10551 	}
10552 
10553 	override int maxHeight() {
10554 		auto m = paddingTop() + paddingBottom();
10555 		foreach(child; children) {
10556 			auto mh = child.maxHeight();
10557 			if(mh == int.max)
10558 				return int.max;
10559 			m += mh;
10560 			m += child.marginBottom();
10561 			m += child.marginTop();
10562 		}
10563 		m += 6;
10564 		if(m < minHeight)
10565 			return minHeight;
10566 		return m;
10567 	}
10568 
10569 	override int minHeight() {
10570 		auto m = paddingTop() + paddingBottom();
10571 		foreach(child; children) {
10572 			m += child.minHeight();
10573 			m += child.marginBottom();
10574 			m += child.marginTop();
10575 		}
10576 		return m + 6;
10577 	}
10578 }
10579 
10580 /++
10581 	$(IMG //arsdnet.net/minigui-screenshots/windows/Fieldset.png, A box saying "baby will" with three round buttons inside it for the options of "eat", "cry", and "sleep")
10582 	$(IMG //arsdnet.net/minigui-screenshots/linux/Fieldset.png, Same thing, but in the default Linux theme.)
10583 +/
10584 version(minigui_screenshots)
10585 @Screenshot("Fieldset")
10586 unittest {
10587 	auto window = new Window(200, 100);
10588 	auto set = new Fieldset("Baby will", window);
10589 	auto option1 = new Radiobox("Eat", set);
10590 	auto option2 = new Radiobox("Cry", set);
10591 	auto option3 = new Radiobox("Sleep", set);
10592 	window.loop();
10593 }
10594 
10595 /// Draws a line
10596 class HorizontalRule : Widget {
10597 	mixin Margin!q{ 2 };
10598 	override int minHeight() { return 2; }
10599 	override int maxHeight() { return 2; }
10600 
10601 	///
10602 	this(Widget parent) {
10603 		super(parent);
10604 	}
10605 
10606 	override void paint(WidgetPainter painter) {
10607 		auto cs = getComputedStyle();
10608 		painter.outlineColor = cs.darkAccentColor;
10609 		painter.drawLine(Point(0, 0), Point(width, 0));
10610 		painter.outlineColor = cs.lightAccentColor;
10611 		painter.drawLine(Point(0, 1), Point(width, 1));
10612 	}
10613 }
10614 
10615 version(minigui_screenshots)
10616 @Screenshot("HorizontalRule")
10617 /++
10618 	$(IMG //arsdnet.net/minigui-screenshots/linux/HorizontalRule.png, Same thing, but in the default Linux theme.)
10619 
10620 +/
10621 unittest {
10622 	auto window = new Window(200, 100);
10623 	auto above = new TextLabel("Above the line", TextAlignment.Left, window);
10624 	new HorizontalRule(window);
10625 	auto below = new TextLabel("Below the line", TextAlignment.Left, window);
10626 	window.loop();
10627 }
10628 
10629 /// ditto
10630 class VerticalRule : Widget {
10631 	mixin Margin!q{ 2 };
10632 	override int minWidth() { return 2; }
10633 	override int maxWidth() { return 2; }
10634 
10635 	///
10636 	this(Widget parent) {
10637 		super(parent);
10638 	}
10639 
10640 	override void paint(WidgetPainter painter) {
10641 		auto cs = getComputedStyle();
10642 		painter.outlineColor = cs.darkAccentColor;
10643 		painter.drawLine(Point(0, 0), Point(0, height));
10644 		painter.outlineColor = cs.lightAccentColor;
10645 		painter.drawLine(Point(1, 0), Point(1, height));
10646 	}
10647 }
10648 
10649 
10650 ///
10651 class Menu : Window {
10652 	void remove() {
10653 		foreach(i, child; parentWindow.children)
10654 			if(child is this) {
10655 				parentWindow._children = parentWindow._children[0 .. i] ~ parentWindow._children[i + 1 .. $];
10656 				break;
10657 			}
10658 		parentWindow.redraw();
10659 
10660 		parentWindow.releaseMouseCapture();
10661 	}
10662 
10663 	///
10664 	void addSeparator() {
10665 		version(win32_widgets)
10666 			AppendMenu(handle, MF_SEPARATOR, 0, null);
10667 		else version(custom_widgets)
10668 			auto hr = new HorizontalRule(this);
10669 		else static assert(0);
10670 	}
10671 
10672 	override int paddingTop() { return 4; }
10673 	override int paddingBottom() { return 4; }
10674 	override int paddingLeft() { return 2; }
10675 	override int paddingRight() { return 2; }
10676 
10677 	version(win32_widgets) {}
10678 	else version(custom_widgets) {
10679 		SimpleWindow dropDown;
10680 		Widget menuParent;
10681 		void popup(Widget parent, int offsetX = 0, int offsetY = int.min) {
10682 			this.menuParent = parent;
10683 
10684 			int w = 150;
10685 			int h = paddingTop + paddingBottom;
10686 			if(this.children.length) {
10687 				// hacking it to get the ideal height out of recomputeChildLayout
10688 				this.width = w;
10689 				this.height = h;
10690 				this.recomputeChildLayout();
10691 				h = this.children[$-1].y + this.children[$-1].height + this.children[$-1].marginBottom;
10692 				h += paddingBottom;
10693 
10694 				h -= 2; // total hack, i just like the way it looks a bit tighter even though technically MenuItem reserves some space to center in normal circumstances
10695 			}
10696 
10697 			if(offsetY == int.min)
10698 				offsetY = parent.defaultLineHeight;
10699 
10700 			auto coord = parent.globalCoordinates();
10701 			dropDown.moveResize(coord.x + offsetX, coord.y + offsetY, w, h);
10702 			this.x = 0;
10703 			this.y = 0;
10704 			this.width = dropDown.width;
10705 			this.height = dropDown.height;
10706 			this.drawableWindow = dropDown;
10707 			this.recomputeChildLayout();
10708 
10709 			static if(UsingSimpledisplayX11)
10710 				XSync(XDisplayConnection.get, 0);
10711 
10712 			dropDown.visibilityChanged = (bool visible) {
10713 				if(visible) {
10714 					this.redraw();
10715 					dropDown.grabInput();
10716 				} else {
10717 					dropDown.releaseInputGrab();
10718 				}
10719 			};
10720 
10721 			dropDown.show();
10722 
10723 			clickListener = this.addEventListener((scope ClickEvent ev) {
10724 				unpopup();
10725 				// need to unlock asap just in case other user handlers block...
10726 				static if(UsingSimpledisplayX11)
10727 					flushGui();
10728 			}, true /* again for asap action */);
10729 		}
10730 
10731 		EventListener clickListener;
10732 	}
10733 	else static assert(false);
10734 
10735 	version(custom_widgets)
10736 	void unpopup() {
10737 		mouseLastOver = mouseLastDownOn = null;
10738 		dropDown.hide();
10739 		if(!menuParent.parentWindow.win.closed) {
10740 			if(auto maw = cast(MouseActivatedWidget) menuParent) {
10741 				maw.setDynamicState(DynamicState.depressed, false);
10742 				maw.setDynamicState(DynamicState.hover, false);
10743 				maw.redraw();
10744 			}
10745 			// menuParent.parentWindow.win.focus();
10746 		}
10747 		clickListener.disconnect();
10748 	}
10749 
10750 	MenuItem[] items;
10751 
10752 	///
10753 	MenuItem addItem(MenuItem item) {
10754 		addChild(item);
10755 		items ~= item;
10756 		version(win32_widgets) {
10757 			AppendMenuW(handle, MF_STRING, item.action is null ? 9000 : item.action.id, toWstringzInternal(item.label));
10758 		}
10759 		return item;
10760 	}
10761 
10762 	string label;
10763 
10764 	version(win32_widgets) {
10765 		HMENU handle;
10766 		///
10767 		this(string label, Widget parent) {
10768 			// not actually passing the parent since it effs up the drawing
10769 			super(cast(Widget) null);// parent);
10770 			this.label = label;
10771 			handle = CreatePopupMenu();
10772 		}
10773 	} else version(custom_widgets) {
10774 		///
10775 		this(string label, Widget parent) {
10776 
10777 			if(dropDown) {
10778 				dropDown.close();
10779 			}
10780 			dropDown = new SimpleWindow(
10781 				150, 4,
10782 				null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.dropdownMenu, WindowFlags.dontAutoShow, parent ? parent.parentWindow.win : null);
10783 
10784 			this.label = label;
10785 
10786 			super(dropDown);
10787 		}
10788 	} else static assert(false);
10789 
10790 	override int maxHeight() { return defaultLineHeight; }
10791 	override int minHeight() { return defaultLineHeight; }
10792 
10793 	version(custom_widgets)
10794 	override void paint(WidgetPainter painter) {
10795 		this.draw3dFrame(painter, FrameStyle.risen, getComputedStyle.background.color);
10796 	}
10797 }
10798 
10799 /++
10800 	A MenuItem belongs to a [Menu] - use [Menu.addItem] to add one - and calls an [Action] when it is clicked.
10801 +/
10802 class MenuItem : MouseActivatedWidget {
10803 	Menu submenu;
10804 
10805 	Action action;
10806 	string label;
10807 
10808 	override int paddingLeft() { return 4; }
10809 
10810 	override int maxHeight() { return defaultLineHeight + 4; }
10811 	override int minHeight() { return defaultLineHeight + 4; }
10812 	override int minWidth() { return defaultTextWidth(label) + 8 + scaleWithDpi(12); }
10813 	override int maxWidth() {
10814 		if(cast(MenuBar) parent) {
10815 			return minWidth();
10816 		}
10817 		return int.max;
10818 	}
10819 	/// This should ONLY be used if there is no associated action, for example, if the menu item is just a submenu.
10820 	this(string lbl, Widget parent = null) {
10821 		super(parent);
10822 		//label = lbl; // FIXME
10823 		foreach(char ch; lbl) // FIXME
10824 			if(ch != '&') // FIXME
10825 				label ~= ch; // FIXME
10826 		tabStop = false; // these are selected some other way
10827 	}
10828 
10829 	///
10830 	this(Action action, Widget parent = null) {
10831 		assert(action !is null);
10832 		this(action.label, parent);
10833 		this.action = action;
10834 		tabStop = false; // these are selected some other way
10835 	}
10836 
10837 	version(custom_widgets)
10838 	override void paint(WidgetPainter painter) {
10839 		auto cs = getComputedStyle();
10840 		if(dynamicState & DynamicState.depressed)
10841 			this.draw3dFrame(painter, FrameStyle.sunk, cs.background.color);
10842 		if(dynamicState & DynamicState.hover)
10843 			painter.outlineColor = cs.activeMenuItemColor;
10844 		else
10845 			painter.outlineColor = cs.foregroundColor;
10846 		painter.fillColor = Color.transparent;
10847 		painter.drawText(scaleWithDpi(Point(cast(MenuBar) this.parent ? 4 : 20, 0)), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
10848 		if(action && action.accelerator !is KeyEvent.init) {
10849 			painter.drawText(scaleWithDpi(Point(cast(MenuBar) this.parent ? 4 : 20, 0)), action.accelerator.toStr(), Point(width - 4, height), TextAlignment.Right | TextAlignment.VerticalCenter);
10850 
10851 		}
10852 	}
10853 
10854 	static class Style : Widget.Style {
10855 		override bool variesWithState(ulong dynamicStateFlags) {
10856 			return super.variesWithState(dynamicStateFlags) || (dynamicStateFlags & (DynamicState.depressed | DynamicState.hover));
10857 		}
10858 	}
10859 	mixin OverrideStyle!Style;
10860 
10861 	override void defaultEventHandler_triggered(Event event) {
10862 		if(action)
10863 		foreach(handler; action.triggered)
10864 			handler();
10865 
10866 		if(auto pmenu = cast(Menu) this.parent)
10867 			pmenu.remove();
10868 
10869 		super.defaultEventHandler_triggered(event);
10870 	}
10871 }
10872 
10873 version(win32_widgets)
10874 /// A "mouse activiated widget" is really just an abstract variant of button.
10875 class MouseActivatedWidget : Widget {
10876 	@property bool isChecked() {
10877 		assert(hwnd);
10878 		return SendMessageW(hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED;
10879 
10880 	}
10881 	@property void isChecked(bool state) {
10882 		assert(hwnd);
10883 		SendMessageW(hwnd, BM_SETCHECK, state ? BST_CHECKED : BST_UNCHECKED, 0);
10884 
10885 	}
10886 
10887 	override void handleWmCommand(ushort cmd, ushort id) {
10888 		if(cmd == 0) {
10889 			auto event = new Event(EventType.triggered, this);
10890 			event.dispatch();
10891 		}
10892 	}
10893 
10894 	this(Widget parent) {
10895 		super(parent);
10896 	}
10897 }
10898 else version(custom_widgets)
10899 /// ditto
10900 class MouseActivatedWidget : Widget {
10901 	@property bool isChecked() { return isChecked_; }
10902 	@property bool isChecked(bool b) { return isChecked_ = b; }
10903 
10904 	private bool isChecked_;
10905 
10906 	this(Widget parent) {
10907 		super(parent);
10908 
10909 		addEventListener((MouseDownEvent ev) {
10910 			if(ev.button == MouseButton.left) {
10911 				setDynamicState(DynamicState.depressed, true);
10912 				setDynamicState(DynamicState.hover, true);
10913 				redraw();
10914 			}
10915 		});
10916 
10917 		addEventListener((MouseUpEvent ev) {
10918 			if(ev.button == MouseButton.left) {
10919 				setDynamicState(DynamicState.depressed, false);
10920 				setDynamicState(DynamicState.hover, false);
10921 				redraw();
10922 			}
10923 		});
10924 
10925 		addEventListener((MouseMoveEvent mme) {
10926 			if(!(mme.state & ModifierState.leftButtonDown)) {
10927 				if(dynamicState_ & DynamicState.depressed) {
10928 					setDynamicState(DynamicState.depressed, false);
10929 					redraw();
10930 				}
10931 			}
10932 		});
10933 	}
10934 
10935 	override void defaultEventHandler_focus(Event ev) {
10936 		super.defaultEventHandler_focus(ev);
10937 		this.redraw();
10938 	}
10939 	override void defaultEventHandler_blur(Event ev) {
10940 		super.defaultEventHandler_blur(ev);
10941 		setDynamicState(DynamicState.depressed, false);
10942 		this.redraw();
10943 	}
10944 	override void defaultEventHandler_keydown(KeyDownEvent ev) {
10945 		super.defaultEventHandler_keydown(ev);
10946 		if(ev.key == Key.Space || ev.key == Key.Enter || ev.key == Key.PadEnter) {
10947 			setDynamicState(DynamicState.depressed, true);
10948 			setDynamicState(DynamicState.hover, true);
10949 			this.redraw();
10950 		}
10951 	}
10952 	override void defaultEventHandler_keyup(KeyUpEvent ev) {
10953 		super.defaultEventHandler_keyup(ev);
10954 		if(!(dynamicState & DynamicState.depressed))
10955 			return;
10956 		setDynamicState(DynamicState.depressed, false);
10957 		setDynamicState(DynamicState.hover, false);
10958 		this.redraw();
10959 
10960 		auto event = new Event(EventType.triggered, this);
10961 		event.sendDirectly();
10962 	}
10963 	override void defaultEventHandler_click(ClickEvent ev) {
10964 		super.defaultEventHandler_click(ev);
10965 		if(ev.button == MouseButton.left) {
10966 			auto event = new Event(EventType.triggered, this);
10967 			event.sendDirectly();
10968 		}
10969 	}
10970 
10971 }
10972 else static assert(false);
10973 
10974 /*
10975 /++
10976 	Like the tablet thing, it would have a label, a description, and a switch slider thingy.
10977 
10978 	Basically the same as a checkbox.
10979 +/
10980 class OnOffSwitch : MouseActivatedWidget {
10981 
10982 }
10983 */
10984 
10985 /++
10986 	History:
10987 		Added June 15, 2021 (dub v10.1)
10988 +/
10989 struct ImageLabel {
10990 	/++
10991 		Defines a label+image combo used by some widgets.
10992 
10993 		If you provide just a text label, that is all the widget will try to
10994 		display. Or just an image will display just that. If you provide both,
10995 		it may display both text and image side by side or display the image
10996 		and offer text on an input event depending on the widget.
10997 
10998 		History:
10999 			The `alignment` parameter was added on September 27, 2021
11000 	+/
11001 	this(string label, TextAlignment alignment = TextAlignment.Center) {
11002 		this.label = label;
11003 		this.displayFlags = DisplayFlags.displayText;
11004 		this.alignment = alignment;
11005 	}
11006 
11007 	/// ditto
11008 	this(string label, MemoryImage image, TextAlignment alignment = TextAlignment.Center) {
11009 		this.label = label;
11010 		this.image = image;
11011 		this.displayFlags = DisplayFlags.displayText | DisplayFlags.displayImage;
11012 		this.alignment = alignment;
11013 	}
11014 
11015 	/// ditto
11016 	this(MemoryImage image, TextAlignment alignment = TextAlignment.Center) {
11017 		this.image = image;
11018 		this.displayFlags = DisplayFlags.displayImage;
11019 		this.alignment = alignment;
11020 	}
11021 
11022 	/// ditto
11023 	this(string label, MemoryImage image, int displayFlags, TextAlignment alignment = TextAlignment.Center) {
11024 		this.label = label;
11025 		this.image = image;
11026 		this.alignment = alignment;
11027 		this.displayFlags = displayFlags;
11028 	}
11029 
11030 	string label;
11031 	MemoryImage image;
11032 
11033 	enum DisplayFlags {
11034 		displayText = 1 << 0,
11035 		displayImage = 1 << 1,
11036 	}
11037 
11038 	int displayFlags = DisplayFlags.displayText | DisplayFlags.displayImage;
11039 
11040 	TextAlignment alignment;
11041 }
11042 
11043 /++
11044 	A basic checked or not checked box with an attached label.
11045 
11046 
11047 	Please note that the ampersand (&) character gets special treatment as described on this page https://docs.microsoft.com/en-us/windows/win32/menurc/common-control-parameters?redirectedfrom=MSDN
11048 
11049 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
11050 
11051 	History:
11052 		The ampersand behavior was always the case on Windows, but it wasn't until June 15, 2021 when Linux was changed to match it and the documentation updated to reflect it.
11053 +/
11054 class Checkbox : MouseActivatedWidget {
11055 	version(win32_widgets) {
11056 		override int maxHeight() { return scaleWithDpi(16); }
11057 		override int minHeight() { return scaleWithDpi(16); }
11058 	} else version(custom_widgets) {
11059 		override int maxHeight() { return defaultLineHeight; }
11060 		override int minHeight() { return defaultLineHeight; }
11061 	} else static assert(0);
11062 
11063 	override int marginLeft() { return 4; }
11064 
11065 	override int flexBasisWidth() { return 24 + cast(int) label.length * 7; }
11066 
11067 	/++
11068 		Just an alias because I keep typing checked out of web habit.
11069 
11070 		History:
11071 			Added May 31, 2021
11072 	+/
11073 	alias checked = isChecked;
11074 
11075 	private string label;
11076 	private dchar accelerator;
11077 
11078 	/++
11079 	+/
11080 	this(string label, Widget parent) {
11081 		this(ImageLabel(label), Appearance.checkbox, parent);
11082 	}
11083 
11084 	/// ditto
11085 	this(string label, Appearance appearance, Widget parent) {
11086 		this(ImageLabel(label), appearance, parent);
11087 	}
11088 
11089 	/++
11090 		Changes the look and may change the ideal size of the widget without changing its behavior. The precise look is platform-specific.
11091 
11092 		History:
11093 			Added June 29, 2021 (dub v10.2)
11094 	+/
11095 	enum Appearance {
11096 		checkbox, /// a normal checkbox
11097 		pushbutton, /// a button that is showed as pushed when checked and up when unchecked. Similar to the bold button in a toolbar in Wordpad.
11098 		//sliderswitch,
11099 	}
11100 	private Appearance appearance;
11101 
11102 	/// ditto
11103 	private this(ImageLabel label, Appearance appearance, Widget parent) {
11104 		super(parent);
11105 		version(win32_widgets) {
11106 			this.label = label.label;
11107 
11108 			uint extraStyle;
11109 			final switch(appearance) {
11110 				case Appearance.checkbox:
11111 				break;
11112 				case Appearance.pushbutton:
11113 					extraStyle |= BS_PUSHLIKE;
11114 				break;
11115 			}
11116 
11117 			createWin32Window(this, "button"w, label.label, BS_CHECKBOX | extraStyle);
11118 		} else version(custom_widgets) {
11119 			label.label.extractWindowsStyleLabel(this.label, this.accelerator);
11120 		} else static assert(0);
11121 	}
11122 
11123 	version(custom_widgets)
11124 	override void paint(WidgetPainter painter) {
11125 		auto cs = getComputedStyle();
11126 		if(isFocused()) {
11127 			painter.pen = Pen(Color.black, 1, Pen.Style.Dotted);
11128 			painter.fillColor = cs.windowBackgroundColor;
11129 			painter.drawRectangle(Point(0, 0), width, height);
11130 			painter.pen = Pen(Color.black, 1, Pen.Style.Solid);
11131 		} else {
11132 			painter.pen = Pen(cs.windowBackgroundColor, 1, Pen.Style.Solid);
11133 			painter.fillColor = cs.windowBackgroundColor;
11134 			painter.drawRectangle(Point(0, 0), width, height);
11135 		}
11136 
11137 
11138 		enum buttonSize = 16;
11139 
11140 		painter.outlineColor = Color.black;
11141 		painter.fillColor = Color.white;
11142 		painter.drawRectangle(scaleWithDpi(Point(2, 2)), scaleWithDpi(buttonSize - 2), scaleWithDpi(buttonSize - 2));
11143 
11144 		if(isChecked) {
11145 			painter.pen = Pen(Color.black, 2);
11146 			// I'm using height so the checkbox is square
11147 			enum padding = 5;
11148 			painter.drawLine(scaleWithDpi(Point(padding, padding)), scaleWithDpi(Point(buttonSize - (padding-2), buttonSize - (padding-2))));
11149 			painter.drawLine(scaleWithDpi(Point(buttonSize-(padding-2), padding)), scaleWithDpi(Point(padding, buttonSize - (padding-2))));
11150 
11151 			painter.pen = Pen(Color.black, 1);
11152 		}
11153 
11154 		if(label !is null) {
11155 			painter.outlineColor = cs.foregroundColor();
11156 			painter.fillColor = cs.foregroundColor();
11157 
11158 			// FIXME: should prolly just align the baseline or something
11159 			painter.drawText(scaleWithDpi(Point(buttonSize + 4, 2)), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
11160 		}
11161 	}
11162 
11163 	override void defaultEventHandler_triggered(Event ev) {
11164 		isChecked = !isChecked;
11165 
11166 		this.emit!(ChangeEvent!bool)(&isChecked);
11167 
11168 		redraw();
11169 	}
11170 
11171 	/// Emits a change event with the checked state
11172 	mixin Emits!(ChangeEvent!bool);
11173 }
11174 
11175 /// Adds empty space to a layout.
11176 class VerticalSpacer : Widget {
11177 	///
11178 	this(Widget parent) {
11179 		super(parent);
11180 	}
11181 }
11182 
11183 /// ditto
11184 class HorizontalSpacer : Widget {
11185 	///
11186 	this(Widget parent) {
11187 		super(parent);
11188 		this.tabStop = false;
11189 	}
11190 }
11191 
11192 
11193 /++
11194 	Creates a radio button with an associated label. These are usually put inside a [Fieldset].
11195 
11196 
11197 	Please note that the ampersand (&) character gets special treatment as described on this page https://docs.microsoft.com/en-us/windows/win32/menurc/common-control-parameters?redirectedfrom=MSDN
11198 
11199 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
11200 
11201 	History:
11202 		The ampersand behavior was always the case on Windows, but it wasn't until June 15, 2021 when Linux was changed to match it and the documentation updated to reflect it.
11203 +/
11204 class Radiobox : MouseActivatedWidget {
11205 
11206 	version(win32_widgets) {
11207 		override int maxHeight() { return scaleWithDpi(16); }
11208 		override int minHeight() { return scaleWithDpi(16); }
11209 	} else version(custom_widgets) {
11210 		override int maxHeight() { return defaultLineHeight; }
11211 		override int minHeight() { return defaultLineHeight; }
11212 	} else static assert(0);
11213 
11214 	override int marginLeft() { return 4; }
11215 
11216 	// FIXME: make a label getter
11217 	private string label;
11218 	private dchar accelerator;
11219 
11220 	version(win32_widgets)
11221 	this(string label, Widget parent) {
11222 		super(parent);
11223 		this.label = label;
11224 		createWin32Window(this, "button"w, label, BS_AUTORADIOBUTTON);
11225 	}
11226 	else version(custom_widgets)
11227 	this(string label, Widget parent) {
11228 		super(parent);
11229 		label.extractWindowsStyleLabel(this.label, this.accelerator);
11230 		height = 16;
11231 		width = height + 4 + cast(int) label.length * 16;
11232 	}
11233 	else static assert(false);
11234 
11235 	version(custom_widgets)
11236 	override void paint(WidgetPainter painter) {
11237 		auto cs = getComputedStyle();
11238 		if(isFocused) {
11239 			painter.fillColor = cs.windowBackgroundColor;
11240 			painter.pen = Pen(Color.black, 1, Pen.Style.Dotted);
11241 		} else {
11242 			painter.fillColor = cs.windowBackgroundColor;
11243 			painter.outlineColor = cs.windowBackgroundColor;
11244 		}
11245 		painter.drawRectangle(Point(0, 0), width, height);
11246 
11247 		painter.pen = Pen(Color.black, 1, Pen.Style.Solid);
11248 
11249 		enum buttonSize = 16;
11250 
11251 		painter.outlineColor = Color.black;
11252 		painter.fillColor = Color.white;
11253 		painter.drawEllipse(scaleWithDpi(Point(2, 2)), scaleWithDpi(Point(buttonSize - 2, buttonSize - 2)));
11254 		if(isChecked) {
11255 			painter.outlineColor = Color.black;
11256 			painter.fillColor = Color.black;
11257 			// I'm using height so the checkbox is square
11258 			painter.drawEllipse(scaleWithDpi(Point(5, 5)), scaleWithDpi(Point(buttonSize - 5, buttonSize - 5)));
11259 		}
11260 
11261 		painter.outlineColor = cs.foregroundColor();
11262 		painter.fillColor = cs.foregroundColor();
11263 
11264 		painter.drawText(scaleWithDpi(Point(buttonSize + 4, 0)), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
11265 	}
11266 
11267 
11268 	override void defaultEventHandler_triggered(Event ev) {
11269 		isChecked = true;
11270 
11271 		if(this.parent) {
11272 			foreach(child; this.parent.children) {
11273 				if(child is this) continue;
11274 				if(auto rb = cast(Radiobox) child) {
11275 					rb.isChecked = false;
11276 					rb.emit!(ChangeEvent!bool)(&rb.isChecked);
11277 					rb.redraw();
11278 				}
11279 			}
11280 		}
11281 
11282 		this.emit!(ChangeEvent!bool)(&this.isChecked);
11283 
11284 		redraw();
11285 	}
11286 
11287 	/// Emits a change event with if it is checked. Note that when you select one in a group, that one will emit changed with value == true, and the previous one will emit changed with value == false right before. A button group may catch this and change the event.
11288 	mixin Emits!(ChangeEvent!bool);
11289 }
11290 
11291 
11292 /++
11293 	Creates a push button with unbounded size. When it is clicked, it emits a `triggered` event.
11294 
11295 
11296 	Please note that the ampersand (&) character gets special treatment as described on this page https://docs.microsoft.com/en-us/windows/win32/menurc/common-control-parameters?redirectedfrom=MSDN
11297 
11298 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
11299 
11300 	History:
11301 		The ampersand behavior was always the case on Windows, but it wasn't until June 15, 2021 when Linux was changed to match it and the documentation updated to reflect it.
11302 +/
11303 class Button : MouseActivatedWidget {
11304 	override int heightStretchiness() { return 3; }
11305 	override int widthStretchiness() { return 3; }
11306 
11307 	/++
11308 		If true, this button will emit trigger events on double (and other quick events, if added) click events as well as on normal single click events.
11309 
11310 		History:
11311 			Added July 2, 2021
11312 	+/
11313 	public bool triggersOnMultiClick;
11314 
11315 	private string label_;
11316 	private TextAlignment alignment;
11317 	private dchar accelerator;
11318 
11319 	///
11320 	string label() { return label_; }
11321 	///
11322 	void label(string l) {
11323 		label_ = l;
11324 		version(win32_widgets) {
11325 			WCharzBuffer bfr = WCharzBuffer(l);
11326 			SetWindowTextW(hwnd, bfr.ptr);
11327 		} else version(custom_widgets) {
11328 			redraw();
11329 		}
11330 	}
11331 
11332 	override void defaultEventHandler_dblclick(DoubleClickEvent ev) {
11333 		super.defaultEventHandler_dblclick(ev);
11334 		if(triggersOnMultiClick) {
11335 			if(ev.button == MouseButton.left) {
11336 				auto event = new Event(EventType.triggered, this);
11337 				event.sendDirectly();
11338 			}
11339 		}
11340 	}
11341 
11342 	private Sprite sprite;
11343 	private int displayFlags;
11344 
11345 	/++
11346 		Creates a push button with the given label, which may be an image or some text.
11347 
11348 		Bugs:
11349 			If the image is bigger than the button, it may not be displayed in the right position on Linux.
11350 
11351 		History:
11352 			The [ImageLabel] overload was added on June 21, 2021 (dub v10.1).
11353 
11354 			The button with label and image will respect requests to show both on Windows as
11355 			of March 28, 2022 iff you provide a manifest file to opt into common controls v6.
11356 	+/
11357 	this(ImageLabel label, Widget parent) {
11358 		version(win32_widgets) {
11359 			// FIXME: use ideal button size instead
11360 			width = 50;
11361 			height = 30;
11362 			super(parent);
11363 
11364 			// BS_BITMAP is set when we want image only, so checking for exactly that combination
11365 			enum imgFlags = ImageLabel.DisplayFlags.displayImage | ImageLabel.DisplayFlags.displayText;
11366 			auto extraStyle = ((label.displayFlags & imgFlags) == ImageLabel.DisplayFlags.displayImage) ? BS_BITMAP : 0;
11367 
11368 			// the transparent thing can mess up borders in other cases, so only going to keep it for bitmap things where it might matter
11369 			createWin32Window(this, "button"w, label.label, BS_PUSHBUTTON | extraStyle, extraStyle == BS_BITMAP ? WS_EX_TRANSPARENT : 0 );
11370 
11371 			if(label.image) {
11372 				sprite = Sprite.fromMemoryImage(parentWindow.win, label.image, true);
11373 
11374 				SendMessageW(hwnd, BM_SETIMAGE, IMAGE_BITMAP, cast(LPARAM) sprite.nativeHandle);
11375 			}
11376 
11377 			this.label = label.label;
11378 		} else version(custom_widgets) {
11379 			width = 50;
11380 			height = 30;
11381 			super(parent);
11382 
11383 			label.label.extractWindowsStyleLabel(this.label_, this.accelerator);
11384 
11385 			if(label.image) {
11386 				this.sprite = Sprite.fromMemoryImage(parentWindow.win, label.image);
11387 				this.displayFlags = label.displayFlags;
11388 			}
11389 
11390 			this.alignment = label.alignment;
11391 		}
11392 	}
11393 
11394 	///
11395 	this(string label, Widget parent) {
11396 		this(ImageLabel(label), parent);
11397 	}
11398 
11399 	override int minHeight() { return defaultLineHeight + 4; }
11400 
11401 	static class Style : Widget.Style {
11402 		override WidgetBackground background() {
11403 			auto cs = widget.getComputedStyle(); // FIXME: this is potentially recursive
11404 
11405 			auto pressed = DynamicState.depressed | DynamicState.hover;
11406 			if((widget.dynamicState & pressed) == pressed) {
11407 				return WidgetBackground(cs.depressedButtonColor());
11408 			} else if(widget.dynamicState & DynamicState.hover) {
11409 				return WidgetBackground(cs.hoveringColor());
11410 			} else {
11411 				return WidgetBackground(cs.buttonColor());
11412 			}
11413 		}
11414 
11415 		override FrameStyle borderStyle() {
11416 			auto pressed = DynamicState.depressed | DynamicState.hover;
11417 			if((widget.dynamicState & pressed) == pressed) {
11418 				return FrameStyle.sunk;
11419 			} else {
11420 				return FrameStyle.risen;
11421 			}
11422 
11423 		}
11424 
11425 		override bool variesWithState(ulong dynamicStateFlags) {
11426 			return super.variesWithState(dynamicStateFlags) || (dynamicStateFlags & (DynamicState.depressed | DynamicState.hover));
11427 		}
11428 	}
11429 	mixin OverrideStyle!Style;
11430 
11431 	version(custom_widgets)
11432 	override void paint(WidgetPainter painter) {
11433 		painter.drawThemed(delegate Rectangle(const Rectangle bounds) {
11434 			if(sprite) {
11435 				sprite.drawAt(
11436 					painter,
11437 					bounds.upperLeft + Point((bounds.width - sprite.width) / 2, (bounds.height - sprite.height) / 2),
11438 					Point(0, 0)
11439 				);
11440 			} else {
11441 				painter.drawText(bounds.upperLeft, label, bounds.lowerRight, alignment | TextAlignment.VerticalCenter);
11442 			}
11443 			return bounds;
11444 		});
11445 	}
11446 
11447 	override int flexBasisWidth() {
11448 		version(win32_widgets) {
11449 			SIZE size;
11450 			SendMessage(hwnd, BCM_GETIDEALSIZE, 0, cast(LPARAM) &size);
11451 			if(size.cx == 0)
11452 				goto fallback;
11453 			return size.cx + scaleWithDpi(16);
11454 		}
11455 		fallback:
11456 			return scaleWithDpi(cast(int) label.length * 8 + 16);
11457 	}
11458 
11459 	override int flexBasisHeight() {
11460 		version(win32_widgets) {
11461 			SIZE size;
11462 			SendMessage(hwnd, BCM_GETIDEALSIZE, 0, cast(LPARAM) &size);
11463 			if(size.cy == 0)
11464 				goto fallback;
11465 			return size.cy + scaleWithDpi(6);
11466 		}
11467 		fallback:
11468 			return defaultLineHeight + 4;
11469 	}
11470 }
11471 
11472 /++
11473 	A button with a consistent size, suitable for user commands like OK and cANCEL.
11474 +/
11475 class CommandButton : Button {
11476 	this(string label, Widget parent) {
11477 		super(label, parent);
11478 	}
11479 
11480 	// FIXME: I think I can simply make this 0 stretchiness instead of max now that the flex basis is there
11481 
11482 	override int maxHeight() {
11483 		return defaultLineHeight + 4;
11484 	}
11485 
11486 	override int maxWidth() {
11487 		return defaultLineHeight * 4;
11488 	}
11489 
11490 	override int marginLeft() { return 12; }
11491 	override int marginRight() { return 12; }
11492 	override int marginTop() { return 12; }
11493 	override int marginBottom() { return 12; }
11494 }
11495 
11496 ///
11497 enum ArrowDirection {
11498 	left, ///
11499 	right, ///
11500 	up, ///
11501 	down ///
11502 }
11503 
11504 ///
11505 version(custom_widgets)
11506 class ArrowButton : Button {
11507 	///
11508 	this(ArrowDirection direction, Widget parent) {
11509 		super("", parent);
11510 		this.direction = direction;
11511 		triggersOnMultiClick = true;
11512 	}
11513 
11514 	private ArrowDirection direction;
11515 
11516 	override int minHeight() { return scaleWithDpi(16); }
11517 	override int maxHeight() { return scaleWithDpi(16); }
11518 	override int minWidth() { return scaleWithDpi(16); }
11519 	override int maxWidth() { return scaleWithDpi(16); }
11520 
11521 	override void paint(WidgetPainter painter) {
11522 		super.paint(painter);
11523 
11524 		auto cs = getComputedStyle();
11525 
11526 		painter.outlineColor = cs.foregroundColor;
11527 		painter.fillColor = cs.foregroundColor;
11528 
11529 		auto offset = Point((this.width - scaleWithDpi(16)) / 2, (this.height - scaleWithDpi(16)) / 2);
11530 
11531 		final switch(direction) {
11532 			case ArrowDirection.up:
11533 				painter.drawPolygon(
11534 					scaleWithDpi(Point(2, 10) + offset),
11535 					scaleWithDpi(Point(7, 5) + offset),
11536 					scaleWithDpi(Point(12, 10) + offset),
11537 					scaleWithDpi(Point(2, 10) + offset)
11538 				);
11539 			break;
11540 			case ArrowDirection.down:
11541 				painter.drawPolygon(
11542 					scaleWithDpi(Point(2, 6) + offset),
11543 					scaleWithDpi(Point(7, 11) + offset),
11544 					scaleWithDpi(Point(12, 6) + offset),
11545 					scaleWithDpi(Point(2, 6) + offset)
11546 				);
11547 			break;
11548 			case ArrowDirection.left:
11549 				painter.drawPolygon(
11550 					scaleWithDpi(Point(10, 2) + offset),
11551 					scaleWithDpi(Point(5, 7) + offset),
11552 					scaleWithDpi(Point(10, 12) + offset),
11553 					scaleWithDpi(Point(10, 2) + offset)
11554 				);
11555 			break;
11556 			case ArrowDirection.right:
11557 				painter.drawPolygon(
11558 					scaleWithDpi(Point(6, 2) + offset),
11559 					scaleWithDpi(Point(11, 7) + offset),
11560 					scaleWithDpi(Point(6, 12) + offset),
11561 					scaleWithDpi(Point(6, 2) + offset)
11562 				);
11563 			break;
11564 		}
11565 	}
11566 }
11567 
11568 private
11569 int[2] getChildPositionRelativeToParentOrigin(Widget c) nothrow {
11570 	int x, y;
11571 	Widget par = c;
11572 	while(par) {
11573 		x += par.x;
11574 		y += par.y;
11575 		par = par.parent;
11576 	}
11577 	return [x, y];
11578 }
11579 
11580 version(win32_widgets)
11581 private
11582 int[2] getChildPositionRelativeToParentHwnd(Widget c) nothrow {
11583 // MapWindowPoints?
11584 	int x, y;
11585 	Widget par = c;
11586 	while(par) {
11587 		x += par.x;
11588 		y += par.y;
11589 		par = par.parent;
11590 		if(par !is null && par.useNativeDrawing())
11591 			break;
11592 	}
11593 	return [x, y];
11594 }
11595 
11596 ///
11597 class ImageBox : Widget {
11598 	private MemoryImage image_;
11599 
11600 	override int widthStretchiness() { return 1; }
11601 	override int heightStretchiness() { return 1; }
11602 	override int widthShrinkiness() { return 1; }
11603 	override int heightShrinkiness() { return 1; }
11604 
11605 	override int flexBasisHeight() {
11606 		return image_.height;
11607 	}
11608 
11609 	override int flexBasisWidth() {
11610 		return image_.width;
11611 	}
11612 
11613 	///
11614 	public void setImage(MemoryImage image){
11615 		this.image_ = image;
11616 		if(this.parentWindow && this.parentWindow.win) {
11617 			if(sprite)
11618 				sprite.dispose();
11619 			sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_, true));
11620 		}
11621 		redraw();
11622 	}
11623 
11624 	/// How to fit the image in the box if they aren't an exact match in size?
11625 	enum HowToFit {
11626 		center, /// centers the image, cropping around all the edges as needed
11627 		crop, /// always draws the image in the upper left, cropping the lower right if needed
11628 		// stretch, /// not implemented
11629 	}
11630 
11631 	private Sprite sprite;
11632 	private HowToFit howToFit_;
11633 
11634 	private Color backgroundColor_;
11635 
11636 	///
11637 	this(MemoryImage image, HowToFit howToFit, Color backgroundColor, Widget parent) {
11638 		this.image_ = image;
11639 		this.tabStop = false;
11640 		this.howToFit_ = howToFit;
11641 		this.backgroundColor_ = backgroundColor;
11642 		super(parent);
11643 		updateSprite();
11644 	}
11645 
11646 	/// ditto
11647 	this(MemoryImage image, HowToFit howToFit, Widget parent) {
11648 		this(image, howToFit, Color.transparent, parent);
11649 	}
11650 
11651 	private void updateSprite() {
11652 		if(sprite is null && this.parentWindow && this.parentWindow.win) {
11653 			sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_, true));
11654 		}
11655 	}
11656 
11657 	override void paint(WidgetPainter painter) {
11658 		updateSprite();
11659 		if(backgroundColor_.a) {
11660 			painter.fillColor = backgroundColor_;
11661 			painter.drawRectangle(Point(0, 0), width, height);
11662 		}
11663 		if(howToFit_ == HowToFit.crop)
11664 			sprite.drawAt(painter, Point(0, 0));
11665 		else if(howToFit_ == HowToFit.center) {
11666 			sprite.drawAt(painter, Point((width - image_.width) / 2, (height - image_.height) / 2));
11667 		}
11668 	}
11669 }
11670 
11671 ///
11672 class TextLabel : Widget {
11673 	override int maxHeight() { return defaultLineHeight; }
11674 	override int minHeight() { return defaultLineHeight; }
11675 	override int minWidth() { return 32; }
11676 
11677 	override int flexBasisHeight() { return minHeight(); }
11678 	override int flexBasisWidth() { return defaultTextWidth(label); }
11679 
11680 	string label_;
11681 
11682 	/++
11683 		Indicates which other control this label is here for. Similar to HTML `for` attribute.
11684 
11685 		In practice this means a click on the label will focus the `labelFor`. In future versions
11686 		it will also set screen reader hints but that is not yet implemented.
11687 
11688 		History:
11689 			Added October 3, 2021 (dub v10.4)
11690 	+/
11691 	Widget labelFor;
11692 
11693 	///
11694 	@scriptable
11695 	string label() { return label_; }
11696 
11697 	///
11698 	@scriptable
11699 	void label(string l) {
11700 		label_ = l;
11701 		version(win32_widgets) {
11702 			WCharzBuffer bfr = WCharzBuffer(l);
11703 			SetWindowTextW(hwnd, bfr.ptr);
11704 		} else version(custom_widgets)
11705 			redraw();
11706 	}
11707 
11708 	///
11709 	this(string label, TextAlignment alignment, Widget parent) {
11710 		this.label_ = label;
11711 		this.alignment = alignment;
11712 		this.tabStop = false;
11713 		super(parent);
11714 
11715 		version(win32_widgets)
11716 		createWin32Window(this, "static"w, label, (alignment & TextAlignment.Center) ? SS_CENTER : 0, (alignment & TextAlignment.Right) ? WS_EX_RIGHT : WS_EX_LEFT);
11717 	}
11718 
11719 	override void defaultEventHandler_click(scope ClickEvent ce) {
11720 		if(this.labelFor !is null)
11721 			this.labelFor.focus();
11722 	}
11723 
11724 	/++
11725 		WARNING: this currently sets TextAlignment.Right as the default. That will change in a future version.
11726 		For future-proofing of your code, if you rely on TextAlignment.Right, you MUST specify that explicitly.
11727 	+/
11728 	this(string label, Widget parent) {
11729 		this(label, TextAlignment.Right, parent);
11730 	}
11731 
11732 
11733 	TextAlignment alignment;
11734 
11735 	version(custom_widgets)
11736 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
11737 		painter.outlineColor = getComputedStyle().foregroundColor;
11738 		painter.drawText(Point(0, 0), this.label, Point(width, height), alignment);
11739 		return bounds;
11740 	}
11741 
11742 }
11743 
11744 version(custom_widgets)
11745 	private struct etc {
11746 		mixin ExperimentalTextComponent;
11747 	}
11748 
11749 version(win32_widgets)
11750 	alias EditableTextWidgetParent = Widget; ///
11751 else version(custom_widgets) {
11752 	version(trash_text) {
11753 		alias EditableTextWidgetParent = ScrollableWidget; ///
11754 	} else {
11755 		alias EditableTextWidgetParent = Widget;
11756 		version=use_new_text_system;
11757 		import arsd.textlayouter;
11758 	}
11759 } else static assert(0);
11760 
11761 version(use_new_text_system)
11762 class TextDisplayHelper : Widget {
11763 	protected TextLayouter l;
11764 	protected ScrollMessageWidget smw;
11765 
11766 	private const(TextLayouter.State)*[] undoStack;
11767 	private const(TextLayouter.State)*[] redoStack;
11768 
11769 	bool readonly;
11770 	bool caretNavigation; // scroll lock can flip this
11771 	bool singleLine;
11772 	bool acceptsTabInput;
11773 
11774 	private Menu ctx;
11775 	override Menu contextMenu(int x, int y) {
11776 		if(ctx is null) {
11777 			ctx = new Menu("Actions", this);
11778 			ctx.addItem(new MenuItem(new Action("&Undo", GenericIcons.Undo, &undo)));
11779 			ctx.addItem(new MenuItem(new Action("&Redo", GenericIcons.Redo, &redo)));
11780 			ctx.addSeparator();
11781 			ctx.addItem(new MenuItem(new Action("Cu&t", GenericIcons.Cut, &cut)));
11782 			ctx.addItem(new MenuItem(new Action("&Copy", GenericIcons.Copy, &copy)));
11783 			ctx.addItem(new MenuItem(new Action("&Paste", GenericIcons.Paste, &paste)));
11784 			ctx.addItem(new MenuItem(new Action("&Delete", 0, &deleteContentOfSelection)));
11785 			ctx.addSeparator();
11786 			ctx.addItem(new MenuItem(new Action("Select &All", 0, &selectAll)));
11787 		}
11788 		return ctx;
11789 	}
11790 
11791 	override void defaultEventHandler_blur(Event ev) {
11792 		super.defaultEventHandler_blur(ev);
11793 		if(l.wasMutated()) {
11794 			auto evt = new ChangeEvent!string(this, &this.content);
11795 			evt.dispatch();
11796 			l.clearWasMutatedFlag();
11797 		}
11798 	}
11799 
11800 	private string content() {
11801 		return l.getTextString();
11802 	}
11803 
11804 	void undo() {
11805 		if(undoStack.length) {
11806 			auto state = undoStack[$-1];
11807 			undoStack = undoStack[0 .. $-1];
11808 			undoStack.assumeSafeAppend();
11809 			redoStack ~= l.saveState();
11810 			l.restoreState(state);
11811 			adjustScrollbarSizes();
11812 			scrollForCaret();
11813 			redraw();
11814 			stateCheckpoint = true;
11815 		}
11816 	}
11817 
11818 	void redo() {
11819 		if(redoStack.length) {
11820 			doStateCheckpoint();
11821 			auto state = redoStack[$-1];
11822 			redoStack = redoStack[0 .. $-1];
11823 			redoStack.assumeSafeAppend();
11824 			l.restoreState(state);
11825 			adjustScrollbarSizes();
11826 			scrollForCaret();
11827 			redraw();
11828 			stateCheckpoint = true;
11829 		}
11830 	}
11831 
11832 	void cut() {
11833 		with(l.selection()) {
11834 			if(!isEmpty()) {
11835 				setClipboardText(parentWindow.win, getContentString());
11836 				doStateCheckpoint();
11837 				replaceContent("");
11838 				adjustScrollbarSizes();
11839 				scrollForCaret();
11840 				this.redraw();
11841 			}
11842 		}
11843 
11844 	}
11845 
11846 	void copy() {
11847 		with(l.selection()) {
11848 			if(!isEmpty()) {
11849 				setClipboardText(parentWindow.win, getContentString());
11850 				this.redraw();
11851 			}
11852 		}
11853 	}
11854 
11855 	void paste() {
11856 		getClipboardText(parentWindow.win, (txt) {
11857 			doStateCheckpoint();
11858 			l.selection.replaceContent(txt);
11859 			adjustScrollbarSizes();
11860 			scrollForCaret();
11861 			this.redraw();
11862 		});
11863 	}
11864 
11865 	void deleteContentOfSelection() {
11866 		doStateCheckpoint();
11867 		l.selection.replaceContent("");
11868 		l.selection.setUserXCoordinate();
11869 		adjustScrollbarSizes();
11870 		scrollForCaret();
11871 		redraw();
11872 	}
11873 
11874 	void selectAll() {
11875 		with(l.selection) {
11876 			moveToStartOfDocument();
11877 			setAnchor();
11878 			moveToEndOfDocument();
11879 			setFocus();
11880 		}
11881 		redraw();
11882 	}
11883 
11884 	protected bool stateCheckpoint = true;
11885 
11886 	protected void doStateCheckpoint() {
11887 		if(stateCheckpoint) {
11888 			undoStack ~= l.saveState();
11889 			stateCheckpoint = false;
11890 		}
11891 	}
11892 
11893 	protected void adjustScrollbarSizes() {
11894 		// FIXME: will want a content area helper function instead of doing all these subtractions myself
11895 		auto borderWidth = 2;
11896 		this.smw.setTotalArea(l.width, l.height);
11897 		this.smw.setViewableArea(
11898 			this.width - this.paddingLeft - this.paddingRight - borderWidth * 2,
11899 			this.height - this.paddingTop - this.paddingBottom - borderWidth * 2);
11900 	}
11901 
11902 	protected void scrollForCaret() {
11903 		// import std.stdio; writeln(l.width, "x", l.height); writeln(this.width - this.paddingLeft - this.paddingRight, " ", this.height - this.paddingTop - this.paddingBottom);
11904 		smw.scrollIntoView(l.selection.focusBoundingBox());
11905 	}
11906 
11907 	// FIXME: this should be a theme changed event listener instead
11908 	private BaseVisualTheme currentTheme;
11909 	override void recomputeChildLayout() {
11910 		if(currentTheme is null)
11911 			currentTheme = WidgetPainter.visualTheme;
11912 		if(WidgetPainter.visualTheme !is currentTheme) {
11913 			currentTheme = WidgetPainter.visualTheme;
11914 			auto ds = this.l.defaultStyle;
11915 			if(auto ms = cast(MyTextStyle) ds) {
11916 				auto cs = getComputedStyle();
11917 				auto font = cs.font();
11918 				if(font !is null)
11919 					ms.font_ = font;
11920 				else {
11921 					auto osc = new OperatingSystemFont();
11922 					osc.loadDefault;
11923 					ms.font_ = osc;
11924 				}
11925 			}
11926 		}
11927 		super.recomputeChildLayout();
11928 	}
11929 
11930 	private Point adjustForSingleLine(Point p) {
11931 		if(singleLine)
11932 			return Point(p.x, this.height / 2);
11933 		else
11934 			return p;
11935 	}
11936 
11937 	private bool wordWrapEnabled_;
11938 
11939 	this(TextLayouter l, ScrollMessageWidget parent) {
11940 		this.smw = parent;
11941 
11942 		smw.addDefaultWheelListeners(16, 16, 8);
11943 		smw.movementPerButtonClick(16, 16);
11944 
11945 		this.defaultPadding = Rectangle(2, 2, 2, 2);
11946 
11947 		this.l = l;
11948 		super(parent);
11949 
11950 		smw.addEventListener((scope ScrollEvent se) {
11951 			this.redraw();
11952 		});
11953 
11954 		bool mouseDown;
11955 
11956 		this.addEventListener((scope ResizeEvent re) {
11957 			// FIXME: I should add a method to give this client area width thing
11958 			if(wordWrapEnabled_)
11959 				this.l.wordWrapWidth = this.width - this.paddingLeft - this.paddingRight;
11960 
11961 			adjustScrollbarSizes();
11962 			scrollForCaret();
11963 
11964 			this.redraw();
11965 		});
11966 
11967 		this.addEventListener((scope KeyDownEvent kde) {
11968 			switch(kde.key) {
11969 				case Key.Up, Key.Down, Key.Left, Key.Right:
11970 				case Key.Home, Key.End:
11971 					stateCheckpoint = true;
11972 					bool setPosition = false;
11973 					switch(kde.key) {
11974 						case Key.Up: l.selection.moveUp(); break;
11975 						case Key.Down: l.selection.moveDown(); break;
11976 						case Key.Left: l.selection.moveLeft(); setPosition = true; break;
11977 						case Key.Right: l.selection.moveRight(); setPosition = true; break;
11978 						case Key.Home: l.selection.moveToStartOfLine(); setPosition = true; break;
11979 						case Key.End: l.selection.moveToEndOfLine(); setPosition = true; break;
11980 						default: assert(0);
11981 					}
11982 
11983 					if(kde.shiftKey)
11984 						l.selection.setFocus();
11985 					else
11986 						l.selection.setAnchor();
11987 					if(setPosition)
11988 						l.selection.setUserXCoordinate();
11989 					scrollForCaret();
11990 					redraw();
11991 				break;
11992 				case Key.PageUp, Key.PageDown:
11993 					// FIXME
11994 					scrollForCaret();
11995 				break;
11996 				case Key.Delete:
11997 					if(l.selection.isEmpty()) {
11998 						l.selection.setAnchor();
11999 						l.selection.moveRight();
12000 						l.selection.setFocus();
12001 					}
12002 					deleteContentOfSelection();
12003 					adjustScrollbarSizes();
12004 					scrollForCaret();
12005 				break;
12006 				case Key.Insert:
12007 				break;
12008 				case Key.A:
12009 					if(kde.ctrlKey)
12010 						selectAll();
12011 				break;
12012 				case Key.F:
12013 					// find
12014 				break;
12015 				case Key.Z:
12016 					if(kde.ctrlKey)
12017 						undo();
12018 				break;
12019 				case Key.R:
12020 					if(kde.ctrlKey)
12021 						redo();
12022 				break;
12023 				case Key.X:
12024 					if(kde.ctrlKey)
12025 						cut();
12026 				break;
12027 				case Key.C:
12028 					if(kde.ctrlKey)
12029 						copy();
12030 				break;
12031 				case Key.V:
12032 					if(kde.ctrlKey)
12033 						paste();
12034 				break;
12035 				case Key.F1:
12036 					with(l.selection()) {
12037 						moveToStartOfLine();
12038 						setAnchor();
12039 						moveToEndOfLine();
12040 						moveToIncludeAdjacentEndOfLineMarker();
12041 						setFocus();
12042 						replaceContent("");
12043 					}
12044 
12045 					redraw();
12046 				break;
12047 				/*
12048 				case Key.F2:
12049 					l.selection().changeStyle((old) => l.registerStyle(new MyTextStyle(
12050 						//(cast(MyTextStyle) old).font,
12051 						font2,
12052 						Color.red)));
12053 					redraw();
12054 				break;
12055 				*/
12056 				case Key.Tab:
12057 					// we process the char event, so don't want to change focus on it
12058 					if(acceptsTabInput)
12059 						kde.preventDefault();
12060 				break;
12061 				default:
12062 			}
12063 		});
12064 
12065 		Point downAt;
12066 
12067 		static if(UsingSimpledisplayX11)
12068 		this.addEventListener((scope ClickEvent ce) {
12069 			if(ce.button == MouseButton.middle) {
12070 				parentWindow.win.getPrimarySelection((txt) {
12071 					l.selection.replaceContent(txt);
12072 					redraw();
12073 				});
12074 			}
12075 		});
12076 
12077 		this.addEventListener((scope MouseDownEvent ce) {
12078 			if(ce.button == MouseButton.left) {
12079 				downAt = Point(ce.clientX - this.paddingLeft, ce.clientY - this.paddingTop);
12080 				l.selection.moveTo(adjustForSingleLine(smw.position + downAt));
12081 				l.selection.setAnchor();
12082 				mouseDown = true;
12083 				parentWindow.captureMouse(this);
12084 				this.redraw();
12085 			} else if(ce.button == MouseButton.right) {
12086 				this.showContextMenu(ce.clientX, ce.clientY);
12087 			}
12088 			//import std.stdio;
12089 			//writeln(ce.clientX, ", ", ce.clientY, " = ", l.offsetOfClick(Point(ce.clientX, ce.clientY)));
12090 		});
12091 
12092 		Timer autoscrollTimer;
12093 		int autoscrollDirection;
12094 		int autoscrollAmount;
12095 
12096 		void autoscroll() {
12097 			switch(autoscrollDirection) {
12098 				case 0: smw.scrollUp(autoscrollAmount); break;
12099 				case 1: smw.scrollDown(autoscrollAmount); break;
12100 				case 2: smw.scrollLeft(autoscrollAmount); break;
12101 				case 3: smw.scrollRight(autoscrollAmount); break;
12102 				default: assert(0);
12103 			}
12104 
12105 			this.redraw();
12106 		}
12107 
12108 		void setAutoscrollTimer(int direction, int amount) {
12109 			if(autoscrollTimer is null) {
12110 				autoscrollTimer = new Timer(1000 / 60, &autoscroll);
12111 			}
12112 
12113 			autoscrollDirection = direction;
12114 			autoscrollAmount = amount;
12115 		}
12116 
12117 		void stopAutoscrollTimer() {
12118 			if(autoscrollTimer !is null) {
12119 				autoscrollTimer.dispose();
12120 				autoscrollTimer = null;
12121 			}
12122 			autoscrollAmount = 0;
12123 			autoscrollDirection = 0;
12124 		}
12125 
12126 		this.addEventListener((scope MouseMoveEvent ce) {
12127 			if(mouseDown) {
12128 				auto movedTo = Point(ce.clientX - this.paddingLeft, ce.clientY - this.paddingTop);
12129 
12130 				// FIXME: when scrolling i actually do want a timer.
12131 				// i also want a zone near the sides of the window where i can auto scroll
12132 
12133 				auto scrollMultiplier = scaleWithDpi(16);
12134 				auto scrollDivisor = scaleWithDpi(16); // if you go more than 64px up it will scroll faster
12135 
12136 				if(!singleLine && movedTo.y < 4) {
12137 					setAutoscrollTimer(0, scrollMultiplier * -(movedTo.y-4) / scrollDivisor);
12138 				} else
12139 				if(!singleLine && (movedTo.y + 6) > this.height) {
12140 					setAutoscrollTimer(1, scrollMultiplier * (movedTo.y + 6 - this.height) / scrollDivisor);
12141 				} else
12142 				if(movedTo.x < 4) {
12143 					setAutoscrollTimer(2, scrollMultiplier * -(movedTo.x-4) / scrollDivisor);
12144 				} else
12145 				if((movedTo.x + 6) > this.width) {
12146 					setAutoscrollTimer(3, scrollMultiplier * (movedTo.x + 6 - this.width) / scrollDivisor);
12147 				} else
12148 					stopAutoscrollTimer();
12149 
12150 				l.selection.moveTo(adjustForSingleLine(smw.position + movedTo));
12151 				l.selection.setFocus();
12152 				this.redraw();
12153 			}
12154 		});
12155 
12156 		this.addEventListener((scope MouseUpEvent ce) {
12157 			// FIXME: assert primary selection
12158 			if(mouseDown && ce.button == MouseButton.left) {
12159 				stateCheckpoint = true;
12160 				//l.selection.moveTo(adjustForSingleLine(smw.position + Point(ce.clientX - this.paddingLeft, ce.clientY - this.paddingTop)));
12161 				//l.selection.setFocus();
12162 				mouseDown = false;
12163 				parentWindow.releaseMouseCapture();
12164 				stopAutoscrollTimer();
12165 				this.redraw();
12166 			}
12167 			//import std.stdio;
12168 			//writeln(ce.clientX, ", ", ce.clientY, " = ", l.offsetOfClick(Point(ce.clientX, ce.clientY)));
12169 		});
12170 
12171 		this.addEventListener((scope CharEvent ce) {
12172 			if(ce.character < 32 && ce.character != '\t' && ce.character != '\n' && ce.character != '\b')
12173 				return; // skip the ctrl+x characters we don't care about as plain text
12174 
12175 			if(singleLine && ce.character == '\n')
12176 				return;
12177 			if(!acceptsTabInput && ce.character == '\t')
12178 				return;
12179 
12180 			doStateCheckpoint();
12181 
12182 			char[4] buffer;
12183 			import std.utf;
12184 			auto stride = encode(buffer, ce.character);
12185 			l.selection.replaceContent(buffer[0 .. stride]);
12186 			l.selection.setUserXCoordinate();
12187 			adjustScrollbarSizes();
12188 			scrollForCaret();
12189 			redraw();
12190 		});
12191 	}
12192 
12193 	static class Style : Widget.Style {
12194 		override WidgetBackground background() {
12195 			return WidgetBackground(WidgetPainter.visualTheme.widgetBackgroundColor);
12196 		}
12197 
12198 		override Color foregroundColor() {
12199 			return WidgetPainter.visualTheme.foregroundColor;
12200 		}
12201 
12202 		override FrameStyle borderStyle() {
12203 			return FrameStyle.sunk;
12204 		}
12205 
12206 		override MouseCursor cursor() {
12207 			return GenericCursor.Text;
12208 		}
12209 	}
12210 	mixin OverrideStyle!Style;
12211 
12212 	override int minHeight() { return borderBoxForContentBox(Rectangle(Point(0, 0), Size(0, Window.lineHeight))).height; }
12213 	override int maxHeight() {
12214 		if(singleLine)
12215 			return minHeight;
12216 		else
12217 			return super.maxHeight();
12218 	}
12219 
12220 	void drawTextSegment(WidgetPainter painter, Point upperLeft, scope const(char)[] text) {
12221 		painter.drawText(upperLeft, text);
12222 	}
12223 
12224 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
12225 		//painter.setFont(font);
12226 
12227 		auto cs = getComputedStyle();
12228 		auto defaultColor = cs.foregroundColor;
12229 
12230 		auto old = painter.setClipRectangle(bounds);
12231 		scope(exit) painter.setClipRectangle(old);
12232 
12233 		l.getDrawableText(delegate bool(txt, style, info, carets...) {
12234 			//import std.stdio; writeln("Segment: ", txt);
12235 			assert(style !is null);
12236 
12237 			auto myStyle = cast(MyTextStyle) style;
12238 			assert(myStyle !is null);
12239 
12240 			painter.setFont(myStyle.font);
12241 			// defaultColor = myStyle.color; // FIXME: so wrong
12242 
12243 			if(info.selections && info.boundingBox.width > 0) {
12244 				auto color = this.isFocused ? cs.selectionBackgroundColor : Color(128, 128, 128); // FIXME don't hardcode
12245 				painter.fillColor = color;
12246 				painter.outlineColor = color;
12247 				painter.drawRectangle(Rectangle(info.boundingBox.upperLeft - smw.position() + bounds.upperLeft, info.boundingBox.size));
12248 				painter.outlineColor = cs.selectionForegroundColor;
12249 				//painter.fillColor = Color.white;
12250 			} else {
12251 				painter.outlineColor = defaultColor;
12252 			}
12253 
12254 			if(this.isFocused)
12255 			foreach(idx, caret; carets) {
12256 				if(idx == 0)
12257 					painter.notifyCursorPosition(caret.boundingBox.left - smw.position.x + bounds.left, caret.boundingBox.top - smw.position.y + bounds.top, caret.boundingBox.width, caret.boundingBox.height);
12258 				painter.drawLine(
12259 					caret.boundingBox.upperLeft + bounds.upperLeft - smw.position(),
12260 					bounds.upperLeft + Point(caret.boundingBox.left, caret.boundingBox.bottom) - smw.position()
12261 				);
12262 			}
12263 
12264 			import std.string;
12265 			if(txt.strip.length)
12266 				drawTextSegment(painter, info.boundingBox.upperLeft - smw.position() + bounds.upperLeft, txt.stripRight);
12267 
12268 			if(info.boundingBox.upperLeft.y - smw.position().y > this.height)
12269 				return false;
12270 			else {
12271 				return true;
12272 			}
12273 		}, Rectangle(smw.position(), bounds.size));
12274 
12275 		/+
12276 		int place = 0;
12277 		int y = 75;
12278 		foreach(width; widths) {
12279 			painter.fillColor = Color.red;
12280 			painter.drawRectangle(Point(place, y), Size(width, 75));
12281 			//y += 15;
12282 			place += width;
12283 		}
12284 		+/
12285 
12286 		return bounds;
12287 	}
12288 
12289 	static class MyTextStyle : TextStyle {
12290 		OperatingSystemFont font_;
12291 		this(OperatingSystemFont font, bool passwordMode = false) {
12292 			this.font_ = font;
12293 		}
12294 
12295 		override OperatingSystemFont font() {
12296 			return font_;
12297 		}
12298 	}
12299 }
12300 
12301 /+
12302 version(use_new_text_system)
12303 class TextWidget : Widget {
12304 	TextLayouter l;
12305 	ScrollMessageWidget smw;
12306 	TextDisplayHelper helper;
12307 	this(TextLayouter l, Widget parent) {
12308 		this.l = l;
12309 		super(parent);
12310 
12311 		smw = new ScrollMessageWidget(this);
12312 		//smw.horizontalScrollBar.hide;
12313 		//smw.verticalScrollBar.hide;
12314 		smw.addDefaultWheelListeners(16, 16, 8);
12315 		smw.movementPerButtonClick(16, 16);
12316 		helper = new TextDisplayHelper(l, smw);
12317 
12318 		// no need to do this here since there's gonna be a resize
12319 		// event immediately before any drawing
12320 		// smw.setTotalArea(l.width, l.height);
12321 		smw.setViewableArea(
12322 			this.width - this.paddingLeft - this.paddingRight,
12323 			this.height - this.paddingTop - this.paddingBottom);
12324 
12325 		/+
12326 		import std.stdio;
12327 		writeln(l.width, "x", l.height);
12328 		+/
12329 	}
12330 }
12331 +/
12332 
12333 
12334 
12335 
12336 /+
12337 	This awful thing has to be rewritten. And it needs to takecare of parentWindow.inputProxy.setIMEPopupLocation too
12338 +/
12339 
12340 /// Contains the implementation of text editing
12341 abstract class EditableTextWidget : EditableTextWidgetParent {
12342 	this(Widget parent) {
12343 		super(parent);
12344 
12345 		version(custom_widgets)
12346 			setupCustomTextEditing();
12347 	}
12348 
12349 	private bool wordWrapEnabled_;
12350 	void wordWrapEnabled(bool enabled) {
12351 		version(win32_widgets) {
12352 			SendMessageW(hwnd, EM_FMTLINES, enabled ? 1 : 0, 0);
12353 		} else version(custom_widgets) {
12354 			wordWrapEnabled_ = enabled;
12355 			version(use_new_text_system)
12356 			textLayout.wordWrapWidth = enabled ? this.width : 0; // FIXME
12357 		} else static assert(false);
12358 	}
12359 
12360 	override int minWidth() { return scaleWithDpi(16); }
12361 	override int widthStretchiness() { return 7; }
12362 
12363 	version(use_new_text_system)
12364 	override int maxHeight() { return tdh.maxHeight; }
12365 
12366 	version(use_new_text_system)
12367 	override void focus() { if(tdh) tdh.focus(); }
12368 
12369 	void selectAll() {
12370 		version(win32_widgets)
12371 			SendMessage(hwnd, EM_SETSEL, 0, -1);
12372 		else version(custom_widgets) {
12373 			version(use_new_text_system)
12374 				tdh.selectAll();
12375 			else
12376 				textLayout.selectAll();
12377 			redraw();
12378 		}
12379 	}
12380 
12381 	version(use_new_text_system)
12382 		TextDisplayHelper tdh;
12383 
12384 	@property string content() {
12385 		version(win32_widgets) {
12386 			wchar[4096] bufferstack;
12387 			wchar[] buffer;
12388 			auto len = GetWindowTextLength(hwnd);
12389 			if(len < bufferstack.length)
12390 				buffer = bufferstack[0 .. len + 1];
12391 			else
12392 				buffer = new wchar[](len + 1);
12393 
12394 			auto l = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
12395 			if(l >= 0)
12396 				return makeUtf8StringFromWindowsString(buffer[0 .. l]);
12397 			else
12398 				return null;
12399 		} else version(custom_widgets) {
12400 			version(use_new_text_system) {
12401 				return textLayout.getTextString();
12402 			} else
12403 				return textLayout.getPlainText();
12404 		} else static assert(false);
12405 	}
12406 	@property void content(string s) {
12407 		version(win32_widgets) {
12408 			WCharzBuffer bfr = WCharzBuffer(s, WindowsStringConversionFlags.convertNewLines);
12409 			SetWindowTextW(hwnd, bfr.ptr);
12410 		} else version(custom_widgets) {
12411 			version(use_new_text_system) {
12412 				selectAll();
12413 				textLayout.selection.replaceContent(s);
12414 
12415 				tdh.adjustScrollbarSizes();
12416 				//scrollForCaret();
12417 
12418 				redraw();
12419 			} else {
12420 				textLayout.clear();
12421 				textLayout.addText(s);
12422 
12423 				{
12424 				// FIXME: it should be able to get this info easier
12425 				auto painter = draw();
12426 				textLayout.redoLayout(painter);
12427 				}
12428 				auto cbb = textLayout.contentBoundingBox();
12429 				setContentSize(cbb.width, cbb.height);
12430 				/*
12431 				textLayout.addText(ForegroundColor.red, s);
12432 				textLayout.addText(ForegroundColor.blue, TextFormat.underline, "http://dpldocs.info/");
12433 				textLayout.addText(" is the best!");
12434 				*/
12435 				redraw();
12436 			}
12437 		}
12438 		else static assert(false);
12439 	}
12440 
12441 	void addText(string txt) {
12442 		version(custom_widgets) {
12443 			version(use_new_text_system) {
12444 				textLayout.appendText(txt);
12445 				redraw();
12446 			} else {
12447 				textLayout.addText(txt);
12448 
12449 				{
12450 				// FIXME: it should be able to get this info easier
12451 				auto painter = draw();
12452 				textLayout.redoLayout(painter);
12453 				}
12454 				auto cbb = textLayout.contentBoundingBox();
12455 				setContentSize(cbb.width, cbb.height);
12456 			}
12457 		} else version(win32_widgets) {
12458 			// get the current selection
12459 			DWORD StartPos, EndPos;
12460 			SendMessageW( hwnd, EM_GETSEL, cast(WPARAM)(&StartPos), cast(LPARAM)(&EndPos) );
12461 
12462 			// move the caret to the end of the text
12463 			int outLength = GetWindowTextLengthW(hwnd);
12464 			SendMessageW( hwnd, EM_SETSEL, outLength, outLength );
12465 
12466 			// insert the text at the new caret position
12467 			WCharzBuffer bfr = WCharzBuffer(txt, WindowsStringConversionFlags.convertNewLines);
12468 			SendMessageW( hwnd, EM_REPLACESEL, TRUE, cast(LPARAM) bfr.ptr );
12469 
12470 			// restore the previous selection
12471 			SendMessageW( hwnd, EM_SETSEL, StartPos, EndPos );
12472 		} else static assert(0);
12473 	}
12474 
12475 	version(custom_widgets)
12476 	version(trash_text)
12477 	override void paintFrameAndBackground(WidgetPainter painter) {
12478 		this.draw3dFrame(painter, FrameStyle.sunk, Color.white);
12479 	}
12480 
12481 	version(use_new_text_system)
12482 	TextDisplayHelper textDisplayHelperFactory(TextLayouter textLayout, ScrollMessageWidget smw) {
12483 		return new TextDisplayHelper(textLayout, smw);
12484 	}
12485 
12486 	version(use_new_text_system)
12487 	TextStyle defaultTextStyle() {
12488 		auto cs = getComputedStyle();
12489 		auto font = cs.font;
12490 		if(font is null) {
12491 			font = new OperatingSystemFont;
12492 			font.loadDefault();
12493 		}
12494 		return new TextDisplayHelper.MyTextStyle(font);
12495 	}
12496 
12497 	version(win32_widgets) { /* will do it with Windows calls in the classes */ }
12498 	else version(custom_widgets) {
12499 		// FIXME
12500 		version(use_new_text_system) {
12501 			TextLayouter textLayout;
12502 
12503 			void setupCustomTextEditing() {
12504 				textLayout = new TextLayouter(defaultTextStyle());
12505 				auto smw = new ScrollMessageWidget(this);
12506 				if(!showingHorizontalScroll)
12507 					smw.horizontalScrollBar.hide();
12508 				if(!showingVerticalScroll)
12509 					smw.verticalScrollBar.hide();
12510 				this.tabStop = false;
12511 				smw.tabStop = false;
12512 				tdh = textDisplayHelperFactory(textLayout, smw);
12513 			}
12514 
12515 		} else {
12516 
12517 			static if(SimpledisplayTimerAvailable)
12518 				Timer caretTimer;
12519 			etc.TextLayout textLayout;
12520 
12521 			void setupCustomTextEditing() {
12522 				textLayout = new etc.TextLayout(Rectangle(4, 2, width - 8, height - 4));
12523 				textLayout.selectionXorColor = getComputedStyle().activeListXorColor;
12524 			}
12525 
12526 			override void paint(WidgetPainter painter) {
12527 				if(parentWindow.win.closed) return;
12528 
12529 				textLayout.boundingBox = Rectangle(4, 2, width - 8, height - 4);
12530 
12531 				/*
12532 				painter.outlineColor = Color.white;
12533 				painter.fillColor = Color.white;
12534 				painter.drawRectangle(Point(4, 4), contentWidth, contentHeight);
12535 				*/
12536 
12537 				painter.outlineColor = Color.black;
12538 				// painter.drawText(Point(4, 4), content, Point(width - 4, height - 4));
12539 
12540 				textLayout.caretShowingOnScreen = false;
12541 
12542 				textLayout.drawInto(painter, !parentWindow.win.closed && isFocused());
12543 			}
12544 		}
12545 
12546 		static class Style : Widget.Style {
12547 			override FrameStyle borderStyle() {
12548 				return FrameStyle.sunk;
12549 			}
12550 			override MouseCursor cursor() {
12551 				return GenericCursor.Text;
12552 			}
12553 		}
12554 		mixin OverrideStyle!Style;
12555 	}
12556 	else static assert(false);
12557 
12558 	version(trash_text)
12559 	version(custom_widgets)
12560 	override void defaultEventHandler_mousedown(MouseDownEvent ev) {
12561 		super.defaultEventHandler_mousedown(ev);
12562 		if(parentWindow.win.closed) return;
12563 		if(ev.button == MouseButton.left) {
12564 			if(textLayout.selectNone())
12565 				redraw();
12566 			textLayout.moveCaretToPixelCoordinates(ev.clientX, ev.clientY);
12567 			this.focus();
12568 			//this.parentWindow.win.grabInput();
12569 		} else if(ev.button == MouseButton.middle) {
12570 			static if(UsingSimpledisplayX11) {
12571 				getPrimarySelection(parentWindow.win, (in char[] txt) {
12572 					textLayout.insert(txt);
12573 					redraw();
12574 
12575 					auto cbb = textLayout.contentBoundingBox();
12576 					setContentSize(cbb.width, cbb.height);
12577 				});
12578 			}
12579 		}
12580 	}
12581 
12582 	version(trash_text)
12583 	version(custom_widgets)
12584 	override void defaultEventHandler_mouseup(MouseUpEvent ev) {
12585 		//this.parentWindow.win.releaseInputGrab();
12586 		super.defaultEventHandler_mouseup(ev);
12587 	}
12588 
12589 	version(trash_text)
12590 	version(custom_widgets)
12591 	override void defaultEventHandler_mousemove(MouseMoveEvent ev) {
12592 		super.defaultEventHandler_mousemove(ev);
12593 		if(ev.state & ModifierState.leftButtonDown) {
12594 			textLayout.selectToPixelCoordinates(ev.clientX, ev.clientY);
12595 			redraw();
12596 		}
12597 	}
12598 
12599 	version(trash_text)
12600 	version(custom_widgets)
12601 	override void defaultEventHandler_focus(Event ev) {
12602 		super.defaultEventHandler_focus(ev);
12603 		if(parentWindow.win.closed) return;
12604 		auto painter = this.draw();
12605 		textLayout.drawCaret(painter);
12606 
12607 		static if(SimpledisplayTimerAvailable)
12608 		if(caretTimer) {
12609 			caretTimer.destroy();
12610 			caretTimer = null;
12611 		}
12612 
12613 		bool blinkingCaret = true;
12614 		static if(UsingSimpledisplayX11)
12615 			if(!Image.impl.xshmAvailable)
12616 				blinkingCaret = false; // if on a remote connection, don't waste bandwidth on an expendable blink
12617 
12618 		if(blinkingCaret)
12619 		static if(SimpledisplayTimerAvailable)
12620 		caretTimer = new Timer(500, {
12621 			if(parentWindow.win.closed) {
12622 				caretTimer.destroy();
12623 				return;
12624 			}
12625 			if(isFocused()) {
12626 				auto painter = this.draw();
12627 				textLayout.drawCaret(painter);
12628 			} else if(textLayout.caretShowingOnScreen) {
12629 				auto painter = this.draw();
12630 				textLayout.eraseCaret(painter);
12631 			}
12632 		});
12633 	}
12634 
12635 	version(trash_text) {
12636 		private string lastContentBlur;
12637 
12638 		override void defaultEventHandler_blur(Event ev) {
12639 			super.defaultEventHandler_blur(ev);
12640 			if(parentWindow.win.closed) return;
12641 			version(custom_widgets) {
12642 				auto painter = this.draw();
12643 				textLayout.eraseCaret(painter);
12644 				static if(SimpledisplayTimerAvailable)
12645 				if(caretTimer) {
12646 					caretTimer.destroy();
12647 					caretTimer = null;
12648 				}
12649 			}
12650 
12651 			if(this.content != lastContentBlur) {
12652 				auto evt = new ChangeEvent!string(this, &this.content);
12653 				evt.dispatch();
12654 				lastContentBlur = this.content;
12655 			}
12656 		}
12657 	}
12658 
12659 	version(win32_widgets) {
12660 		private string lastContentBlur;
12661 
12662 		override void defaultEventHandler_blur(Event ev) {
12663 			super.defaultEventHandler_blur(ev);
12664 
12665 			if(this.content != lastContentBlur) {
12666 				auto evt = new ChangeEvent!string(this, &this.content);
12667 				evt.dispatch();
12668 				lastContentBlur = this.content;
12669 			}
12670 		}
12671 	}
12672 
12673 
12674 	version(trash_text)
12675 	version(custom_widgets)
12676 	override void defaultEventHandler_char(CharEvent ev) {
12677 		super.defaultEventHandler_char(ev);
12678 		textLayout.insert(ev.character);
12679 		redraw();
12680 
12681 		// FIXME: too inefficient
12682 		auto cbb = textLayout.contentBoundingBox();
12683 		setContentSize(cbb.width, cbb.height);
12684 	}
12685 	version(trash_text)
12686 	version(custom_widgets)
12687 	override void defaultEventHandler_keydown(KeyDownEvent ev) {
12688 		//super.defaultEventHandler_keydown(ev);
12689 		switch(ev.key) {
12690 			case Key.Delete:
12691 				textLayout.delete_();
12692 				redraw();
12693 			break;
12694 			case Key.Left:
12695 				textLayout.moveLeft();
12696 				redraw();
12697 			break;
12698 			case Key.Right:
12699 				textLayout.moveRight();
12700 				redraw();
12701 			break;
12702 			case Key.Up:
12703 				textLayout.moveUp();
12704 				redraw();
12705 			break;
12706 			case Key.Down:
12707 				textLayout.moveDown();
12708 				redraw();
12709 			break;
12710 			case Key.Home:
12711 				textLayout.moveHome();
12712 				redraw();
12713 			break;
12714 			case Key.End:
12715 				textLayout.moveEnd();
12716 				redraw();
12717 			break;
12718 			case Key.PageUp:
12719 				foreach(i; 0 .. 32)
12720 				textLayout.moveUp();
12721 				redraw();
12722 			break;
12723 			case Key.PageDown:
12724 				foreach(i; 0 .. 32)
12725 				textLayout.moveDown();
12726 				redraw();
12727 			break;
12728 
12729 			default:
12730 				 {} // intentionally blank, let "char" handle it
12731 		}
12732 		/*
12733 		if(ev.key == Key.Backspace) {
12734 			textLayout.backspace();
12735 			redraw();
12736 		}
12737 		*/
12738 		ensureVisibleInScroll(textLayout.caretBoundingBox());
12739 	}
12740 
12741 	version(use_new_text_system) {
12742 		bool showingVerticalScroll() { return true; }
12743 		bool showingHorizontalScroll() { return true; }
12744 	}
12745 }
12746 
12747 ///
12748 class LineEdit : EditableTextWidget {
12749 	// FIXME: hack
12750 	version(custom_widgets) {
12751 	override bool showingVerticalScroll() { return false; }
12752 	override bool showingHorizontalScroll() { return false; }
12753 	}
12754 
12755 	override int flexBasisWidth() { return 250; }
12756 
12757 	///
12758 	this(Widget parent) {
12759 		super(parent);
12760 		version(win32_widgets) {
12761 			createWin32Window(this, "edit"w, "",
12762 				0, WS_EX_CLIENTEDGE);//|WS_HSCROLL|ES_AUTOHSCROLL);
12763 		} else version(custom_widgets) {
12764 			version(trash_text) {
12765 				setupCustomTextEditing();
12766 				addEventListener(delegate(CharEvent ev) {
12767 					if(ev.character == '\n')
12768 						ev.preventDefault();
12769 				});
12770 			}
12771 		} else static assert(false);
12772 	}
12773 
12774 	version(use_new_text_system)
12775 	override TextDisplayHelper textDisplayHelperFactory(TextLayouter textLayout, ScrollMessageWidget smw) {
12776 		auto tdh = new TextDisplayHelper(textLayout, smw);
12777 		tdh.singleLine = true;
12778 		return tdh;
12779 	}
12780 
12781 	version(win32_widgets) {
12782 		mixin Padding!q{2};
12783 		override int minHeight() { return borderBoxForContentBox(Rectangle(Point(0, 0), Size(0, defaultLineHeight))).height; }
12784 		override int maxHeight() { return minHeight; }
12785 	}
12786 
12787 	/+
12788 	@property void passwordMode(bool p) {
12789 		SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) | ES_PASSWORD);
12790 	}
12791 	+/
12792 }
12793 
12794 /++
12795 	A [LineEdit] that displays `*` in place of the actual characters.
12796 
12797 	Alas, Windows requires the window to be created differently to use this style,
12798 	so it had to be a new class instead of a toggle on and off on an existing object.
12799 
12800 	FIXME: this is not yet implemented on Linux, it will work the same as a TextEdit there for now.
12801 
12802 	History:
12803 		Added January 24, 2021
12804 +/
12805 class PasswordEdit : EditableTextWidget {
12806 	version(custom_widgets) {
12807 	override bool showingVerticalScroll() { return false; }
12808 	override bool showingHorizontalScroll() { return false; }
12809 	}
12810 
12811 	override int flexBasisWidth() { return 250; }
12812 
12813 	version(use_new_text_system)
12814 	override TextStyle defaultTextStyle() {
12815 		auto cs = getComputedStyle();
12816 
12817 		auto osf = new class OperatingSystemFont {
12818 			this() {
12819 				super(cs.font);
12820 			}
12821 			override int stringWidth(scope const(char)[] text, SimpleWindow window = null) {
12822 				int count = 0;
12823 				foreach(dchar ch; text)
12824 					count++;
12825 				return count * super.stringWidth("*", window);
12826 			}
12827 		};
12828 
12829 		return new TextDisplayHelper.MyTextStyle(osf);
12830 	}
12831 
12832 	version(use_new_text_system)
12833 	override TextDisplayHelper textDisplayHelperFactory(TextLayouter textLayout, ScrollMessageWidget smw) {
12834 		static class TDH : TextDisplayHelper {
12835 			this(TextLayouter textLayout, ScrollMessageWidget smw) {
12836 				singleLine = true;
12837 				super(textLayout, smw);
12838 			}
12839 
12840 			override void drawTextSegment(WidgetPainter painter, Point upperLeft, scope const(char)[] text) {
12841 				char[256] buffer = void;
12842 				int bufferLength = 0;
12843 				foreach(dchar ch; text)
12844 					buffer[bufferLength++] = '*';
12845 				painter.drawText(upperLeft, buffer[0..bufferLength]);
12846 			}
12847 		}
12848 
12849 		return new TDH(textLayout, smw);
12850 	}
12851 
12852 	///
12853 	this(Widget parent) {
12854 		super(parent);
12855 		version(win32_widgets) {
12856 			createWin32Window(this, "edit"w, "",
12857 				ES_PASSWORD, WS_EX_CLIENTEDGE);//|WS_HSCROLL|ES_AUTOHSCROLL);
12858 		} else version(custom_widgets) {
12859 			version(trash_text)
12860 			setupCustomTextEditing();
12861 			addEventListener(delegate(CharEvent ev) {
12862 				if(ev.character == '\n')
12863 					ev.preventDefault();
12864 			});
12865 		} else static assert(false);
12866 	}
12867 	version(win32_widgets) {
12868 		mixin Padding!q{2};
12869 		override int minHeight() { return borderBoxForContentBox(Rectangle(Point(0, 0), Size(0, defaultLineHeight))).height; }
12870 		override int maxHeight() { return minHeight; }
12871 	}
12872 }
12873 
12874 
12875 ///
12876 class TextEdit : EditableTextWidget {
12877 	///
12878 	this(Widget parent) {
12879 		super(parent);
12880 		version(win32_widgets) {
12881 			createWin32Window(this, "edit"w, "",
12882 				0|WS_VSCROLL|WS_HSCROLL|ES_MULTILINE|ES_WANTRETURN|ES_AUTOHSCROLL|ES_AUTOVSCROLL, WS_EX_CLIENTEDGE);
12883 		} else version(custom_widgets) {
12884 			version(trash_text)
12885 			setupCustomTextEditing();
12886 		} else static assert(false);
12887 	}
12888 	override int maxHeight() { return int.max; }
12889 	override int heightStretchiness() { return 7; }
12890 
12891 	override int flexBasisWidth() { return 250; }
12892 	override int flexBasisHeight() { return 25; }
12893 }
12894 
12895 
12896 /++
12897 
12898 +/
12899 version(none)
12900 class RichTextDisplay : Widget {
12901 	@property void content(string c) {}
12902 	void appendContent(string c) {}
12903 }
12904 
12905 ///
12906 class MessageBox : Window {
12907 	private string message;
12908 	MessageBoxButton buttonPressed = MessageBoxButton.None;
12909 	///
12910 	this(string message, string[] buttons = ["OK"], MessageBoxButton[] buttonIds = [MessageBoxButton.OK]) {
12911 		super(300, 100);
12912 
12913 		assert(buttons.length);
12914 		assert(buttons.length ==  buttonIds.length);
12915 
12916 		this.message = message;
12917 
12918 		int buttonsWidth = cast(int) buttons.length * 50 + (cast(int) buttons.length - 1) * 16;
12919 		buttonsWidth = scaleWithDpi(buttonsWidth);
12920 
12921 		int x = this.width / 2 - buttonsWidth / 2;
12922 
12923 		foreach(idx, buttonText; buttons) {
12924 			auto button = new Button(buttonText, this);
12925 			button.x = x;
12926 			button.y = height - (button.height + 10);
12927 			button.addEventListener(EventType.triggered, ((size_t idx) { return () {
12928 				this.buttonPressed = buttonIds[idx];
12929 				win.close();
12930 			}; })(idx));
12931 
12932 			button.registerMovement();
12933 			x += button.width;
12934 			x += scaleWithDpi(16);
12935 			if(idx == 0)
12936 				button.focus();
12937 		}
12938 
12939 		win.show();
12940 		redraw();
12941 	}
12942 
12943 	override void paint(WidgetPainter painter) {
12944 		super.paint(painter);
12945 
12946 		auto cs = getComputedStyle();
12947 
12948 		painter.outlineColor = cs.foregroundColor();
12949 		painter.fillColor = cs.foregroundColor();
12950 
12951 		painter.drawText(Point(0, 0), message, Point(width, height / 2), TextAlignment.Center | TextAlignment.VerticalCenter);
12952 	}
12953 
12954 	// this one is all fixed position
12955 	override void recomputeChildLayout() {}
12956 }
12957 
12958 ///
12959 enum MessageBoxStyle {
12960 	OK, ///
12961 	OKCancel, ///
12962 	RetryCancel, ///
12963 	YesNo, ///
12964 	YesNoCancel, ///
12965 	RetryCancelContinue /// In a multi-part process, if one part fails, ask the user if you should retry that failed step, cancel the entire process, or just continue with the next step, accepting failure on this step.
12966 }
12967 
12968 ///
12969 enum MessageBoxIcon {
12970 	None, ///
12971 	Info, ///
12972 	Warning, ///
12973 	Error ///
12974 }
12975 
12976 /// Identifies the button the user pressed on a message box.
12977 enum MessageBoxButton {
12978 	None, /// The user closed the message box without clicking any of the buttons.
12979 	OK, ///
12980 	Cancel, ///
12981 	Retry, ///
12982 	Yes, ///
12983 	No, ///
12984 	Continue ///
12985 }
12986 
12987 
12988 /++
12989 	Displays a modal message box, blocking until the user dismisses it.
12990 
12991 	Returns: the button pressed.
12992 +/
12993 MessageBoxButton messageBox(string title, string message, MessageBoxStyle style = MessageBoxStyle.OK, MessageBoxIcon icon = MessageBoxIcon.None) {
12994 	version(win32_widgets) {
12995 		WCharzBuffer t = WCharzBuffer(title);
12996 		WCharzBuffer m = WCharzBuffer(message);
12997 		UINT type;
12998 		with(MessageBoxStyle)
12999 		final switch(style) {
13000 			case OK: type |= MB_OK; break;
13001 			case OKCancel: type |= MB_OKCANCEL; break;
13002 			case RetryCancel: type |= MB_RETRYCANCEL; break;
13003 			case YesNo: type |= MB_YESNO; break;
13004 			case YesNoCancel: type |= MB_YESNOCANCEL; break;
13005 			case RetryCancelContinue: type |= MB_CANCELTRYCONTINUE; break;
13006 		}
13007 		with(MessageBoxIcon)
13008 		final switch(icon) {
13009 			case None: break;
13010 			case Info: type |= MB_ICONINFORMATION; break;
13011 			case Warning: type |= MB_ICONWARNING; break;
13012 			case Error: type |= MB_ICONERROR; break;
13013 		}
13014 		switch(MessageBoxW(null, m.ptr, t.ptr, type)) {
13015 			case IDOK: return MessageBoxButton.OK;
13016 			case IDCANCEL: return MessageBoxButton.Cancel;
13017 			case IDTRYAGAIN, IDRETRY: return MessageBoxButton.Retry;
13018 			case IDYES: return MessageBoxButton.Yes;
13019 			case IDNO: return MessageBoxButton.No;
13020 			case IDCONTINUE: return MessageBoxButton.Continue;
13021 			default: return MessageBoxButton.None;
13022 		}
13023 	} else {
13024 		string[] buttons;
13025 		MessageBoxButton[] buttonIds;
13026 		with(MessageBoxStyle)
13027 		final switch(style) {
13028 			case OK:
13029 				buttons = ["OK"];
13030 				buttonIds = [MessageBoxButton.OK];
13031 			break;
13032 			case OKCancel:
13033 				buttons = ["OK", "Cancel"];
13034 				buttonIds = [MessageBoxButton.OK, MessageBoxButton.Cancel];
13035 			break;
13036 			case RetryCancel:
13037 				buttons = ["Retry", "Cancel"];
13038 				buttonIds = [MessageBoxButton.Retry, MessageBoxButton.Cancel];
13039 			break;
13040 			case YesNo:
13041 				buttons = ["Yes", "No"];
13042 				buttonIds = [MessageBoxButton.Yes, MessageBoxButton.No];
13043 			break;
13044 			case YesNoCancel:
13045 				buttons = ["Yes", "No", "Cancel"];
13046 				buttonIds = [MessageBoxButton.Yes, MessageBoxButton.No, MessageBoxButton.Cancel];
13047 			break;
13048 			case RetryCancelContinue:
13049 				buttons = ["Try Again", "Cancel", "Continue"];
13050 				buttonIds = [MessageBoxButton.Retry, MessageBoxButton.Cancel, MessageBoxButton.Continue];
13051 			break;
13052 		}
13053 		auto mb = new MessageBox(message, buttons, buttonIds);
13054 		EventLoop el = EventLoop.get;
13055 		el.run(() { return !mb.win.closed; });
13056 		return mb.buttonPressed;
13057 	}
13058 }
13059 
13060 /// ditto
13061 int messageBox(string message, MessageBoxStyle style = MessageBoxStyle.OK, MessageBoxIcon icon = MessageBoxIcon.None) {
13062 	return messageBox(null, message, style, icon);
13063 }
13064 
13065 
13066 
13067 ///
13068 alias void delegate(Widget handlerAttachedTo, Event event) EventHandler;
13069 
13070 /++
13071 	This is an opaque type you can use to disconnect an event handler when you're no longer interested.
13072 
13073 	History:
13074 		The data members were `public` (albiet undocumented and not intended for use) prior to May 13, 2021. They are now `private`, reflecting the single intended use of this object.
13075 +/
13076 struct EventListener {
13077 	private Widget widget;
13078 	private string event;
13079 	private EventHandler handler;
13080 	private bool useCapture;
13081 
13082 	///
13083 	void disconnect() {
13084 		widget.removeEventListener(this);
13085 	}
13086 }
13087 
13088 /++
13089 	The purpose of this enum was to give a compile-time checked version of various standard event strings.
13090 
13091 	Now, I recommend you use a statically typed event object instead.
13092 
13093 	See_Also: [Event]
13094 +/
13095 enum EventType : string {
13096 	click = "click", ///
13097 
13098 	mouseenter = "mouseenter", ///
13099 	mouseleave = "mouseleave", ///
13100 	mousein = "mousein", ///
13101 	mouseout = "mouseout", ///
13102 	mouseup = "mouseup", ///
13103 	mousedown = "mousedown", ///
13104 	mousemove = "mousemove", ///
13105 
13106 	keydown = "keydown", ///
13107 	keyup = "keyup", ///
13108 	char_ = "char", ///
13109 
13110 	focus = "focus", ///
13111 	blur = "blur", ///
13112 
13113 	triggered = "triggered", ///
13114 
13115 	change = "change", ///
13116 }
13117 
13118 /++
13119 	Represents an event that is currently being processed.
13120 
13121 
13122 	Minigui's event model is based on the web browser. An event has a name, a target,
13123 	and an associated data object. It starts from the window and works its way down through
13124 	the target through all intermediate [Widget]s, triggering capture phase handlers as it goes,
13125 	then goes back up again all the way back to the window, triggering bubble phase handlers. At
13126 	the end, if [Event.preventDefault] has not been called, it calls the target widget's default
13127 	handlers for the event (please note that default handlers will be called even if [Event.stopPropagation]
13128 	was called; that just stops it from calling other handlers in the widget tree, but the default happens
13129 	whenever propagation is done, not only if it gets to the end of the chain).
13130 
13131 	This model has several nice points:
13132 
13133 	$(LIST
13134 		* It is easy to delegate dynamic handlers to a parent. You can have a parent container
13135 		  with event handlers set, then add/remove children as much as you want without needing
13136 		  to manage the event handlers on them - the parent alone can manage everything.
13137 
13138 		* It is easy to create new custom events in your application.
13139 
13140 		* It is familiar to many web developers.
13141 	)
13142 
13143 	There's a few downsides though:
13144 
13145 	$(LIST
13146 		* There's not a lot of type safety.
13147 
13148 		* You don't get a static list of what events a widget can emit.
13149 
13150 		* Tracing where an event got cancelled along the chain can get difficult; the downside of
13151 		  the central delegation benefit is it can be lead to debugging of action at a distance.
13152 	)
13153 
13154 	In May 2021, I started to adjust this model to minigui takes better advantage of D over Javascript
13155 	while keeping the benefits - and most compatibility with - the existing model. The main idea is
13156 	to simply use a D object type which provides a static interface as well as a built-in event name.
13157 	Then, a new static interface allows you to see what an event can emit and attach handlers to it
13158 	similarly to C#, which just forwards to the JS style api. They're fully compatible so you can still
13159 	delegate to a parent and use custom events as well as using the runtime dynamic access, in addition
13160 	to having a little more help from the D compiler and documentation generator.
13161 
13162 	Your code would change like this:
13163 
13164 	---
13165 	// old
13166 	widget.addEventListener("keydown", (Event ev) { ... }, /* optional arg */ useCapture );
13167 
13168 	// new
13169 	widget.addEventListener((KeyDownEvent ev) { ... }, /* optional arg */ useCapture );
13170 	---
13171 
13172 	The old-style code will still work, but using certain members of the [Event] class will generate deprecation warnings. Changing handlers to the new style will silence all those warnings at once without requiring any other changes to your code.
13173 
13174 	All you have to do is replace the string with a specific Event subclass. It will figure out the event string from the class.
13175 
13176 	Alternatively, you can cast the Event yourself to the appropriate subclass, but it is easier to let the library do it for you!
13177 
13178 	Thus the family of functions are:
13179 
13180 	[Widget.addEventListener] is the fully-flexible base method. It has two main overload families: one with the string and one without. The one with the string takes the Event object, the one without determines the string from the type you pass. The string "*" matches ALL events that pass through.
13181 
13182 	[Widget.addDirectEventListener] is addEventListener, but only calls the handler if target == this. Useful for something you can't afford to delegate.
13183 
13184 	[Widget.setDefaultEventHandler] is what is called if no preventDefault was called. This should be called in the widget's constructor to set default behaivor. Default event handlers are only called on the event target.
13185 
13186 	Let's implement a custom widget that can emit a ChangeEvent describing its `checked` property:
13187 
13188 	---
13189 	class MyCheckbox : Widget {
13190 		/// This gives a chance to document it and generates a convenience function to send it and attach handlers.
13191 		/// It is NOT actually required but should be used whenever possible.
13192 		mixin Emits!(ChangeEvent!bool);
13193 
13194 		this(Widget parent) {
13195 			super(parent);
13196 			setDefaultEventHandler((ClickEvent) { checked = !checked; });
13197 		}
13198 
13199 		private bool _checked;
13200 		@property bool checked() { return _checked; }
13201 		@property void checked(bool set) {
13202 			_checked = set;
13203 			emit!(ChangeEvent!bool)(&checked);
13204 		}
13205 	}
13206 	---
13207 
13208 	## Creating Your Own Events
13209 
13210 	To avoid clashing in the string namespace, your events should use your module and class name as the event string. The simple code `mixin Register;` in your Event subclass will do this for you.
13211 
13212 	---
13213 	class MyEvent : Event {
13214 		this(Widget target) { super(EventString, target); }
13215 		mixin Register; // adds EventString and other reflection information
13216 	}
13217 	---
13218 
13219 	Then declare that it is sent with the [Emits] mixin, so you can use [Widget.emit] to dispatch it.
13220 
13221 	History:
13222 		Prior to May 2021, Event had a set of pre-made members with no extensibility (outside of diy casts) and no static checks on field presence.
13223 
13224 		After that, those old pre-made members are deprecated accessors and the fields are moved to child classes. To transition, change string events to typed events or do a dynamic cast (don't forget the null check!) in your handler.
13225 +/
13226 /+
13227 
13228 	## General Conventions
13229 
13230 	Change events should NOT be emitted when a value is changed programmatically. Indeed, methods should usually not send events. The point of an event is to know something changed and when you call a method, you already know about it.
13231 
13232 
13233 	## Qt-style signals and slots
13234 
13235 	Some events make sense to use with just name and data type. These are one-way notifications with no propagation nor default behavior and thus separate from the other event system.
13236 
13237 	The intention is for events to be used when
13238 
13239 	---
13240 	class Demo : Widget {
13241 		this() {
13242 			myPropertyChanged = Signal!int(this);
13243 		}
13244 		@property myProperty(int v) {
13245 			myPropertyChanged.emit(v);
13246 		}
13247 
13248 		Signal!int myPropertyChanged; // i need to get `this` off it and inspect the name...
13249 		// but it can just genuinely not care about `this` since that's not really passed.
13250 	}
13251 
13252 	class Foo : Widget {
13253 		// the slot uda is not necessary, but it helps the script and ui builder find it.
13254 		@slot void setValue(int v) { ... }
13255 	}
13256 
13257 	demo.myPropertyChanged.connect(&foo.setValue);
13258 	---
13259 
13260 	The Signal type has a disabled default constructor, meaning your widget constructor must pass `this` to it in its constructor.
13261 
13262 	Some events may also wish to implement the Signal interface. These use particular arguments to call a method automatically.
13263 
13264 	class StringChangeEvent : ChangeEvent, Signal!string {
13265 		mixin SignalImpl
13266 	}
13267 
13268 +/
13269 class Event : ReflectableProperties {
13270 	/// Creates an event without populating any members and without sending it. See [dispatch]
13271 	this(string eventName, Widget emittedBy) {
13272 		this.eventName = eventName;
13273 		this.srcElement = emittedBy;
13274 	}
13275 
13276 
13277 	/// Implementations for the [ReflectableProperties] interface/
13278 	void getPropertiesList(scope void delegate(string name) sink) const {}
13279 	/// ditto
13280 	void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink) { }
13281 	/// ditto
13282 	SetPropertyResult setPropertyFromString(string name, scope const(char)[] str, bool strIsJson) {
13283 		return SetPropertyResult.notPermitted;
13284 	}
13285 
13286 
13287 	/+
13288 	/++
13289 		This is an internal implementation detail of [Register] and is subject to be changed or removed at any time without notice.
13290 
13291 		It is just protected so the mixin template can see it from user modules. If I made it private, even my own mixin template couldn't see it due to mixin scoping rules.
13292 	+/
13293 	protected final void sinkJsonString(string memberName, scope const(char)[] value, scope void delegate(string name, scope const(char)[] value) finalSink) {
13294 		if(value.length == 0) {
13295 			finalSink(memberName, `""`);
13296 			return;
13297 		}
13298 
13299 		char[1024] bufferBacking;
13300 		char[] buffer = bufferBacking;
13301 		int bufferPosition;
13302 
13303 		void sink(char ch) {
13304 			if(bufferPosition >= buffer.length)
13305 				buffer.length = buffer.length + 1024;
13306 			buffer[bufferPosition++] = ch;
13307 		}
13308 
13309 		sink('"');
13310 
13311 		foreach(ch; value) {
13312 			switch(ch) {
13313 				case '\\':
13314 					sink('\\'); sink('\\');
13315 				break;
13316 				case '"':
13317 					sink('\\'); sink('"');
13318 				break;
13319 				case '\n':
13320 					sink('\\'); sink('n');
13321 				break;
13322 				case '\r':
13323 					sink('\\'); sink('r');
13324 				break;
13325 				case '\t':
13326 					sink('\\'); sink('t');
13327 				break;
13328 				default:
13329 					sink(ch);
13330 			}
13331 		}
13332 
13333 		sink('"');
13334 
13335 		finalSink(memberName, buffer[0 .. bufferPosition]);
13336 	}
13337 	+/
13338 
13339 	/+
13340 	enum EventInitiator {
13341 		system,
13342 		minigui,
13343 		user
13344 	}
13345 
13346 	immutable EventInitiator; initiatedBy;
13347 	+/
13348 
13349 	/++
13350 		Events should generally follow the propagation model, but there's some exceptions
13351 		to that rule. If so, they should override this to return false. In that case, only
13352 		bubbling event handlers on the target itself and capturing event handlers on the containing
13353 		window will be called. (That is, [dispatch] will call [sendDirectly] instead of doing the normal
13354 		capture -> target -> bubble process.)
13355 
13356 		History:
13357 			Added May 12, 2021
13358 	+/
13359 	bool propagates() const pure nothrow @nogc @safe {
13360 		return true;
13361 	}
13362 
13363 	/++
13364 		hints as to whether preventDefault will actually do anything. not entirely reliable.
13365 
13366 		History:
13367 			Added May 14, 2021
13368 	+/
13369 	bool cancelable() const pure nothrow @nogc @safe {
13370 		return true;
13371 	}
13372 
13373 	/++
13374 		You can mix this into child class to register some boilerplate. It includes the `EventString`
13375 		member, a constructor, and implementations of the dynamic get data interfaces.
13376 
13377 		If you fail to do this, your event will probably not have full compatibility but it might still work for you.
13378 
13379 
13380 		You can override the default EventString by simply providing your own in the form of
13381 		`enum string EventString = "some.name";` The default is the name of your class and its parent entity
13382 		which provides some namespace protection against conflicts in other libraries while still being fairly
13383 		easy to use.
13384 
13385 		If you provide your own constructor, it will override the default constructor provided here. A constructor
13386 		must call `super(EventString, passed_widget_target)` at some point. The `passed_widget_target` must be the
13387 		first argument to your constructor.
13388 
13389 		History:
13390 			Added May 13, 2021.
13391 	+/
13392 	protected static mixin template Register() {
13393 		public enum string EventString = __traits(identifier, __traits(parent, typeof(this))) ~ "." ~  __traits(identifier, typeof(this));
13394 		this(Widget target) { super(EventString, target); }
13395 
13396 		mixin ReflectableProperties.RegisterGetters;
13397 	}
13398 
13399 	/++
13400 		This is the widget that emitted the event.
13401 
13402 
13403 		The aliased names come from Javascript for ease of web developers to transition in, but they're all synonyms.
13404 
13405 		History:
13406 			The `source` name was added on May 14, 2021. It is a little weird that `source` and `target` are synonyms,
13407 			but that's a side effect of it doing both capture and bubble handlers and people are used to it from the web
13408 			so I don't intend to remove these aliases.
13409 	+/
13410 	Widget source;
13411 	/// ditto
13412 	alias source target;
13413 	/// ditto
13414 	alias source srcElement;
13415 
13416 	Widget relatedTarget; /// Note: likely to be deprecated at some point.
13417 
13418 	/// Prevents the default event handler (if there is one) from being called
13419 	void preventDefault() {
13420 		lastDefaultPrevented = true;
13421 		defaultPrevented = true;
13422 	}
13423 
13424 	/// Stops the event propagation immediately.
13425 	void stopPropagation() {
13426 		propagationStopped = true;
13427 	}
13428 
13429 	private bool defaultPrevented;
13430 	private bool propagationStopped;
13431 	private string eventName;
13432 
13433 	private bool isBubbling;
13434 
13435 	/// This is an internal implementation detail you should not use. It would be private if the language allowed it and it may be removed without notice.
13436 	protected void adjustScrolling() { }
13437 	/// ditto
13438 	protected void adjustClientCoordinates(int deltaX, int deltaY) { }
13439 
13440 	/++
13441 		this sends it only to the target. If you want propagation, use dispatch() instead.
13442 
13443 		This should be made private!!!
13444 
13445 	+/
13446 	void sendDirectly() {
13447 		if(srcElement is null)
13448 			return;
13449 
13450 		// i capturing on the parent too. The main reason for this is that gives a central place to log all events for the debug window.
13451 
13452 		//debug if(eventName != "mousemove" && target !is null && target.parentWindow && target.parentWindow.devTools)
13453 			//target.parentWindow.devTools.log("Event ", eventName, " dispatched directly to ", srcElement);
13454 
13455 		adjustScrolling();
13456 
13457 		if(auto e = target.parentWindow) {
13458 			if(auto handlers = "*" in e.capturingEventHandlers)
13459 			foreach(handler; *handlers)
13460 				if(handler) handler(e, this);
13461 			if(auto handlers = eventName in e.capturingEventHandlers)
13462 			foreach(handler; *handlers)
13463 				if(handler) handler(e, this);
13464 		}
13465 
13466 		auto e = srcElement;
13467 
13468 		if(auto handlers = eventName in e.bubblingEventHandlers)
13469 		foreach(handler; *handlers)
13470 			if(handler) handler(e, this);
13471 
13472 		if(auto handlers = "*" in e.bubblingEventHandlers)
13473 		foreach(handler; *handlers)
13474 			if(handler) handler(e, this);
13475 
13476 		// there's never a default for a catch-all event
13477 		if(!defaultPrevented)
13478 			if(eventName in e.defaultEventHandlers)
13479 				e.defaultEventHandlers[eventName](e, this);
13480 	}
13481 
13482 	/// this dispatches the element using the capture -> target -> bubble process
13483 	void dispatch() {
13484 		if(srcElement is null)
13485 			return;
13486 
13487 		if(!propagates) {
13488 			sendDirectly;
13489 			return;
13490 		}
13491 
13492 		//debug if(eventName != "mousemove" && target !is null && target.parentWindow && target.parentWindow.devTools)
13493 			//target.parentWindow.devTools.log("Event ", eventName, " dispatched to ", srcElement);
13494 
13495 		adjustScrolling();
13496 		// first capture, then bubble
13497 
13498 		Widget[] chain;
13499 		Widget curr = srcElement;
13500 		while(curr) {
13501 			auto l = curr;
13502 			chain ~= l;
13503 			curr = curr.parent;
13504 		}
13505 
13506 		isBubbling = false;
13507 
13508 		foreach_reverse(e; chain) {
13509 			if(auto handlers = "*" in e.capturingEventHandlers)
13510 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
13511 
13512 			if(propagationStopped)
13513 				break;
13514 
13515 			if(auto handlers = eventName in e.capturingEventHandlers)
13516 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
13517 
13518 			// the default on capture should really be to always do nothing
13519 
13520 			//if(!defaultPrevented)
13521 			//	if(eventName in e.defaultEventHandlers)
13522 			//		e.defaultEventHandlers[eventName](e.element, this);
13523 
13524 			if(propagationStopped)
13525 				break;
13526 		}
13527 
13528 		int adjustX;
13529 		int adjustY;
13530 
13531 		isBubbling = true;
13532 		if(!propagationStopped)
13533 		foreach(e; chain) {
13534 			if(auto handlers = eventName in e.bubblingEventHandlers)
13535 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
13536 
13537 			if(propagationStopped)
13538 				break;
13539 
13540 			if(auto handlers = "*" in e.bubblingEventHandlers)
13541 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
13542 
13543 			if(propagationStopped)
13544 				break;
13545 
13546 			if(e.encapsulatedChildren()) {
13547 				adjustClientCoordinates(adjustX, adjustY);
13548 				target = e;
13549 			} else {
13550 				adjustX += e.x;
13551 				adjustY += e.y;
13552 			}
13553 		}
13554 
13555 		if(!defaultPrevented)
13556 		foreach(e; chain) {
13557 			if(eventName in e.defaultEventHandlers)
13558 				e.defaultEventHandlers[eventName](e, this);
13559 		}
13560 	}
13561 
13562 
13563 	/* old compatibility things */
13564 	deprecated("Use some subclass of KeyEventBase instead of plain Event in your handler going forward. WARNING these may crash on non-key events!")
13565 	final @property {
13566 		Key key() { return (cast(KeyEventBase) this).key; }
13567 		KeyEvent originalKeyEvent() { return (cast(KeyEventBase) this).originalKeyEvent; }
13568 
13569 		bool ctrlKey() { return (cast(KeyEventBase) this).ctrlKey; }
13570 		bool altKey() { return (cast(KeyEventBase) this).altKey; }
13571 		bool shiftKey() { return (cast(KeyEventBase) this).shiftKey; }
13572 	}
13573 
13574 	deprecated("Use some subclass of MouseEventBase instead of Event in your handler going forward. WARNING these may crash on non-mouse events!")
13575 	final @property {
13576 		int clientX() { return (cast(MouseEventBase) this).clientX; }
13577 		int clientY() { return (cast(MouseEventBase) this).clientY; }
13578 
13579 		int viewportX() { return (cast(MouseEventBase) this).viewportX; }
13580 		int viewportY() { return (cast(MouseEventBase) this).viewportY; }
13581 
13582 		int button() { return (cast(MouseEventBase) this).button; }
13583 		int buttonLinear() { return (cast(MouseEventBase) this).buttonLinear; }
13584 	}
13585 
13586 	deprecated("Use either a KeyEventBase or a MouseEventBase instead of Event in your handler going forward")
13587 	final @property {
13588 		int state() {
13589 			if(auto meb = cast(MouseEventBase) this)
13590 				return meb.state;
13591 			if(auto keb = cast(KeyEventBase) this)
13592 				return keb.state;
13593 			assert(0);
13594 		}
13595 	}
13596 
13597 	deprecated("Use a CharEvent instead of Event in your handler going forward")
13598 	final @property {
13599 		dchar character() {
13600 			if(auto ce = cast(CharEvent) this)
13601 				return ce.character;
13602 			return dchar.init;
13603 		}
13604 	}
13605 
13606 	// for change events
13607 	@property {
13608 		///
13609 		int intValue() { return 0; }
13610 		///
13611 		string stringValue() { return null; }
13612 	}
13613 }
13614 
13615 /++
13616 	This lets you statically verify you send the events you claim you send and gives you a hook to document them.
13617 
13618 	Please note that a widget may send events not listed as Emits. You can always construct and dispatch
13619 	dynamic and custom events, but the static list helps ensure you get them right.
13620 
13621 	If this is declared, you can use [Widget.emit] to send the event.
13622 
13623 	All events work the same way though, following the capture->widget->bubble model described under [Event].
13624 
13625 	History:
13626 		Added May 4, 2021
13627 +/
13628 mixin template Emits(EventType) {
13629 	import arsd.minigui : EventString;
13630 	static if(is(EventType : Event) && !is(EventType == Event))
13631 		mixin("private EventType[0] emits_" ~ EventStringIdentifier!EventType ~";");
13632 	else
13633 		static assert(0, "You can only emit subclasses of Event");
13634 }
13635 
13636 /// ditto
13637 mixin template Emits(string eventString) {
13638 	mixin("private Event[0] emits_" ~ eventString ~";");
13639 }
13640 
13641 /*
13642 class SignalEvent(string name) : Event {
13643 
13644 }
13645 */
13646 
13647 /++
13648 	Command Events are used with a widget wants to issue a higher-level, yet loosely coupled command do its parents and other interested listeners, for example, "scroll up".
13649 
13650 
13651 	Command Events are a bit special in the way they're used. You don't typically refer to them by object, but instead by a name string and a set of arguments. The expectation is that they will be delegated to a parent, which "consumes" the command - it handles it and stops its propagation upward. The [consumesCommand] method will call your handler with the arguments, then stop the command event's propagation for you, meaning you don't have to call [Event.stopPropagation]. A command event should have no default behavior, so calling [Event.preventDefault] is not necessary either.
13652 
13653 	History:
13654 		Added on May 13, 2021. Prior to that, you'd most likely `addEventListener(EventType.triggered, ...)` to handle similar things.
13655 +/
13656 class CommandEvent : Event {
13657 	enum EventString = "command";
13658 	this(Widget source, string CommandString = EventString) {
13659 		super(CommandString, source);
13660 	}
13661 }
13662 
13663 /++
13664 	A [CommandEvent] is typically actually an instance of these to hold the strongly-typed arguments.
13665 +/
13666 class CommandEventWithArgs(Args...) : CommandEvent {
13667 	this(Widget source, string CommandString, Args args) { super(source, CommandString); this.args = args; }
13668 	Args args;
13669 }
13670 
13671 /++
13672 	Declares that the given widget consumes a command identified by the `CommandString` AND containing `Args`. Your `handler` is called with the arguments, then the event's propagation is stopped, so it will not be seen by the consumer's parents.
13673 
13674 	See [CommandEvent] for more information.
13675 
13676 	Returns:
13677 		The [EventListener] you can use to remove the handler.
13678 +/
13679 EventListener consumesCommand(string CommandString, WidgetType, Args...)(WidgetType w, void delegate(Args) handler) {
13680 	return w.addEventListener(CommandString, (Event ev) {
13681 		if(ev.target is w)
13682 			return; // it does not consume its own commands!
13683 		if(auto cev = cast(CommandEventWithArgs!Args) ev) {
13684 			handler(cev.args);
13685 			ev.stopPropagation();
13686 		}
13687 	});
13688 }
13689 
13690 /++
13691 	Emits a command to the sender widget's parents with the given `CommandString` and `args`. You have no way of knowing if it was ever actually consumed due to the loose coupling. Instead, the consumer may broadcast a state update back toward you.
13692 +/
13693 void emitCommand(string CommandString, WidgetType, Args...)(WidgetType w, Args args) {
13694 	auto event = new CommandEventWithArgs!Args(w, CommandString, args);
13695 	event.dispatch();
13696 }
13697 
13698 class ResizeEvent : Event {
13699 	enum EventString = "resize";
13700 
13701 	this(Widget target) { super(EventString, target); }
13702 
13703 	override bool propagates() const { return false; }
13704 }
13705 
13706 /++
13707 	ClosingEvent is fired when a user is attempting to close a window. You can `preventDefault` to cancel the close.
13708 
13709 	ClosedEvent happens when the window has been closed. It is already gone by the time this event fires, meaning you cannot prevent the close. Use [ClosingEvent] if you want to cancel, use [ClosedEvent] if you simply want to be notified.
13710 
13711 	History:
13712 		Added June 21, 2021 (dub v10.1)
13713 +/
13714 class ClosingEvent : Event {
13715 	enum EventString = "closing";
13716 
13717 	this(Widget target) { super(EventString, target); }
13718 
13719 	override bool propagates() const { return false; }
13720 	override bool cancelable() const { return true; }
13721 }
13722 
13723 /// ditto
13724 class ClosedEvent : Event {
13725 	enum EventString = "closed";
13726 
13727 	this(Widget target) { super(EventString, target); }
13728 
13729 	override bool propagates() const { return false; }
13730 	override bool cancelable() const { return false; }
13731 }
13732 
13733 ///
13734 class BlurEvent : Event {
13735 	enum EventString = "blur";
13736 
13737 	// FIXME: related target?
13738 	this(Widget target) { super(EventString, target); }
13739 
13740 	override bool propagates() const { return false; }
13741 }
13742 
13743 ///
13744 class FocusEvent : Event {
13745 	enum EventString = "focus";
13746 
13747 	// FIXME: related target?
13748 	this(Widget target) { super(EventString, target); }
13749 
13750 	override bool propagates() const { return false; }
13751 }
13752 
13753 /++
13754 	FocusInEvent is a FocusEvent that propagates, while FocusOutEvent is a BlurEvent that propagates.
13755 
13756 	History:
13757 		Added July 3, 2021
13758 +/
13759 class FocusInEvent : Event {
13760 	enum EventString = "focusin";
13761 
13762 	// FIXME: related target?
13763 	this(Widget target) { super(EventString, target); }
13764 
13765 	override bool cancelable() const { return false; }
13766 }
13767 
13768 /// ditto
13769 class FocusOutEvent : Event {
13770 	enum EventString = "focusout";
13771 
13772 	// FIXME: related target?
13773 	this(Widget target) { super(EventString, target); }
13774 
13775 	override bool cancelable() const { return false; }
13776 }
13777 
13778 ///
13779 class ScrollEvent : Event {
13780 	enum EventString = "scroll";
13781 	this(Widget target) { super(EventString, target); }
13782 
13783 	override bool cancelable() const { return false; }
13784 }
13785 
13786 /++
13787 	Indicates that a character has been typed by the user. Normally dispatched to the currently focused widget.
13788 
13789 	History:
13790 		Added May 2, 2021. Previously, this was simply a "char" event and `character` as a member of the [Event] base class.
13791 +/
13792 class CharEvent : Event {
13793 	enum EventString = "char";
13794 	this(Widget target, dchar ch) {
13795 		character = ch;
13796 		super(EventString, target);
13797 	}
13798 
13799 	immutable dchar character;
13800 }
13801 
13802 /++
13803 	You should generally use a `ChangeEvent!Type` instead of this directly. See [ChangeEvent] for more information.
13804 +/
13805 abstract class ChangeEventBase : Event {
13806 	enum EventString = "change";
13807 	this(Widget target) {
13808 		super(EventString, target);
13809 	}
13810 
13811 	/+
13812 		// idk where or how exactly i want to do this.
13813 		// i might come back to it later.
13814 
13815 	// If a widget itself broadcasts one of theses itself, it stops propagation going down
13816 	// this way the source doesn't get too confused (think of a nested scroll widget)
13817 	//
13818 	// the idea is like the scroll bar emits a command event saying like "scroll left one line"
13819 	// then you consume that command and change you scroll x position to whatever. then you do
13820 	// some kind of change event that is broadcast back to the children and any horizontal scroll
13821 	// listeners are now able to update, without having an explicit connection between them.
13822 	void broadcastToChildren(string fieldName) {
13823 
13824 	}
13825 	+/
13826 }
13827 
13828 /++
13829 	Single-value widgets (that is, ones with a programming interface that just expose a value that the user has control over) should emit this after their value changes.
13830 
13831 
13832 	Generally speaking, if your widget can reasonably have a `@property T value();` or `@property bool checked();` method, it should probably emit this event when that value changes to inform its parents that they can now read a new value. Whether you emit it on each keystroke or other intermediate values or only when a value is committed (e.g. when the user leaves the field) is up to the widget. You might even make that a togglable property depending on your needs (emitting events can get expensive).
13833 
13834 	The delegate you pass to the constructor ought to be a handle to your getter property. If your widget has `@property string value()` for example, you emit `ChangeEvent!string(&value);`
13835 
13836 	Since it is emitted after the value has already changed, [preventDefault] is unlikely to do anything.
13837 
13838 	History:
13839 		Added May 11, 2021. Prior to that, widgets would more likely just send `new Event("change")`. These typed ChangeEvents are still compatible with listeners subscribed to generic change events.
13840 +/
13841 class ChangeEvent(T) : ChangeEventBase {
13842 	this(Widget target, T delegate() getNewValue) {
13843 		assert(getNewValue !is null);
13844 		this.getNewValue = getNewValue;
13845 		super(target);
13846 	}
13847 
13848 	private T delegate() getNewValue;
13849 
13850 	/++
13851 		Gets the new value that just changed.
13852 	+/
13853 	@property T value() {
13854 		return getNewValue();
13855 	}
13856 
13857 	/// compatibility method for old generic Events
13858 	static if(is(immutable T == immutable int))
13859 		override int intValue() { return value; }
13860 	/// ditto
13861 	static if(is(immutable T == immutable string))
13862 		override string stringValue() { return value; }
13863 }
13864 
13865 /++
13866 	Contains shared properties for [KeyDownEvent]s and [KeyUpEvent]s.
13867 
13868 
13869 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
13870 
13871 	History:
13872 		Added May 2, 2021. Previously, its properties were members of the [Event] base class.
13873 +/
13874 abstract class KeyEventBase : Event {
13875 	this(string name, Widget target) {
13876 		super(name, target);
13877 	}
13878 
13879 	// for key events
13880 	Key key; ///
13881 
13882 	KeyEvent originalKeyEvent;
13883 
13884 	/++
13885 		Indicates the current state of the given keyboard modifier keys.
13886 
13887 		History:
13888 			Added to events on April 15, 2020.
13889 	+/
13890 	bool ctrlKey;
13891 
13892 	/// ditto
13893 	bool altKey;
13894 
13895 	/// ditto
13896 	bool shiftKey;
13897 
13898 	/++
13899 		The raw bitflags that are parsed out into [ctrlKey], [altKey], and [shiftKey].
13900 
13901 		See [arsd.simpledisplay.ModifierState] for other possible flags.
13902 	+/
13903 	int state;
13904 
13905 	mixin Register;
13906 }
13907 
13908 /++
13909 	Indicates that the user has pressed a key on the keyboard, or if they've been holding it long enough to repeat (key down events are sent both on the initial press then repeated by the OS on its own time.) For available properties, see [KeyEventBase].
13910 
13911 
13912 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
13913 
13914 	Please note that a `KeyDownEvent` will also often send a [CharEvent], but there is not necessarily a one-to-one relationship between them. For example, a capital letter may send KeyDownEvent for Key.Shift, then KeyDownEvent for the letter's key (this key may not match the letter due to keyboard mappings), then CharEvent for the letter, then KeyUpEvent for the letter, and finally, KeyUpEvent for shift.
13915 
13916 	For some characters, there are other key down events as well. A compose key can be pressed and released, followed by several letters pressed and released to generate one character. This is why [CharEvent] is a separate entity.
13917 
13918 	See_Also: [KeyUpEvent], [CharEvent]
13919 
13920 	History:
13921 		Added May 2, 2021. Previously, it was only seen as the base [Event] class on "keydown" event listeners.
13922 +/
13923 class KeyDownEvent : KeyEventBase {
13924 	enum EventString = "keydown";
13925 	this(Widget target) { super(EventString, target); }
13926 }
13927 
13928 /++
13929 	Indicates that the user has released a key on the keyboard. For available properties, see [KeyEventBase].
13930 
13931 
13932 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
13933 
13934 	See_Also: [KeyDownEvent], [CharEvent]
13935 
13936 	History:
13937 		Added May 2, 2021. Previously, it was only seen as the base [Event] class on "keyup" event listeners.
13938 +/
13939 class KeyUpEvent : KeyEventBase {
13940 	enum EventString = "keyup";
13941 	this(Widget target) { super(EventString, target); }
13942 }
13943 
13944 /++
13945 	Contains shared properties for various mouse events;
13946 
13947 
13948 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
13949 
13950 	History:
13951 		Added May 2, 2021. Previously, its properties were members of the [Event] base class.
13952 +/
13953 abstract class MouseEventBase : Event {
13954 	this(string name, Widget target) {
13955 		super(name, target);
13956 	}
13957 
13958 	// for mouse events
13959 	int clientX; /// The mouse event location relative to the target widget
13960 	int clientY; /// ditto
13961 
13962 	int viewportX; /// The mouse event location relative to the window origin
13963 	int viewportY; /// ditto
13964 
13965 	int button; /// See: [MouseEvent.button]
13966 	int buttonLinear; /// See: [MouseEvent.buttonLinear]
13967 
13968 	/++
13969 		Indicates the current state of the given keyboard modifier keys.
13970 
13971 		History:
13972 			Added to mouse events on September 28, 2010.
13973 	+/
13974 	bool ctrlKey;
13975 
13976 	/// ditto
13977 	bool altKey;
13978 
13979 	/// ditto
13980 	bool shiftKey;
13981 
13982 
13983 
13984 	int state; ///
13985 
13986 	/++
13987 		for consistent names with key event.
13988 
13989 		History:
13990 			Added September 28, 2021 (dub v10.3)
13991 	+/
13992 	alias modifierState = state;
13993 
13994 	/++
13995 		Mouse wheel movement sends down/up/click events just like other buttons clicking. This method is to help you filter that out.
13996 
13997 		History:
13998 			Added May 15, 2021
13999 	+/
14000 	bool isMouseWheel() {
14001 		return button == MouseButton.wheelUp || button == MouseButton.wheelDown;
14002 	}
14003 
14004 	// private
14005 	override void adjustClientCoordinates(int deltaX, int deltaY) {
14006 		clientX += deltaX;
14007 		clientY += deltaY;
14008 	}
14009 
14010 	override void adjustScrolling() {
14011 	version(custom_widgets) { // TEMP
14012 		viewportX = clientX;
14013 		viewportY = clientY;
14014 		if(auto se = cast(ScrollableWidget) srcElement) {
14015 			clientX += se.scrollOrigin.x;
14016 			clientY += se.scrollOrigin.y;
14017 		} else if(auto se = cast(ScrollableContainerWidget) srcElement) {
14018 			//clientX += se.scrollX_;
14019 			//clientY += se.scrollY_;
14020 		}
14021 	}
14022 	}
14023 
14024 	mixin Register;
14025 }
14026 
14027 /++
14028 	Indicates that the user has worked with the mouse over your widget. For available properties, see [MouseEventBase].
14029 
14030 
14031 	$(WARNING
14032 		Important: MouseDownEvent, MouseUpEvent, ClickEvent, and DoubleClickEvent are all sent for all mouse buttons and
14033 		for wheel movement! You should check the [MouseEventBase.button|button] property in most your handlers to get correct
14034 		behavior.
14035 	)
14036 
14037 	[MouseDownEvent] is sent when the user presses a mouse button. It is also sent on mouse wheel movement.
14038 
14039 	[MouseUpEvent] is sent when the user releases a mouse button.
14040 
14041 	[MouseMoveEvent] is sent when the mouse is moved. Please note you may not receive this in some cases unless a button is also pressed; the system is free to withhold them as an optimization. (In practice, [arsd.simpledisplay] does not request mouse motion event without a held button if it is on a remote X11 link, but does elsewhere at this time.)
14042 
14043 	[ClickEvent] is sent when the user clicks on the widget. It may also be sent with keyboard control, though minigui prefers to send a "triggered" event in addition to a mouse click and instead of a simulated mouse click in cases like keyboard activation of a button.
14044 
14045 	[DoubleClickEvent] is sent when the user clicks twice on a thing quickly, immediately after the second MouseDownEvent. The sequence is: MouseDownEvent, MouseUpEvent, ClickEvent, MouseDownEvent, DoubleClickEvent, MouseUpEvent. The second ClickEvent is NOT sent. Note that this is differnet than Javascript! They would send down,up,click,down,up,click,dblclick. Minigui does it differently because this is the way the Windows OS reports it.
14046 
14047 	[MouseOverEvent] is sent then the mouse first goes over a widget. Please note that this participates in event propagation of children! Use [MouseEnterEvent] instead if you are only interested in a specific element's whole bounding box instead of the top-most element in any particular location.
14048 
14049 	[MouseOutEvent] is sent when the mouse exits a target. Please note that this participates in event propagation of children! Use [MouseLeaveEvent] instead if you are only interested in a specific element's whole bounding box instead of the top-most element in any particular location.
14050 
14051 	[MouseEnterEvent] is sent when the mouse enters the bounding box of a widget.
14052 
14053 	[MouseLeaveEvent] is sent when the mouse leaves the bounding box of a widget.
14054 
14055 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
14056 
14057 	Rationale:
14058 
14059 		If you only want to do drag, mousedown/up works just fine being consistently sent.
14060 
14061 		If you want click, that event does what you expect (if the user mouse downs then moves the mouse off the widget before going up, no click event happens - a click is only down and back up on the same thing).
14062 
14063 		If you want double click and listen to that specifically, it also just works, and if you only cared about clicks, odds are the double click should do the same thing as a single click anyway - the double was prolly accidental - so only sending the event once is prolly what user intended.
14064 
14065 	History:
14066 		Added May 2, 2021. Previously, it was only seen as the base [Event] class on event listeners. See the member [EventString] to see what the associated string is with these elements.
14067 +/
14068 class MouseUpEvent : MouseEventBase {
14069 	enum EventString = "mouseup"; ///
14070 	this(Widget target) { super(EventString, target); }
14071 }
14072 /// ditto
14073 class MouseDownEvent : MouseEventBase {
14074 	enum EventString = "mousedown"; ///
14075 	this(Widget target) { super(EventString, target); }
14076 }
14077 /// ditto
14078 class MouseMoveEvent : MouseEventBase {
14079 	enum EventString = "mousemove"; ///
14080 	this(Widget target) { super(EventString, target); }
14081 }
14082 /// ditto
14083 class ClickEvent : MouseEventBase {
14084 	enum EventString = "click"; ///
14085 	this(Widget target) { super(EventString, target); }
14086 }
14087 /// ditto
14088 class DoubleClickEvent : MouseEventBase {
14089 	enum EventString = "dblclick"; ///
14090 	this(Widget target) { super(EventString, target); }
14091 }
14092 /// ditto
14093 class MouseOverEvent : Event {
14094 	enum EventString = "mouseover"; ///
14095 	this(Widget target) { super(EventString, target); }
14096 }
14097 /// ditto
14098 class MouseOutEvent : Event {
14099 	enum EventString = "mouseout"; ///
14100 	this(Widget target) { super(EventString, target); }
14101 }
14102 /// ditto
14103 class MouseEnterEvent : Event {
14104 	enum EventString = "mouseenter"; ///
14105 	this(Widget target) { super(EventString, target); }
14106 
14107 	override bool propagates() const { return false; }
14108 }
14109 /// ditto
14110 class MouseLeaveEvent : Event {
14111 	enum EventString = "mouseleave"; ///
14112 	this(Widget target) { super(EventString, target); }
14113 
14114 	override bool propagates() const { return false; }
14115 }
14116 
14117 private bool isAParentOf(Widget a, Widget b) {
14118 	if(a is null || b is null)
14119 		return false;
14120 
14121 	while(b !is null) {
14122 		if(a is b)
14123 			return true;
14124 		b = b.parent;
14125 	}
14126 
14127 	return false;
14128 }
14129 
14130 private struct WidgetAtPointResponse {
14131 	Widget widget;
14132 
14133 	// x, y relative to the widget in the response.
14134 	int x;
14135 	int y;
14136 }
14137 
14138 private WidgetAtPointResponse widgetAtPoint(Widget starting, int x, int y) {
14139 	assert(starting !is null);
14140 
14141 	starting.addScrollPosition(x, y);
14142 
14143 	auto child = starting.getChildAtPosition(x, y);
14144 	while(child) {
14145 		if(child.hidden)
14146 			continue;
14147 		starting = child;
14148 		x -= child.x;
14149 		y -= child.y;
14150 		auto r = starting.widgetAtPoint(x, y);//starting.getChildAtPosition(x, y);
14151 		child = r.widget;
14152 		if(child is starting)
14153 			break;
14154 	}
14155 	return WidgetAtPointResponse(starting, x, y);
14156 }
14157 
14158 version(win32_widgets) {
14159 private:
14160 	import core.sys.windows.commctrl;
14161 
14162 	pragma(lib, "comctl32");
14163 	shared static this() {
14164 		// http://msdn.microsoft.com/en-us/library/windows/desktop/bb775507(v=vs.85).aspx
14165 		INITCOMMONCONTROLSEX ic;
14166 		ic.dwSize = cast(DWORD) ic.sizeof;
14167 		ic.dwICC = ICC_UPDOWN_CLASS | ICC_WIN95_CLASSES | ICC_BAR_CLASSES | ICC_PROGRESS_CLASS | ICC_COOL_CLASSES | ICC_STANDARD_CLASSES | ICC_USEREX_CLASSES;
14168 		if(!InitCommonControlsEx(&ic)) {
14169 			//import std.stdio; writeln("ICC failed");
14170 		}
14171 	}
14172 
14173 
14174 	// everything from here is just win32 headers copy pasta
14175 private:
14176 extern(Windows):
14177 
14178 	alias HANDLE HMENU;
14179 	HMENU CreateMenu();
14180 	bool SetMenu(HWND, HMENU);
14181 	HMENU CreatePopupMenu();
14182 	enum MF_POPUP = 0x10;
14183 	enum MF_STRING = 0;
14184 
14185 
14186 	BOOL InitCommonControlsEx(const INITCOMMONCONTROLSEX*);
14187 	struct INITCOMMONCONTROLSEX {
14188 		DWORD dwSize;
14189 		DWORD dwICC;
14190 	}
14191 	enum HINST_COMMCTRL = cast(HINSTANCE) (-1);
14192 enum {
14193         IDB_STD_SMALL_COLOR,
14194         IDB_STD_LARGE_COLOR,
14195         IDB_VIEW_SMALL_COLOR = 4,
14196         IDB_VIEW_LARGE_COLOR = 5
14197 }
14198 enum {
14199         STD_CUT,
14200         STD_COPY,
14201         STD_PASTE,
14202         STD_UNDO,
14203         STD_REDOW,
14204         STD_DELETE,
14205         STD_FILENEW,
14206         STD_FILEOPEN,
14207         STD_FILESAVE,
14208         STD_PRINTPRE,
14209         STD_PROPERTIES,
14210         STD_HELP,
14211         STD_FIND,
14212         STD_REPLACE,
14213         STD_PRINT // = 14
14214 }
14215 
14216 alias HANDLE HIMAGELIST;
14217 	HIMAGELIST ImageList_Create(int, int, UINT, int, int);
14218 	int ImageList_Add(HIMAGELIST, HBITMAP, HBITMAP);
14219         BOOL ImageList_Destroy(HIMAGELIST);
14220 
14221 uint MAKELONG(ushort a, ushort b) {
14222         return cast(uint) ((b << 16) | a);
14223 }
14224 
14225 
14226 struct TBBUTTON {
14227 	int   iBitmap;
14228 	int   idCommand;
14229 	BYTE  fsState;
14230 	BYTE  fsStyle;
14231 	version(Win64)
14232 	BYTE[6] bReserved;
14233 	else
14234 	BYTE[2]  bReserved;
14235 	DWORD dwData;
14236 	INT_PTR   iString;
14237 }
14238 
14239 	enum {
14240 		TB_ADDBUTTONSA   = WM_USER + 20,
14241 		TB_INSERTBUTTONA = WM_USER + 21,
14242 		TB_GETIDEALSIZE = WM_USER + 99,
14243 	}
14244 
14245 struct SIZE {
14246 	LONG cx;
14247 	LONG cy;
14248 }
14249 
14250 
14251 enum {
14252 	TBSTATE_CHECKED       = 1,
14253 	TBSTATE_PRESSED       = 2,
14254 	TBSTATE_ENABLED       = 4,
14255 	TBSTATE_HIDDEN        = 8,
14256 	TBSTATE_INDETERMINATE = 16,
14257 	TBSTATE_WRAP          = 32
14258 }
14259 
14260 
14261 
14262 enum {
14263 	ILC_COLOR    = 0,
14264 	ILC_COLOR4   = 4,
14265 	ILC_COLOR8   = 8,
14266 	ILC_COLOR16  = 16,
14267 	ILC_COLOR24  = 24,
14268 	ILC_COLOR32  = 32,
14269 	ILC_COLORDDB = 254,
14270 	ILC_MASK     = 1,
14271 	ILC_PALETTE  = 2048
14272 }
14273 
14274 
14275 alias TBBUTTON*       PTBBUTTON, LPTBBUTTON;
14276 
14277 
14278 enum {
14279 	TB_ENABLEBUTTON          = WM_USER + 1,
14280 	TB_CHECKBUTTON,
14281 	TB_PRESSBUTTON,
14282 	TB_HIDEBUTTON,
14283 	TB_INDETERMINATE, //     = WM_USER + 5,
14284 	TB_ISBUTTONENABLED       = WM_USER + 9,
14285 	TB_ISBUTTONCHECKED,
14286 	TB_ISBUTTONPRESSED,
14287 	TB_ISBUTTONHIDDEN,
14288 	TB_ISBUTTONINDETERMINATE, // = WM_USER + 13,
14289 	TB_SETSTATE              = WM_USER + 17,
14290 	TB_GETSTATE              = WM_USER + 18,
14291 	TB_ADDBITMAP             = WM_USER + 19,
14292 	TB_DELETEBUTTON          = WM_USER + 22,
14293 	TB_GETBUTTON,
14294 	TB_BUTTONCOUNT,
14295 	TB_COMMANDTOINDEX,
14296 	TB_SAVERESTOREA,
14297 	TB_CUSTOMIZE,
14298 	TB_ADDSTRINGA,
14299 	TB_GETITEMRECT,
14300 	TB_BUTTONSTRUCTSIZE,
14301 	TB_SETBUTTONSIZE,
14302 	TB_SETBITMAPSIZE,
14303 	TB_AUTOSIZE, //          = WM_USER + 33,
14304 	TB_GETTOOLTIPS           = WM_USER + 35,
14305 	TB_SETTOOLTIPS           = WM_USER + 36,
14306 	TB_SETPARENT             = WM_USER + 37,
14307 	TB_SETROWS               = WM_USER + 39,
14308 	TB_GETROWS,
14309 	TB_GETBITMAPFLAGS,
14310 	TB_SETCMDID,
14311 	TB_CHANGEBITMAP,
14312 	TB_GETBITMAP,
14313 	TB_GETBUTTONTEXTA,
14314 	TB_REPLACEBITMAP, //     = WM_USER + 46,
14315 	TB_GETBUTTONSIZE         = WM_USER + 58,
14316 	TB_SETBUTTONWIDTH        = WM_USER + 59,
14317 	TB_GETBUTTONTEXTW        = WM_USER + 75,
14318 	TB_SAVERESTOREW          = WM_USER + 76,
14319 	TB_ADDSTRINGW            = WM_USER + 77,
14320 }
14321 
14322 extern(Windows)
14323 BOOL EnumChildWindows(HWND, WNDENUMPROC, LPARAM);
14324 
14325 alias extern(Windows) BOOL function (HWND, LPARAM) WNDENUMPROC;
14326 
14327 
14328 	enum {
14329 		TB_SETINDENT = WM_USER + 47,
14330 		TB_SETIMAGELIST,
14331 		TB_GETIMAGELIST,
14332 		TB_LOADIMAGES,
14333 		TB_GETRECT,
14334 		TB_SETHOTIMAGELIST,
14335 		TB_GETHOTIMAGELIST,
14336 		TB_SETDISABLEDIMAGELIST,
14337 		TB_GETDISABLEDIMAGELIST,
14338 		TB_SETSTYLE,
14339 		TB_GETSTYLE,
14340 		//TB_GETBUTTONSIZE,
14341 		//TB_SETBUTTONWIDTH,
14342 		TB_SETMAXTEXTROWS,
14343 		TB_GETTEXTROWS // = WM_USER + 61
14344 	}
14345 
14346 enum {
14347 	CCM_FIRST            = 0x2000,
14348 	CCM_LAST             = CCM_FIRST + 0x200,
14349 	CCM_SETBKCOLOR       = 8193,
14350 	CCM_SETCOLORSCHEME   = 8194,
14351 	CCM_GETCOLORSCHEME   = 8195,
14352 	CCM_GETDROPTARGET    = 8196,
14353 	CCM_SETUNICODEFORMAT = 8197,
14354 	CCM_GETUNICODEFORMAT = 8198,
14355 	CCM_SETVERSION       = 0x2007,
14356 	CCM_GETVERSION       = 0x2008,
14357 	CCM_SETNOTIFYWINDOW  = 0x2009
14358 }
14359 
14360 
14361 enum {
14362 	PBM_SETRANGE     = WM_USER + 1,
14363 	PBM_SETPOS,
14364 	PBM_DELTAPOS,
14365 	PBM_SETSTEP,
14366 	PBM_STEPIT,   // = WM_USER + 5
14367 	PBM_SETRANGE32   = 1030,
14368 	PBM_GETRANGE,
14369 	PBM_GETPOS,
14370 	PBM_SETBARCOLOR, // = 1033
14371 	PBM_SETBKCOLOR   = CCM_SETBKCOLOR
14372 }
14373 
14374 enum {
14375 	PBS_SMOOTH   = 1,
14376 	PBS_VERTICAL = 4
14377 }
14378 
14379 enum {
14380         ICC_LISTVIEW_CLASSES = 1,
14381         ICC_TREEVIEW_CLASSES = 2,
14382         ICC_BAR_CLASSES      = 4,
14383         ICC_TAB_CLASSES      = 8,
14384         ICC_UPDOWN_CLASS     = 16,
14385         ICC_PROGRESS_CLASS   = 32,
14386         ICC_HOTKEY_CLASS     = 64,
14387         ICC_ANIMATE_CLASS    = 128,
14388         ICC_WIN95_CLASSES    = 255,
14389         ICC_DATE_CLASSES     = 256,
14390         ICC_USEREX_CLASSES   = 512,
14391         ICC_COOL_CLASSES     = 1024,
14392 	ICC_STANDARD_CLASSES = 0x00004000,
14393 }
14394 
14395 	enum WM_USER = 1024;
14396 }
14397 
14398 version(win32_widgets)
14399 	pragma(lib, "comdlg32");
14400 
14401 
14402 ///
14403 enum GenericIcons : ushort {
14404 	None, ///
14405 	// these happen to match the win32 std icons numerically if you just subtract one from the value
14406 	Cut, ///
14407 	Copy, ///
14408 	Paste, ///
14409 	Undo, ///
14410 	Redo, ///
14411 	Delete, ///
14412 	New, ///
14413 	Open, ///
14414 	Save, ///
14415 	PrintPreview, ///
14416 	Properties, ///
14417 	Help, ///
14418 	Find, ///
14419 	Replace, ///
14420 	Print, ///
14421 }
14422 
14423 enum FileDialogType {
14424 	Automatic,
14425 	Open,
14426 	Save
14427 }
14428 string previousFileReferenced;
14429 
14430 /++
14431 	Used in automatic menu functions to indicate that the user should be able to browse for a file.
14432 
14433 	Params:
14434 		storage = an alias to a `static string` variable that stores the last file referenced. It will
14435 		use this to pre-fill the dialog with a suggestion.
14436 
14437 		Please note that it MUST be `static` or you will get compile errors.
14438 
14439 		filters = the filters param to [getFileName]
14440 
14441 		type = the type if dialog to show. If `FileDialogType.Automatic`, it the driver code will
14442 		guess based on the function name. If it has the word "Save" or "Export" in it, it will show
14443 		a save dialog box. Otherwise, it will show an open dialog box.
14444 +/
14445 struct FileName(alias storage = previousFileReferenced, string[] filters = null, FileDialogType type = FileDialogType.Automatic) {
14446 	string name;
14447 	alias name this;
14448 }
14449 
14450 /++
14451 	History:
14452 		onCancel was added November 6, 2021.
14453 
14454 		The dialog itself on Linux was modified on December 2, 2021 to include
14455 		a directory picker in addition to the command line completion view.
14456 
14457 		The `initialDirectory` argument was added November 9, 2022 (dub v10.10)
14458 	Future_directions:
14459 		I want to add some kind of custom preview and maybe thumbnail thing in the future,
14460 		at least on Linux, maybe on Windows too.
14461 +/
14462 void getOpenFileName(
14463 	void delegate(string) onOK,
14464 	string prefilledName = null,
14465 	string[] filters = null,
14466 	void delegate() onCancel = null,
14467 	string initialDirectory = null,
14468 )
14469 {
14470 	return getFileName(true, onOK, prefilledName, filters, onCancel, initialDirectory);
14471 }
14472 
14473 /// ditto
14474 void getSaveFileName(
14475 	void delegate(string) onOK,
14476 	string prefilledName = null,
14477 	string[] filters = null,
14478 	void delegate() onCancel = null,
14479 	string initialDirectory = null,
14480 )
14481 {
14482 	return getFileName(false, onOK, prefilledName, filters, onCancel, initialDirectory);
14483 }
14484 
14485 void getFileName(
14486 	bool openOrSave,
14487 	void delegate(string) onOK,
14488 	string prefilledName = null,
14489 	string[] filters = null, // format here is like ["Text files\0*.txt;*.text", "Image files\0*.png;*.jpg"]
14490 	void delegate() onCancel = null,
14491 	string initialDirectory = null,
14492 )
14493 {
14494 
14495 	version(win32_widgets) {
14496 		import core.sys.windows.commdlg;
14497 	/*
14498 	Ofn.lStructSize = sizeof(OPENFILENAME);
14499 	Ofn.hwndOwner = hWnd;
14500 	Ofn.lpstrFilter = szFilter;
14501 	Ofn.lpstrFile= szFile;
14502 	Ofn.nMaxFile = sizeof(szFile)/ sizeof(*szFile);
14503 	Ofn.lpstrFileTitle = szFileTitle;
14504 	Ofn.nMaxFileTitle = sizeof(szFileTitle);
14505 	Ofn.lpstrInitialDir = (LPSTR)NULL;
14506 	Ofn.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT;
14507 	Ofn.lpstrTitle = szTitle;
14508 	 */
14509 
14510 
14511 		wchar[1024] file = 0;
14512 		wchar[1024] filterBuffer = 0;
14513 		makeWindowsString(prefilledName, file[]);
14514 		OPENFILENAME ofn;
14515 		ofn.lStructSize = ofn.sizeof;
14516 		if(filters.length) {
14517 			string filter;
14518 			foreach(i, f; filters) {
14519 				filter ~= f;
14520 				filter ~= "\0";
14521 			}
14522 			filter ~= "\0";
14523 			ofn.lpstrFilter = makeWindowsString(filter, filterBuffer[], 0 /* already terminated */).ptr;
14524 		}
14525 		ofn.lpstrFile = file.ptr;
14526 		ofn.nMaxFile = file.length;
14527 
14528 		wchar[1024] initialDir = 0;
14529 		if(initialDirectory !is null) {
14530 			makeWindowsString(initialDirectory, initialDir[]);
14531 			ofn.lpstrInitialDir = file.ptr;
14532 		}
14533 
14534 		if(openOrSave ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn))
14535 		{
14536 			string okString = makeUtf8StringFromWindowsString(ofn.lpstrFile);
14537 			if(okString.length && okString[$-1] == '\0')
14538 				okString = okString[0..$-1];
14539 			onOK(okString);
14540 		} else {
14541 			if(onCancel)
14542 				onCancel();
14543 		}
14544 	} else version(custom_widgets) {
14545 		if(filters.length == 0)
14546 			filters = ["All Files\0*.*"];
14547 		auto picker = new FilePicker(prefilledName, filters, initialDirectory);
14548 		picker.onOK = onOK;
14549 		picker.onCancel = onCancel;
14550 		picker.show();
14551 	}
14552 }
14553 
14554 version(custom_widgets)
14555 private
14556 class FilePicker : Dialog {
14557 	void delegate(string) onOK;
14558 	void delegate() onCancel;
14559 	LineEdit lineEdit;
14560 
14561 	enum GetFilesResult {
14562 		success,
14563 		fileNotFound
14564 	}
14565 	static GetFilesResult getFiles(string cwd, scope void delegate(string name, bool isDirectory) dg) {
14566 		version(Windows) {
14567 			WIN32_FIND_DATA data;
14568 			WCharzBuffer search = WCharzBuffer(cwd ~ "/*");
14569 			auto handle = FindFirstFileW(search.ptr, &data);
14570 			scope(exit) if(handle !is INVALID_HANDLE_VALUE) FindClose(handle);
14571 			if(handle is INVALID_HANDLE_VALUE) {
14572 				if(GetLastError() == ERROR_FILE_NOT_FOUND)
14573 					return GetFilesResult.fileNotFound;
14574 				throw new WindowsApiException("FindFirstFileW", GetLastError());
14575 			}
14576 
14577 			try_more:
14578 
14579 			string name = makeUtf8StringFromWindowsString(data.cFileName[0 .. findIndexOfZero(data.cFileName[])]);
14580 
14581 			dg(name, (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? true : false);
14582 
14583 			auto ret = FindNextFileW(handle, &data);
14584 			if(ret == 0) {
14585 				if(GetLastError() == ERROR_NO_MORE_FILES)
14586 					return GetFilesResult.success;
14587 				throw new WindowsApiException("FindNextFileW", GetLastError());
14588 			}
14589 
14590 			goto try_more;
14591 
14592 		} else version(Posix) {
14593 			import core.sys.posix.dirent;
14594 			import core.stdc.errno;
14595 			auto dir = opendir((cwd ~ "\0").ptr);
14596 			scope(exit)
14597 				if(dir) closedir(dir);
14598 			if(dir is null)
14599 				throw new ErrnoApiException("opendir [" ~ cwd ~ "]", errno);
14600 
14601 			auto dirent = readdir(dir);
14602 			if(dirent is null)
14603 				return GetFilesResult.fileNotFound;
14604 
14605 			try_more:
14606 
14607 			string name = dirent.d_name[0 .. findIndexOfZero(dirent.d_name[])].idup;
14608 
14609 			dg(name, dirent.d_type == DT_DIR);
14610 
14611 			dirent = readdir(dir);
14612 			if(dirent is null)
14613 				return GetFilesResult.success;
14614 
14615 			goto try_more;
14616 		} else static assert(0);
14617 	}
14618 
14619 	// returns common prefix
14620 	string loadFiles(string cwd, string[] filters...) {
14621 		string[] files;
14622 		string[] dirs;
14623 
14624 		string commonPrefix;
14625 
14626 		getFiles(cwd, (string name, bool isDirectory) {
14627 			if(name == ".")
14628 				return; // skip this as unnecessary
14629 			if(isDirectory)
14630 				dirs ~= name;
14631 			else {
14632 				foreach(filter; filters)
14633 				if(
14634 					filter.length <= 1 ||
14635 					filter == "*.*" ||
14636 					(filter[0] == '*' && name.endsWith(filter[1 .. $])) ||
14637 					(filter[$-1] == '*' && name.startsWith(filter[0 .. $ - 1]))
14638 				)
14639 				{
14640 					files ~= name;
14641 
14642 					if(filter.length > 0 && filter[$-1] == '*') {
14643 						if(commonPrefix is null) {
14644 							commonPrefix = name;
14645 						} else {
14646 							foreach(idx, char i; name) {
14647 								if(idx >= commonPrefix.length || i != commonPrefix[idx]) {
14648 									commonPrefix = commonPrefix[0 .. idx];
14649 									break;
14650 								}
14651 							}
14652 						}
14653 					}
14654 
14655 					break;
14656 				}
14657 			}
14658 		});
14659 
14660 		extern(C) static int comparator(scope const void* a, scope const void* b) {
14661 			auto sa = *cast(string*) a;
14662 			auto sb = *cast(string*) b;
14663 
14664 			for(int i = 0; i < sa.length; i++) {
14665 				if(i == sb.length)
14666 					return 1;
14667 				return sa[i] - sb[i];
14668 			}
14669 
14670 			return 0;
14671 		}
14672 
14673 		nonPhobosSort(files, &comparator);
14674 		nonPhobosSort(dirs, &comparator);
14675 
14676 		listWidget.clear();
14677 		dirWidget.clear();
14678 		foreach(name; dirs)
14679 			dirWidget.addOption(name);
14680 		foreach(name; files)
14681 			listWidget.addOption(name);
14682 
14683 		return commonPrefix;
14684 	}
14685 
14686 	ListWidget listWidget;
14687 	ListWidget dirWidget;
14688 
14689 	string currentDirectory;
14690 	string[] processedFilters;
14691 
14692 	//string[] filters = null, // format here is like ["Text files\0*.txt;*.text", "Image files\n*.png;*.jpg"]
14693 	this(string prefilledName, string[] filters, string initialDirectory, Window owner = null) {
14694 		super(300, 200, "Choose File..."); // owner);
14695 
14696 		foreach(filter; filters) {
14697 			while(filter.length && filter[0] != 0) {
14698 				filter = filter[1 .. $];
14699 			}
14700 			if(filter.length)
14701 				filter = filter[1 .. $]; // trim off the 0
14702 
14703 			while(filter.length) {
14704 				int idx = 0;
14705 				while(idx < filter.length && filter[idx] != ';') {
14706 					idx++;
14707 				}
14708 
14709 				processedFilters ~= filter[0 .. idx];
14710 				if(idx < filter.length)
14711 					idx++; // skip the ;
14712 				filter = filter[idx .. $];
14713 			}
14714 		}
14715 
14716 		currentDirectory = initialDirectory is null ? "." : initialDirectory;
14717 
14718 		{
14719 			auto hl = new HorizontalLayout(this);
14720 			dirWidget = new ListWidget(hl);
14721 			listWidget = new ListWidget(hl);
14722 
14723 			// double click events normally trigger something else but
14724 			// here user might be clicking kinda fast and we'd rather just
14725 			// keep it
14726 			dirWidget.addEventListener((scope DoubleClickEvent dev) {
14727 				auto ce = new ChangeEvent!void(dirWidget, () {});
14728 				ce.dispatch();
14729 			});
14730 
14731 			dirWidget.addEventListener((scope ChangeEvent!void sce) {
14732 				string v;
14733 				foreach(o; dirWidget.options)
14734 					if(o.selected) {
14735 						v = o.label;
14736 						break;
14737 					}
14738 				if(v.length) {
14739 					currentDirectory ~= "/" ~ v;
14740 					loadFiles(currentDirectory, processedFilters);
14741 				}
14742 			});
14743 
14744 			// double click here, on the other hand, selects the file
14745 			// and moves on
14746 			listWidget.addEventListener((scope DoubleClickEvent dev) {
14747 				OK();
14748 			});
14749 		}
14750 
14751 		lineEdit = new LineEdit(this);
14752 		lineEdit.focus();
14753 		lineEdit.addEventListener(delegate(CharEvent event) {
14754 			if(event.character == '\t' || event.character == '\n')
14755 				event.preventDefault();
14756 		});
14757 
14758 		listWidget.addEventListener(EventType.change, () {
14759 			foreach(o; listWidget.options)
14760 				if(o.selected)
14761 					lineEdit.content = o.label;
14762 		});
14763 
14764 		loadFiles(currentDirectory, processedFilters);
14765 
14766 		lineEdit.addEventListener((KeyDownEvent event) {
14767 			if(event.key == Key.Tab) {
14768 
14769 				auto current = lineEdit.content;
14770 				if(current.length >= 2 && current[0 ..2] == "./")
14771 					current = current[2 .. $];
14772 
14773 				auto commonPrefix = loadFiles(".", current ~ "*");
14774 
14775 				if(commonPrefix.length)
14776 					lineEdit.content = commonPrefix;
14777 
14778 				// FIXME: if that is a directory, add the slash? or even go inside?
14779 
14780 				event.preventDefault();
14781 			}
14782 		});
14783 
14784 		lineEdit.content = prefilledName;
14785 
14786 		auto hl = new HorizontalLayout(60, this);
14787 		auto cancelButton = new Button("Cancel", hl);
14788 		auto okButton = new Button("OK", hl);
14789 
14790 		cancelButton.addEventListener(EventType.triggered, &Cancel);
14791 		okButton.addEventListener(EventType.triggered, &OK);
14792 
14793 		this.addEventListener((KeyDownEvent event) {
14794 			if(event.key == Key.Enter || event.key == Key.PadEnter) {
14795 				event.preventDefault();
14796 				OK();
14797 			}
14798 			if(event.key == Key.Escape)
14799 				Cancel();
14800 		});
14801 
14802 	}
14803 
14804 	override void OK() {
14805 		if(lineEdit.content.length) {
14806 			string accepted;
14807 			auto c = lineEdit.content;
14808 			if(c.length && c[0] == '/')
14809 				accepted = c;
14810 			else
14811 				accepted = currentDirectory ~ "/" ~ lineEdit.content;
14812 
14813 			if(isDir(accepted)) {
14814 				// FIXME: would be kinda nice to support ~ and collapse these paths too
14815 				// FIXME: would also be nice to actually show the "Looking in..." directory and maybe the filters but later.
14816 				currentDirectory = accepted;
14817 				loadFiles(currentDirectory, processedFilters);
14818 				lineEdit.content = "";
14819 				return;
14820 			}
14821 
14822 			if(onOK)
14823 				onOK(accepted);
14824 		}
14825 		close();
14826 	}
14827 
14828 	override void Cancel() {
14829 		if(onCancel)
14830 			onCancel();
14831 		close();
14832 	}
14833 }
14834 
14835 private bool isDir(string name) {
14836 	version(Windows) {
14837 		auto ws = WCharzBuffer(name);
14838 		auto ret = GetFileAttributesW(ws.ptr);
14839 		if(ret == INVALID_FILE_ATTRIBUTES)
14840 			return false;
14841 		return (ret & FILE_ATTRIBUTE_DIRECTORY) != 0;
14842 	} else version(Posix) {
14843 		import core.sys.posix.sys.stat;
14844 		stat_t buf;
14845 		auto ret = stat((name ~ '\0').ptr, &buf);
14846 		if(ret == -1)
14847 			return false; // I could probably check more specific errors tbh
14848 		return (buf.st_mode & S_IFMT) == S_IFDIR;
14849 	} else return false;
14850 }
14851 
14852 /*
14853 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775947%28v=vs.85%29.aspx#check_boxes
14854 http://msdn.microsoft.com/en-us/library/windows/desktop/ms633574%28v=vs.85%29.aspx
14855 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775943%28v=vs.85%29.aspx
14856 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775951%28v=vs.85%29.aspx
14857 http://msdn.microsoft.com/en-us/library/windows/desktop/ms632680%28v=vs.85%29.aspx
14858 http://msdn.microsoft.com/en-us/library/windows/desktop/ms644996%28v=vs.85%29.aspx#message_box
14859 http://www.sbin.org/doc/Xlib/chapt_03.html
14860 
14861 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760433%28v=vs.85%29.aspx
14862 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760446%28v=vs.85%29.aspx
14863 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760443%28v=vs.85%29.aspx
14864 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760476%28v=vs.85%29.aspx
14865 */
14866 
14867 
14868 // These are all for setMenuAndToolbarFromAnnotatedCode
14869 /// This item in the menu will be preceded by a separator line
14870 /// Group: generating_from_code
14871 struct separator {}
14872 deprecated("It was misspelled, use separator instead") alias seperator = separator;
14873 /// Program-wide keyboard shortcut to trigger the action
14874 /// Group: generating_from_code
14875 struct accelerator { string keyString; }
14876 /// tells which menu the action will be on
14877 /// Group: generating_from_code
14878 struct menu { string name; }
14879 /// Describes which toolbar section the action appears on
14880 /// Group: generating_from_code
14881 struct toolbar { string groupName; }
14882 ///
14883 /// Group: generating_from_code
14884 struct icon { ushort id; }
14885 ///
14886 /// Group: generating_from_code
14887 struct label { string label; }
14888 ///
14889 /// Group: generating_from_code
14890 struct hotkey { dchar ch; }
14891 ///
14892 /// Group: generating_from_code
14893 struct tip { string tip; }
14894 
14895 
14896 /++
14897 	Observes and allows inspection of an object via automatic gui
14898 +/
14899 /// Group: generating_from_code
14900 ObjectInspectionWindow objectInspectionWindow(T)(T t) if(is(T == class)) {
14901 	return new ObjectInspectionWindowImpl!(T)(t);
14902 }
14903 
14904 class ObjectInspectionWindow : Window {
14905 	this(int a, int b, string c) {
14906 		super(a, b, c);
14907 	}
14908 
14909 	abstract void readUpdatesFromObject();
14910 }
14911 
14912 class ObjectInspectionWindowImpl(T) : ObjectInspectionWindow {
14913 	T t;
14914 	this(T t) {
14915 		this.t = t;
14916 
14917 		super(300, 400, "ObjectInspectionWindow - " ~ T.stringof);
14918 
14919 		foreach(memberName; __traits(derivedMembers, T)) {{
14920 			alias member = I!(__traits(getMember, t, memberName))[0];
14921 			alias type = typeof(member);
14922 			static if(is(type == int)) {
14923 				auto le = new LabeledLineEdit(memberName ~ ": ", this);
14924 				//le.addEventListener("char", (Event ev) {
14925 					//if((ev.character < '0' || ev.character > '9') && ev.character != '-')
14926 						//ev.preventDefault();
14927 				//});
14928 				le.addEventListener(EventType.change, (Event ev) {
14929 					__traits(getMember, t, memberName) = cast(type) stringToLong(ev.stringValue);
14930 				});
14931 
14932 				updateMemberDelegates[memberName] = () {
14933 					le.content = toInternal!string(__traits(getMember, t, memberName));
14934 				};
14935 			}
14936 		}}
14937 	}
14938 
14939 	void delegate()[string] updateMemberDelegates;
14940 
14941 	override void readUpdatesFromObject() {
14942 		foreach(k, v; updateMemberDelegates)
14943 			v();
14944 	}
14945 }
14946 
14947 /++
14948 	Creates a dialog based on a data structure.
14949 
14950 	---
14951 	dialog((YourStructure value) {
14952 		// the user filled in the struct and clicked OK,
14953 		// you can check the members now
14954 	});
14955 	---
14956 
14957 	Params:
14958 		initialData = the initial value to show in the dialog. It will not modify this unless
14959 		it is a class then it might, no promises.
14960 
14961 	History:
14962 		The overload that lets you specify `initialData` was added on December 30, 2021 (dub v10.5)
14963 +/
14964 /// Group: generating_from_code
14965 void dialog(T)(void delegate(T) onOK, void delegate() onCancel = null, string title = T.stringof) {
14966 	dialog(T.init, onOK, onCancel, title);
14967 }
14968 /// ditto
14969 void dialog(T)(T initialData, void delegate(T) onOK, void delegate() onCancel = null, string title = T.stringof) {
14970 	auto dg = new AutomaticDialog!T(initialData, onOK, onCancel, title);
14971 	dg.show();
14972 }
14973 
14974 private static template I(T...) { alias I = T; }
14975 
14976 
14977 private string beautify(string name, char space = ' ', bool allLowerCase = false) {
14978 	if(name == "id")
14979 		return allLowerCase ? name : "ID";
14980 
14981 	char[160] buffer;
14982 	int bufferIndex = 0;
14983 	bool shouldCap = true;
14984 	bool shouldSpace;
14985 	bool lastWasCap;
14986 	foreach(idx, char ch; name) {
14987 		if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
14988 
14989 		if((ch >= 'A' && ch <= 'Z') || ch == '_') {
14990 			if(lastWasCap) {
14991 				// two caps in a row, don't change. Prolly acronym.
14992 			} else {
14993 				if(idx)
14994 					shouldSpace = true; // new word, add space
14995 			}
14996 
14997 			lastWasCap = true;
14998 		} else {
14999 			lastWasCap = false;
15000 		}
15001 
15002 		if(shouldSpace) {
15003 			buffer[bufferIndex++] = space;
15004 			if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
15005 			shouldSpace = false;
15006 		}
15007 		if(shouldCap) {
15008 			if(ch >= 'a' && ch <= 'z')
15009 				ch -= 32;
15010 			shouldCap = false;
15011 		}
15012 		if(allLowerCase && ch >= 'A' && ch <= 'Z')
15013 			ch += 32;
15014 		buffer[bufferIndex++] = ch;
15015 	}
15016 	return buffer[0 .. bufferIndex].idup;
15017 }
15018 
15019 /++
15020 	This is the implementation for [dialog]. None of its details are guaranteed stable and may change at any time; the stable interface is just the [dialog] function at this time.
15021 +/
15022 class AutomaticDialog(T) : Dialog {
15023 	T t;
15024 
15025 	void delegate(T) onOK;
15026 	void delegate() onCancel;
15027 
15028 	override int paddingTop() { return defaultLineHeight; }
15029 	override int paddingBottom() { return defaultLineHeight; }
15030 	override int paddingRight() { return defaultLineHeight; }
15031 	override int paddingLeft() { return defaultLineHeight; }
15032 
15033 	this(T initialData, void delegate(T) onOK, void delegate() onCancel, string title) {
15034 		assert(onOK !is null);
15035 
15036 		t = initialData;
15037 
15038 		static if(is(T == class)) {
15039 			if(t is null)
15040 				t = new T();
15041 		}
15042 		this.onOK = onOK;
15043 		this.onCancel = onCancel;
15044 		super(400, cast(int)(__traits(allMembers, T).length * 2) * (defaultLineHeight + 4 + 2) + Window.lineHeight + 56, title);
15045 
15046 		static if(is(T == class))
15047 			this.addDataControllerWidget(t);
15048 		else
15049 			this.addDataControllerWidget(&t);
15050 
15051 		auto hl = new HorizontalLayout(this);
15052 		auto stretch = new HorizontalSpacer(hl); // to right align
15053 		auto ok = new CommandButton("OK", hl);
15054 		auto cancel = new CommandButton("Cancel", hl);
15055 		ok.addEventListener(EventType.triggered, &OK);
15056 		cancel.addEventListener(EventType.triggered, &Cancel);
15057 
15058 		this.addEventListener((KeyDownEvent ev) {
15059 			if(ev.key == Key.Enter || ev.key == Key.PadEnter) {
15060 				ok.focus();
15061 				OK();
15062 				ev.preventDefault();
15063 			}
15064 			if(ev.key == Key.Escape) {
15065 				Cancel();
15066 				ev.preventDefault();
15067 			}
15068 		});
15069 
15070 		this.addEventListener((scope ClosedEvent ce) {
15071 			if(onCancel)
15072 				onCancel();
15073 		});
15074 
15075 		//this.children[0].focus();
15076 	}
15077 
15078 	override void OK() {
15079 		onOK(t);
15080 		close();
15081 	}
15082 
15083 	override void Cancel() {
15084 		if(onCancel)
15085 			onCancel();
15086 		close();
15087 	}
15088 }
15089 
15090 private template baseClassCount(Class) {
15091 	private int helper() {
15092 		int count = 0;
15093 		static if(is(Class bases == super)) {
15094 			foreach(base; bases)
15095 				static if(is(base == class))
15096 					count += 1 + baseClassCount!base;
15097 		}
15098 		return count;
15099 	}
15100 
15101 	enum int baseClassCount = helper();
15102 }
15103 
15104 private long stringToLong(string s) {
15105 	long ret;
15106 	if(s.length == 0)
15107 		return ret;
15108 	bool negative = s[0] == '-';
15109 	if(negative)
15110 		s = s[1 .. $];
15111 	foreach(ch; s) {
15112 		if(ch >= '0' && ch <= '9') {
15113 			ret *= 10;
15114 			ret += ch - '0';
15115 		}
15116 	}
15117 	if(negative)
15118 		ret = -ret;
15119 	return ret;
15120 }
15121 
15122 
15123 interface ReflectableProperties {
15124 	/++
15125 		Iterates the event's properties as strings. Note that keys may be repeated and a get property request may
15126 		call your sink with `null`. It it does, it means the key either doesn't request or cannot be represented by
15127 		json in the current implementation.
15128 
15129 		This is auto-implemented for you if you mixin [RegisterGetters] in your child classes and only have
15130 		properties of type `bool`, `int`, `double`, or `string`. For other ones, you will need to do it yourself
15131 		as of the June 2, 2021 release.
15132 
15133 		History:
15134 			Added June 2, 2021.
15135 
15136 		See_Also: [getPropertyAsString], [setPropertyFromString]
15137 	+/
15138 	void getPropertiesList(scope void delegate(string name) sink) const;// @nogc pure nothrow;
15139 	/++
15140 		Requests a property to be delivered to you as a string, through your `sink` delegate.
15141 
15142 		If the `value` is null, it means the property could not be retreived. If `valueIsJson`, it should
15143 		be interpreted as json, otherwise, it is just a plain string.
15144 
15145 		The sink should always be called exactly once for each call (it is basically a return value, but it might
15146 		use a local buffer it maintains instead of allocating a return value).
15147 
15148 		History:
15149 			Added June 2, 2021.
15150 
15151 		See_Also: [getPropertiesList], [setPropertyFromString]
15152 	+/
15153 	void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink);
15154 	/++
15155 		Sets the given property, if it exists, to the given value, if possible. If `strIsJson` is true, it will json decode (if the implementation wants to) then apply the value, otherwise it will treat it as a plain string.
15156 
15157 		History:
15158 			Added June 2, 2021.
15159 
15160 		See_Also: [getPropertiesList], [getPropertyAsString], [SetPropertyResult]
15161 	+/
15162 	SetPropertyResult setPropertyFromString(string name, scope const(char)[] str, bool strIsJson);
15163 
15164 	/// [setPropertyFromString] possible return values
15165 	enum SetPropertyResult {
15166 		success = 0, /// the property has been successfully set to the request value
15167 		notPermitted = -1, /// the property exists but it cannot be changed at this time
15168 		notImplemented = -2, /// the set function is not implemented for the given property (which may or may not exist)
15169 		noSuchProperty = -3, /// there is no property by that name
15170 		wrongFormat = -4, /// the string was given in the wrong format, e.g. passing "two" for an int value
15171 		invalidValue = -5, /// the string is in the correct format, but the specific given value could not be used (for example, because it was out of bounds)
15172 	}
15173 
15174 	/++
15175 		You can mix this in to get an implementation in child classes. This does [setPropertyFromString].
15176 
15177 		Your original base class, however, must implement its own methods. I recommend doing the initial ones by hand.
15178 
15179 		For [Widget] and [Event], the library provides [Widget.Register] and [Event.Register] that call these for you, so you should
15180 		rarely need to use these building blocks directly.
15181 	+/
15182 	mixin template RegisterSetters() {
15183 		override SetPropertyResult setPropertyFromString(string name, scope const(char)[] value, bool valueIsJson) {
15184 			switch(name) {
15185 				foreach(memberName; __traits(derivedMembers, typeof(this))) {
15186 					case memberName:
15187 						static if(is(typeof(__traits(getMember, this, memberName)) : const bool)) {
15188 							if(value != "true" && value != "false")
15189 								return SetPropertyResult.wrongFormat;
15190 							__traits(getMember, this, memberName) = value == "true" ? true : false;
15191 							return SetPropertyResult.success;
15192 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const long)) {
15193 							import core.stdc.stdlib;
15194 							char[128] zero = 0;
15195 							if(buffer.length + 1 >= zero.length)
15196 								return SetPropertyResult.wrongFormat;
15197 							zero[0 .. buffer.length] = buffer[];
15198 							__traits(getMember, this, memberName) = strtol(buffer.ptr, null, 10);
15199 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const double)) {
15200 							import core.stdc.stdlib;
15201 							char[128] zero = 0;
15202 							if(buffer.length + 1 >= zero.length)
15203 								return SetPropertyResult.wrongFormat;
15204 							zero[0 .. buffer.length] = buffer[];
15205 							__traits(getMember, this, memberName) = strtod(buffer.ptr, null, 10);
15206 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const string)) {
15207 							__traits(getMember, this, memberName) = value.idup;
15208 						} else {
15209 							return SetPropertyResult.notImplemented;
15210 						}
15211 
15212 				}
15213 				default:
15214 					return super.setPropertyFromString(name, value, valueIsJson);
15215 			}
15216 		}
15217 	}
15218 
15219 	/++
15220 		You can mix this in to get an implementation in child classes. This does [getPropertyAsString] and [getPropertiesList].
15221 
15222 		Your original base class, however, must implement its own methods. I recommend doing the initial ones by hand.
15223 
15224 		For [Widget] and [Event], the library provides [Widget.Register] and [Event.Register] that call these for you, so you should
15225 		rarely need to use these building blocks directly.
15226 	+/
15227 	mixin template RegisterGetters() {
15228 		override void getPropertiesList(scope void delegate(string name) sink) const {
15229 			super.getPropertiesList(sink);
15230 
15231 			foreach(memberName; __traits(derivedMembers, typeof(this))) {
15232 				sink(memberName);
15233 			}
15234 		}
15235 		override void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink) {
15236 			switch(name) {
15237 				foreach(memberName; __traits(derivedMembers, typeof(this))) {
15238 					case memberName:
15239 						static if(is(typeof(__traits(getMember, this, memberName)) : const bool)) {
15240 							sink(name, __traits(getMember, this, memberName) ? "true" : "false", true);
15241 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const long)) {
15242 							import core.stdc.stdio;
15243 							char[32] buffer;
15244 							auto len = snprintf(buffer.ptr, buffer.length, "%lld", cast(long) __traits(getMember, this, memberName));
15245 							sink(name, buffer[0 .. len], true);
15246 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const double)) {
15247 							import core.stdc.stdio;
15248 							char[32] buffer;
15249 							auto len = snprintf(buffer.ptr, buffer.length, "%f", cast(double) __traits(getMember, this, memberName));
15250 							sink(name, buffer[0 .. len], true);
15251 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const string)) {
15252 							sink(name, __traits(getMember, this, memberName), false);
15253 							//sinkJsonString(memberName, __traits(getMember, this, memberName), sink);
15254 						} else {
15255 							sink(name, null, true);
15256 						}
15257 
15258 					return;
15259 				}
15260 				default:
15261 					return super.getPropertyAsString(name, sink);
15262 			}
15263 		}
15264 	}
15265 }
15266 
15267 private struct Stack(T) {
15268 	this(int maxSize) {
15269 		internalLength = 0;
15270 		arr = initialBuffer[];
15271 	}
15272 
15273 	///.
15274 	void push(T t) {
15275 		if(internalLength >= arr.length) {
15276 			auto oldarr = arr;
15277 			if(arr.length < 4096)
15278 				arr = new T[arr.length * 2];
15279 			else
15280 				arr = new T[arr.length + 4096];
15281 			arr[0 .. oldarr.length] = oldarr[];
15282 		}
15283 
15284 		arr[internalLength] = t;
15285 		internalLength++;
15286 	}
15287 
15288 	///.
15289 	T pop() {
15290 		assert(internalLength);
15291 		internalLength--;
15292 		return arr[internalLength];
15293 	}
15294 
15295 	///.
15296 	T peek() {
15297 		assert(internalLength);
15298 		return arr[internalLength - 1];
15299 	}
15300 
15301 	///.
15302 	@property bool empty() {
15303 		return internalLength ? false : true;
15304 	}
15305 
15306 	///.
15307 	private T[] arr;
15308 	private size_t internalLength;
15309 	private T[64] initialBuffer;
15310 	// the static array is allocated with this object, so if we have a small stack (which we prolly do; dom trees usually aren't insanely deep),
15311 	// using this saves us a bunch of trips to the GC. In my last profiling, I got about a 50x improvement in the push()
15312 	// function thanks to this, and push() was actually one of the slowest individual functions in the code!
15313 }
15314 
15315 /// This is the lazy range that walks the tree for you. It tries to go in the lexical order of the source: node, then children from first to last, each recursively.
15316 private struct WidgetStream {
15317 
15318 	///.
15319 	@property Widget front() {
15320 		return current.widget;
15321 	}
15322 
15323 	/// Use Widget.tree instead.
15324 	this(Widget start) {
15325 		current.widget = start;
15326 		current.childPosition = -1;
15327 		isEmpty = false;
15328 		stack = typeof(stack)(0);
15329 	}
15330 
15331 	/*
15332 		Handle it
15333 		handle its children
15334 
15335 	*/
15336 
15337 	///.
15338 	void popFront() {
15339 	    more:
15340 	    	if(isEmpty) return;
15341 
15342 		// FIXME: the profiler says this function is somewhat slow (noticeable because it can be called a lot of times)
15343 
15344 		current.childPosition++;
15345 		if(current.childPosition >= current.widget.children.length) {
15346 			if(stack.empty())
15347 				isEmpty = true;
15348 			else {
15349 				current = stack.pop();
15350 				goto more;
15351 			}
15352 		} else {
15353 			stack.push(current);
15354 			current.widget = current.widget.children[current.childPosition];
15355 			current.childPosition = -1;
15356 		}
15357 	}
15358 
15359 	///.
15360 	@property bool empty() {
15361 		return isEmpty;
15362 	}
15363 
15364 	private:
15365 
15366 	struct Current {
15367 		Widget widget;
15368 		int childPosition;
15369 	}
15370 
15371 	Current current;
15372 
15373 	Stack!(Current) stack;
15374 
15375 	bool isEmpty;
15376 }
15377 
15378 
15379 /+
15380 
15381 	I could fix up the hierarchy kinda like this
15382 
15383 	class Widget {
15384 		Widget[] children() { return null; }
15385 	}
15386 	interface WidgetContainer {
15387 		Widget asWidget();
15388 		void addChild(Widget w);
15389 
15390 		// alias asWidget this; // but meh
15391 	}
15392 
15393 	Widget can keep a (Widget parent) ctor, but it should prolly deprecate and tell people to instead change their ctors to take WidgetContainer instead.
15394 
15395 	class Layout : Widget, WidgetContainer {}
15396 
15397 	class Window : WidgetContainer {}
15398 
15399 
15400 	All constructors that previously took Widgets should now take WidgetContainers instead
15401 
15402 
15403 
15404 	But I'm kinda meh toward it, im not sure this is a real problem even though there are some addChild things that throw "plz don't".
15405 +/
15406 
15407 /+
15408 	LAYOUTS 2.0
15409 
15410 	can just be assigned as a function. assigning a new one will cause it to be immediately called.
15411 
15412 	they simply are responsible for the recomputeChildLayout. If this pointer is null, it uses the default virtual one.
15413 
15414 	recomputeChildLayout only really needs a property accessor proxy... just the layout info too.
15415 
15416 	and even Paint can just use computedStyle...
15417 
15418 		background color
15419 		font
15420 		border color and style
15421 
15422 	And actually the style proxy can offer some helper routines to draw these like the draw 3d box
15423 		please note that many widgets and in some modes will completely ignore properties as they will.
15424 		they are just hints you set, not promises.
15425 
15426 
15427 
15428 
15429 
15430 	So generally the existing virtual functions are just the default for the class. But individual objects
15431 	or stylesheets can override this. The virtual ones count as tag-level specificity in css.
15432 +/
15433 
15434 /++
15435 	Structure to represent a collection of background hints. New features can be added here, so make sure you use the provided constructors and factories for maximum compatibility.
15436 
15437 	History:
15438 		Added May 24, 2021.
15439 +/
15440 struct WidgetBackground {
15441 	/++
15442 		A background with the given solid color.
15443 	+/
15444 	this(Color color) {
15445 		this.color = color;
15446 	}
15447 
15448 	this(WidgetBackground bg) {
15449 		this = bg;
15450 	}
15451 
15452 	/++
15453 		Creates a widget from the string.
15454 
15455 		Currently, it only supports solid colors via [Color.fromString], but it will likely be expanded in the future to something more like css.
15456 	+/
15457 	static WidgetBackground fromString(string s) {
15458 		return WidgetBackground(Color.fromString(s));
15459 	}
15460 
15461 	/++
15462 		The background is not necessarily a solid color, but you can always specify a color as a fallback.
15463 
15464 		History:
15465 			Made `public` on December 18, 2022 (dub v10.10).
15466 	+/
15467 	Color color;
15468 }
15469 
15470 /++
15471 	Interface to a custom visual theme which is able to access and use style hint properties, draw stylistic elements, and even completely override existing class' paint methods (though I'd note that can be a lot harder than it may seem due to the various little details of state you need to reflect visually, so that should be your last result!)
15472 
15473 	Please note that this is only guaranteed to be used by custom widgets, and custom widgets are generally inferior to system widgets. Layout properties may be used by sytstem widgets though.
15474 
15475 	You should not inherit from this directly, but instead use [VisualTheme].
15476 
15477 	History:
15478 		Added May 8, 2021
15479 +/
15480 abstract class BaseVisualTheme {
15481 	/// Don't implement this, instead use [VisualTheme] and implement `paint` methods on specific subclasses you want to override.
15482 	abstract void doPaint(Widget widget, WidgetPainter painter);
15483 
15484 	/+
15485 	/// Don't implement this, instead use [VisualTheme] and implement `StyleOverride` aliases on specific subclasses you want to override.
15486 	abstract void useStyleProperties(Widget w, scope void delegate(scope Widget.Style props) dg);
15487 	+/
15488 
15489 	/++
15490 		Returns the property as a string, or null if it was not overridden in the style definition. The idea here is something like css,
15491 		where the interpretation of the string varies for each property and may include things like measurement units.
15492 	+/
15493 	abstract string getPropertyString(Widget widget, string propertyName);
15494 
15495 	/++
15496 		Default background color of the window. Widgets also use this to simulate transparency.
15497 
15498 		Probably some shade of grey.
15499 	+/
15500 	abstract Color windowBackgroundColor();
15501 	abstract Color widgetBackgroundColor();
15502 	abstract Color foregroundColor();
15503 	abstract Color lightAccentColor();
15504 	abstract Color darkAccentColor();
15505 
15506 	/++
15507 		Colors used to indicate active selections in lists and text boxes, etc.
15508 	+/
15509 	abstract Color selectionForegroundColor();
15510 	/// ditto
15511 	abstract Color selectionBackgroundColor();
15512 
15513 	deprecated("Use selectionForegroundColor and selectionBackgroundColor instead") Color selectionColor() { return selectionBackgroundColor(); }
15514 
15515 	abstract OperatingSystemFont defaultFont();
15516 
15517 	private OperatingSystemFont defaultFontCache_;
15518 	private bool defaultFontCachePopulated;
15519 	private OperatingSystemFont defaultFontCached() {
15520 		if(!defaultFontCachePopulated) {
15521 			// FIXME: set this to false if X disconnect or if visual theme changes
15522 			defaultFontCache_ = defaultFont();
15523 			defaultFontCachePopulated = true;
15524 		}
15525 		return defaultFontCache_;
15526 	}
15527 }
15528 
15529 /+
15530 	A widget should have:
15531 		classList
15532 		dataset
15533 		attributes
15534 		computedStyles
15535 		state (persistent)
15536 		dynamic state (focused, hover, etc)
15537 +/
15538 
15539 // visualTheme.computedStyle(this).paddingLeft
15540 
15541 
15542 /++
15543 	This is your entry point to create your own visual theme for custom widgets.
15544 +/
15545 abstract class VisualTheme(CRTP) : BaseVisualTheme {
15546 	override string getPropertyString(Widget widget, string propertyName) {
15547 		return null;
15548 	}
15549 
15550 	/+
15551 		mixin StyleOverride!Widget
15552 	final override void useStyleProperties(Widget w, scope void delegate(scope Widget.Style props) dg) {
15553 		w.useStyleProperties(dg);
15554 	}
15555 	+/
15556 
15557 	final override void doPaint(Widget widget, WidgetPainter painter) {
15558 		auto derived = cast(CRTP) cast(void*) this;
15559 
15560 		scope void delegate(Widget, WidgetPainter) bestMatch;
15561 		int bestMatchScore;
15562 
15563 		static if(__traits(hasMember, CRTP, "paint"))
15564 		foreach(overload; __traits(getOverloads, CRTP, "paint")) {
15565 			static if(is(typeof(overload) Params == __parameters)) {
15566 				static assert(Params.length == 2);
15567 				static assert(is(Params[0] : Widget));
15568 				static assert(is(Params[1] == WidgetPainter));
15569 				static assert(is(typeof(&__traits(child, derived, overload)) == delegate), "Found a paint method that doesn't appear to be a delegate. One cause of this can be your dmd being too old, make sure it is version 2.094 or newer to use this feature."); // , __traits(getLocation, overload).stringof ~ " is not a delegate " ~ typeof(&__traits(child, derived, overload)).stringof);
15570 
15571 				alias type = Params[0];
15572 				if(cast(type) widget) {
15573 					auto score = baseClassCount!type;
15574 
15575 					if(score > bestMatchScore) {
15576 						bestMatch = cast(typeof(bestMatch)) &__traits(child, derived, overload);
15577 						bestMatchScore = score;
15578 					}
15579 				}
15580 			} else static assert(0, "paint should be a method.");
15581 		}
15582 
15583 		if(bestMatch)
15584 			bestMatch(widget, painter);
15585 		else
15586 			widget.paint(painter);
15587 	}
15588 
15589 	// I have to put these here even though I kinda don't want to since dmd regressed on detecting unimplemented interface functions through abstract classes
15590 	override Color windowBackgroundColor() { return Color(212, 212, 212); }
15591 	override Color widgetBackgroundColor() { return Color.white; }
15592 	override Color foregroundColor() { return Color.black; }
15593 	override Color darkAccentColor() { return Color(172, 172, 172); }
15594 	override Color lightAccentColor() { return Color(223, 223, 223); }
15595 	override Color selectionForegroundColor() { return Color.white; }
15596 	override Color selectionBackgroundColor() { return Color(0, 0, 128); }
15597 	override OperatingSystemFont defaultFont() { return null; } // will just use the default out of simpledisplay's xfontstr
15598 
15599 	private static struct Cached {
15600 		// i prolly want to do this
15601 	}
15602 }
15603 
15604 final class DefaultVisualTheme : VisualTheme!DefaultVisualTheme {
15605 	/+
15606 	OperatingSystemFont defaultFont() { return new OperatingSystemFont("Times New Roman", 8, FontWeight.medium); }
15607 	Color windowBackgroundColor() { return Color(242, 242, 242); }
15608 	Color darkAccentColor() { return windowBackgroundColor; }
15609 	Color lightAccentColor() { return windowBackgroundColor; }
15610 	+/
15611 }
15612 
15613 /++
15614 	Event fired when an [Observeable] variable changes. You will want to add an event listener referencing
15615 	the field like `widget.addEventListener((scope StateChanged!(Whatever.field) ev) { });`
15616 
15617 	History:
15618 		Moved from minigui_addons.webview to main minigui on November 27, 2021 (dub v10.4)
15619 +/
15620 class StateChanged(alias field) : Event {
15621 	enum EventString = __traits(identifier, __traits(parent, field)) ~ "." ~ __traits(identifier, field) ~ ":change";
15622 	override bool cancelable() const { return false; }
15623 	this(Widget target, typeof(field) newValue) {
15624 		this.newValue = newValue;
15625 		super(EventString, target);
15626 	}
15627 
15628 	typeof(field) newValue;
15629 }
15630 
15631 /++
15632 	Convenience function to add a `triggered` event listener.
15633 
15634 	Its implementation is simply `w.addEventListener("triggered", dg);`
15635 
15636 	History:
15637 		Added November 27, 2021 (dub v10.4)
15638 +/
15639 void addWhenTriggered(Widget w, void delegate() dg) {
15640 	w.addEventListener("triggered", dg);
15641 }
15642 
15643 /++
15644 	Observable varables can be added to widgets and when they are changed, it fires
15645 	off a [StateChanged] event so you can react to it.
15646 
15647 	It is implemented as a getter and setter property, along with another helper you
15648 	can use to subscribe whith is `name_changed`. You can also subscribe to the [StateChanged]
15649 	event through the usual means. Just give the name of the variable. See [StateChanged] for an
15650 	example.
15651 
15652 	History:
15653 		Moved from minigui_addons.webview to main minigui on November 27, 2021 (dub v10.4)
15654 +/
15655 mixin template Observable(T, string name) {
15656 	private T backing;
15657 
15658 	mixin(q{
15659 		void } ~ name ~ q{_changed (void delegate(T) dg) {
15660 			this.addEventListener((StateChanged!this_thing ev) {
15661 				dg(ev.newValue);
15662 			});
15663 		}
15664 
15665 		@property T } ~ name ~ q{ () {
15666 			return backing;
15667 		}
15668 
15669 		@property void } ~ name ~ q{ (T t) {
15670 			backing = t;
15671 			auto event = new StateChanged!this_thing(this, t);
15672 			event.dispatch();
15673 		}
15674 	});
15675 
15676 	mixin("private alias this_thing = " ~ name ~ ";");
15677 }
15678 
15679 
15680 private bool startsWith(string test, string thing) {
15681 	if(test.length < thing.length)
15682 		return false;
15683 	return test[0 .. thing.length] == thing;
15684 }
15685 
15686 private bool endsWith(string test, string thing) {
15687 	if(test.length < thing.length)
15688 		return false;
15689 	return test[$ - thing.length .. $] == thing;
15690 }
15691 
15692 // still do layout delegation
15693 // and... split off Window from Widget.
15694 
15695 version(minigui_screenshots)
15696 struct Screenshot {
15697 	string name;
15698 }
15699 
15700 version(minigui_screenshots)
15701 static if(__VERSION__ > 2092)
15702 mixin(q{
15703 shared static this() {
15704 	import core.runtime;
15705 
15706 	static UnitTestResult screenshotMagic() {
15707 		string name;
15708 
15709 		import arsd.png;
15710 
15711 		auto results = new Window();
15712 		auto button = new Button("do it", results);
15713 
15714 		Window.newWindowCreated = delegate(Window w) {
15715 			Timer timer;
15716 			timer = new Timer(250, {
15717 				auto img = w.win.takeScreenshot();
15718 				timer.destroy();
15719 
15720 				version(Windows)
15721 					writePng("/var/www/htdocs/minigui-screenshots/windows/" ~ name ~ ".png", img);
15722 				else
15723 					writePng("/var/www/htdocs/minigui-screenshots/linux/" ~ name ~ ".png", img);
15724 
15725 				w.close();
15726 			});
15727 		};
15728 
15729 		button.addWhenTriggered( {
15730 
15731 		foreach(test; __traits(getUnitTests, mixin(__MODULE__))) {
15732 			name = null;
15733 			static foreach(attr; __traits(getAttributes, test)) {
15734 				static if(is(typeof(attr) == Screenshot))
15735 					name = attr.name;
15736 			}
15737 			if(name.length) {
15738 				test();
15739 			}
15740 		}
15741 
15742 		});
15743 
15744 		results.loop();
15745 
15746 		return UnitTestResult(0, 0, false, false);
15747 	}
15748 
15749 
15750 	Runtime.extendedModuleUnitTester = &screenshotMagic;
15751 }
15752 });
15753 version(minigui_screenshots) {
15754 	version(unittest)
15755 		void main() {}
15756 	else static assert(0, "dont forget the -unittest flag to dmd");
15757 }
15758 
15759 // FIXME: i called hotkey accelerator in some places. hotkey = key when menu is active like E&xit. accelerator = global shortcut.
15760 // FIXME: make multiple accelerators disambiguate based ona rgs
15761 // FIXME: MainWindow ctor should have same arg order as Window
15762 // FIXME: mainwindow ctor w/ client area size instead of total size.
15763 // Push on/off button (basically an alternate display of a checkbox) -- BS_PUSHLIKE and maybe BS_TEXT (BS_TOP moves it). see also BS_FLAT.
15764 // FIXME: tri-state checkbox
15765 // FIXME: subordinate controls grouping...