1 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb775498%28v=vs.85%29.aspx
2 
3 // if doing nested menus, make sure the straight line from where it pops up to any destination on the new popup is not going to disappear the menu until at least a delay
4 
5 // me@arsd:~/.kde/share/config$ vim kdeglobals
6 
7 // FIXME: i kinda like how you can show find locations in scrollbars in the chrome browisers i wanna support that here too.
8 
9 // https://www.freedesktop.org/wiki/Accessibility/AT-SPI2/
10 
11 // for responsive design, a collapsible widget that if it doesn't have enough room, it just automatically becomes a "more" button or whatever.
12 
13 // responsive minigui, menu search, and file open with a preview hook on the side.
14 
15 // FIXME: add menu checkbox and menu icon eventually
16 
17 /*
18 
19 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
20 
21 the virtual functions remain as the default calculated values. then the reads go through some proxy object that can override it...
22 */
23 
24 // FIXME: a popup with slightly shaped window pointing at the mouse might eb useful in places
25 
26 // FIXME: text label must be copyable to the clipboard, at least as a full chunk.
27 
28 // FIXME: opt-in file picker widget with image support
29 
30 // FIXME: number widget
31 
32 // https://www.codeguru.com/cpp/controls/buttonctrl/advancedbuttons/article.php/c5161/Native-Win32-ThemeAware-OwnerDraw-Controls-No-MFC.htm
33 // https://docs.microsoft.com/en-us/windows/win32/controls/using-visual-styles
34 
35 // osx style menu search.
36 
37 // would be cool for a scroll bar to have marking capabilities
38 // kinda like vim's marks just on clicks etc and visual representation
39 // generically. may be cool to add an up arrow to the bottom too
40 //
41 // leave a shadow of where you last were for going back easily
42 
43 // So a window needs to have a selection, and that can be represented by a type. This is manipulated by various
44 // functions like cut, copy, paste. Widgets can have a selection and that would assert teh selection ownership for
45 // the window.
46 
47 // so what about context menus?
48 
49 // https://docs.microsoft.com/en-us/windows/desktop/Controls/about-custom-draw
50 
51 // FIXME: make the scroll thing go to bottom when the content changes.
52 
53 // 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
54 
55 // FIXME: the scroll area MUST be fixed to use the proper apis under the hood.
56 
57 
58 // FIXME: add a command search thingy built in and implement tip.
59 // FIXME: omg omg what if menu functions have arguments and it can pop up a gui or command line script them?!
60 
61 // On Windows:
62 // FIXME: various labels look broken in high contrast mode
63 // FIXME: changing themes while the program is upen doesn't trigger a redraw
64 
65 // add note about manifest to documentation. also icons.
66 
67 // a pager control is just a horizontal scroll area just with arrows on the sides instead of a scroll bar
68 // FIXME: clear the corner of scrollbars if they pop up
69 
70 // minigui needs to have a stdout redirection for gui mode on windows writeln
71 
72 // I kinda wanna do state reacting. sort of. idk tho
73 
74 // need a viewer widget that works like a web page - arrows scroll down consistently
75 
76 // I want a nanovega widget, and a svg widget with some kind of event handlers attached to the inside.
77 
78 // FIXME: the menus should be a bit more discoverable, at least a single click to open the others instead of two.
79 // and help info about menu items.
80 // and search in menus?
81 
82 // FIXME: a scroll area event signaling when a thing comes into view might be good
83 // FIXME: arrow key navigation and accelerators in dialog boxes will be a must
84 
85 // FIXME: unify Windows style line endings
86 
87 /*
88 	TODO:
89 
90 	pie menu
91 
92 	class Form with submit behavior -- see AutomaticDialog
93 
94 	disabled widgets and menu items
95 
96 	event cleanup
97 	tooltips.
98 	api improvements
99 
100 	margins are kinda broken, they don't collapse like they should. at least.
101 
102 	a table form btw would be a horizontal layout of vertical layouts holding each column
103 	that would give the same width things
104 */
105 
106 /*
107 
108 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
109 */
110 
111 /++
112 	minigui is a smallish GUI widget library, aiming to be on par with at least
113 	HTML4 forms and a few other expected gui components. It uses native controls
114 	on Windows and does its own thing on Linux (Mac is not currently supported but
115 	may be later, and should use native controls) to keep size down. The Linux
116 	appearance is similar to Windows 95 and avoids using images to maintain network
117 	efficiency on remote X connections, though you can customize that.
118 
119 
120 	minigui's only required dependencies are [arsd.simpledisplay] and [arsd.color],
121 	on which it is built. simpledisplay provides the low-level interfaces and minigui
122 	builds the concept of widgets inside the windows on top of it.
123 
124 	Its #1 goal is to be useful without being large and complicated like GTK and Qt.
125 	It isn't hugely concerned with appearance - on Windows, it just uses the native
126 	controls and native theme, and on Linux, it keeps it simple and I may change that
127 	at any time, though after May 2021, you can customize some things with css-inspired
128 	[Widget.Style] classes. (On Windows, if you compile with `-version=custom_widgets`,
129 	you can use the custom implementation there too, but... you shouldn't.)
130 
131 	The event model is similar to what you use in the browser with Javascript and the
132 	layout engine tries to automatically fit things in, similar to a css flexbox.
133 
134 	FOR BEST RESULTS: be sure to link with the appropriate subsystem command
135 	`-L/SUBSYSTEM:WINDOWS:5.0`, for example, because otherwise you'll get a
136 	console and other visual bugs.
137 
138 	HTML_To_Classes:
139 	$(SMALL_TABLE
140 		HTML Code | Minigui Class
141 
142 		`<input type="text">` | [LineEdit]
143 		`<textarea>` | [TextEdit]
144 		`<select>` | [DropDownSelection]
145 		`<input type="checkbox">` | [Checkbox]
146 		`<input type="radio">` | [Radiobox]
147 		`<button>` | [Button]
148 	)
149 
150 
151 	Stretchiness:
152 		The default is 4. You can use larger numbers for things that should
153 		consume a lot of space, and lower numbers for ones that are better at
154 		smaller sizes.
155 
156 	Overlapped_input:
157 		COMING EVENTUALLY:
158 		minigui will include a little bit of I/O functionality that just works
159 		with the event loop. If you want to get fancy, I suggest spinning up
160 		another thread and posting events back and forth.
161 
162 	$(H2 Add ons)
163 		See the `minigui_addons` directory in the arsd repo for some add on widgets
164 		you can import separately too.
165 
166 	$(H3 XML definitions)
167 		If you use [arsd.minigui_xml], you can create widget trees from XML at runtime.
168 
169 	$(H3 Scriptability)
170 		minigui is compatible with [arsd.script]. If you see `@scriptable` on a method
171 		in this documentation, it means you can call it from the script language.
172 
173 		Tip: to allow easy creation of widget trees from script, import [arsd.minigui_xml]
174 		and make [arsd.minigui_xml.makeWidgetFromString] available to your script:
175 
176 		---
177 		import arsd.minigui_xml;
178 		import arsd.script;
179 
180 		var globals = var.emptyObject;
181 		globals.makeWidgetFromString = &makeWidgetFromString;
182 
183 		// this now works
184 		interpret(`var window = makeWidgetFromString("<MainWindow />");`, globals);
185 		---
186 
187 		More to come.
188 
189 	History:
190 		Minigui had mostly additive changes or bug fixes since its inception until May 2021.
191 
192 		In May 2021 (dub v10.0), minigui got an overhaul. If it was versioned independently, I'd
193 		tag this as version 2.0.
194 
195 		Among the changes:
196 		$(LIST
197 			* 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.
198 
199 			See [Event] for details.
200 
201 			* 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.
202 
203 			See [DoubleClickEvent] for details.
204 
205 			* 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.
206 
207 			See [Widget.Style] for details.
208 
209 			// * A widget must now opt in to receiving keyboard focus, rather than opting out.
210 
211 			* 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.
212 
213 			* 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.
214 
215 			* [LabeledLineEdit] changed its default layout to vertical instead of horizontal. You can restore the old behavior by passing a `TextAlignment` argument to the constructor.
216 
217 			* 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.
218 
219 			* Various non-breaking additions.
220 		)
221 +/
222 module arsd.minigui;
223 
224 import arsd.core;
225 
226 /++
227 	This hello world sample will have an oversized button, but that's ok, you see your first window!
228 +/
229 version(Demo)
230 unittest {
231 	import arsd.minigui;
232 
233 	void main() {
234 		auto window = new MainWindow();
235 
236 		auto hello = new TextLabel("Hello, world!", TextAlignment.Center, window);
237 		auto button = new Button("Close", window);
238 		button.addWhenTriggered({
239 			window.close();
240 		});
241 
242 		window.loop();
243 	}
244 
245 	main(); // exclude from docs
246 }
247 
248 /++
249 	This example shows one way you can partition your window into a header
250 	and sidebar. Here, the header and sidebar have a fixed width, while the
251 	rest of the content sizes with the window.
252 
253 	It might be a new way of thinking about window layout to do things this
254 	way - perhaps [GridLayout] more matches your style of thought - but the
255 	concept here is to partition the window into sub-boxes with a particular
256 	size, then partition those boxes into further boxes.
257 
258 	$(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.)
259 
260 	So to make the header, start with a child layout that has a max height.
261 	It will use that space from the top, then the remaining children will
262 	split the remaining area, meaning you can think of is as just being another
263 	box you can split again. Keep splitting until you have the look you desire.
264 +/
265 // https://github.com/adamdruppe/arsd/issues/310
266 version(minigui_screenshots)
267 @Screenshot("layout")
268 unittest {
269 	import arsd.minigui;
270 
271 	// This helper class is just to help make the layout boxes visible.
272 	// think of it like a <div style="background-color: whatever;"></div> in HTML.
273 	class ColorWidget : Widget {
274 		this(Color color, Widget parent) {
275 			this.color = color;
276 			super(parent);
277 		}
278 		Color color;
279 		class Style : Widget.Style {
280 			override WidgetBackground background() { return WidgetBackground(color); }
281 		}
282 		mixin OverrideStyle!Style;
283 	}
284 
285 	void main() {
286 		auto window = new Window;
287 
288 		// the key is to give it a max height. This is one way to do it:
289 		auto header = new class HorizontalLayout {
290 			this() { super(window); }
291 			override int maxHeight() { return 50; }
292 		};
293 		// this next line is a shortcut way of doing it too, but it only works
294 		// for HorizontalLayout and VerticalLayout, and is less explicit, so it
295 		// is good to know how to make a new class like above anyway.
296 		// auto header = new HorizontalLayout(50, window);
297 
298 		auto bar = new HorizontalLayout(window);
299 
300 		// or since this is so common, VerticalLayout and HorizontalLayout both
301 		// can just take an argument in their constructor for max width/height respectively
302 
303 		// (could have tone this above too, but I wanted to demo both techniques)
304 		auto left = new VerticalLayout(100, bar);
305 
306 		// and this is the main section's container. A plain Widget instance is good enough here.
307 		auto container = new Widget(bar);
308 
309 		// and these just add color to the containers we made above for the screenshot.
310 		// in a real application, you can just add your actual controls instead of these.
311 		auto headerColorBox = new ColorWidget(Color.teal, header);
312 		auto leftColorBox = new ColorWidget(Color.green, left);
313 		auto rightColorBox = new ColorWidget(Color.purple, container);
314 
315 		window.loop();
316 	}
317 
318 	main(); // exclude from docs
319 }
320 
321 
322 public import arsd.simpledisplay;
323 /++
324 	Convenience import to override the Windows GDI Rectangle function (you can still use it through fully-qualified imports)
325 
326 	History:
327 		Was private until May 15, 2021.
328 +/
329 public alias Rectangle = arsd.color.Rectangle; // I specifically want this in here, not the win32 GDI Rectangle()
330 
331 version(Windows) {
332 	import core.sys.windows.winnls;
333 	import core.sys.windows.windef;
334 	import core.sys.windows.basetyps;
335 	import core.sys.windows.winbase;
336 	import core.sys.windows.winuser;
337 	import core.sys.windows.wingdi;
338 	static import gdi = core.sys.windows.wingdi;
339 }
340 
341 version(Windows) {
342 	version(minigui_manifest) {} else version=minigui_no_manifest;
343 
344 	version(minigui_no_manifest) {} else
345 	static if(__VERSION__ >= 2_083)
346 	version(CRuntime_Microsoft) { // FIXME: mingw?
347 		// assume we want commctrl6 whenever possible since there's really no reason not to
348 		// and this avoids some of the manifest hassle
349 		pragma(linkerDirective, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"");
350 	}
351 }
352 
353 // this is a hack to call the original window procedure on native win32 widgets if our event listener thing prevents default.
354 private bool lastDefaultPrevented;
355 
356 /// Methods marked with this are available from scripts if added to the [arsd.script] engine.
357 alias scriptable = arsd_jsvar_compatible;
358 
359 version(Windows) {
360 	// use native widgets when available unless specifically asked otherwise
361 	version(custom_widgets) {
362 		enum bool UsingCustomWidgets = true;
363 		enum bool UsingWin32Widgets = false;
364 	} else {
365 		version = win32_widgets;
366 		enum bool UsingCustomWidgets = false;
367 		enum bool UsingWin32Widgets = true;
368 	}
369 	// and native theming when needed
370 	//version = win32_theming;
371 } else {
372 	enum bool UsingCustomWidgets = true;
373 	enum bool UsingWin32Widgets = false;
374 	version=custom_widgets;
375 }
376 
377 
378 
379 /*
380 
381 	The main goals of minigui.d are to:
382 		1) Provide basic widgets that just work in a lightweight lib.
383 		   I basically want things comparable to a plain HTML form,
384 		   plus the easy and obvious things you expect from Windows
385 		   apps like a menu.
386 		2) Use native things when possible for best functionality with
387 		   least library weight.
388 		3) Give building blocks to provide easy extension for your
389 		   custom widgets, or hooking into additional native widgets
390 		   I didn't wrap.
391 		4) Provide interfaces for easy interaction between third
392 		   party minigui extensions. (event model, perhaps
393 		   signals/slots, drop-in ease of use bits.)
394 		5) Zero non-system dependencies, including Phobos as much as
395 		   I reasonably can. It must only import arsd.color and
396 		   my simpledisplay.d. If you need more, it will have to be
397 		   an extension module.
398 		6) An easy layout system that generally works.
399 
400 	A stretch goal is to make it easy to make gui forms with code,
401 	some kind of resource file (xml?) and even a wysiwyg designer.
402 
403 	Another stretch goal is to make it easy to hook data into the gui,
404 	including from reflection. So like auto-generate a form from a
405 	function signature or struct definition, or show a list from an
406 	array that automatically updates as the array is changed. Then,
407 	your program focuses on the data more than the gui interaction.
408 
409 
410 
411 	STILL NEEDED:
412 		* combo box. (this is diff than select because you can free-form edit too. more like a lineedit with autoselect)
413 		* slider
414 		* listbox
415 		* spinner
416 		* label?
417 		* rich text
418 */
419 
420 
421 /+
422 	enum LayoutMethods {
423 		 verticalFlex,
424 		 horizontalFlex,
425 		 inlineBlock, // left to right, no stretch, goes to next line as needed
426 		 static, // just set to x, y
427 		 verticalNoStretch, // browser style default
428 
429 		 inlineBlockFlex, // goes left to right, flexing, but when it runs out of space, it spills into next line
430 
431 		 grid, // magic
432 	}
433 +/
434 
435 /++
436 	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.
437 
438 
439 	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.
440 
441 	---
442 	class MinimalWidget : Widget {
443 		this(Widget parent) {
444 			super(parent);
445 		}
446 	}
447 	---
448 
449 	$(SIDEBAR
450 		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.
451 	)
452 
453 	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.
454 
455 	Among the things you'll most likely want to change in your custom widget:
456 
457 	$(LIST
458 		* 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.)
459 
460 		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.
461 
462 		Do this $(I after) calling the `super` constructor.
463 
464 		* 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.
465 
466 		Generally, painting is a job for leaf widgets, since child widgets would obscure your drawing area anyway. However, it is your decision.
467 
468 		* 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.
469 
470 		* 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.
471 	)
472 
473 	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.
474 
475 	It is also possible to embed a [SimpleWindow]-based native window inside a widget. See [OpenGlWidget]'s source code as an example.
476 
477 	Your own custom-drawn and native system controls can exist side-by-side.
478 
479 	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.
480 +/
481 class Widget : ReflectableProperties {
482 
483 	private bool willDraw() {
484 		return true;
485 	}
486 
487 	/+
488 	/++
489 		Calling this directly after constructor can give you a reflectable object as-needed so you don't pay for what you don't need.
490 
491 		History:
492 			Added September 15, 2021
493 			implemented.... ???
494 	+/
495 	void prepareReflection(this This)() {
496 
497 	}
498 	+/
499 
500 	private bool _enabled = true;
501 
502 	/++
503 		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.
504 
505 		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.
506 
507 		History:
508 			Added November 23, 2021 (dub v10.4)
509 
510 			Warning: the specific behavior of disabling with parents may change in the future.
511 		Bugs:
512 			Currently only implemented for widgets backed by native Windows controls.
513 
514 		See_Also: [disabledReason], [disabledBy]
515 	+/
516 	@property bool enabled() {
517 		return disabledBy() is null;
518 	}
519 
520 	/// ditto
521 	@property void enabled(bool yes) {
522 		_enabled = yes;
523 		version(win32_widgets) {
524 			if(hwnd)
525 				EnableWindow(hwnd, yes);
526 		}
527 		setDynamicState(DynamicState.disabled, yes);
528 	}
529 
530 	private string disabledReason_;
531 
532 	/++
533 		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.
534 
535 		Setting this does NOT disable the widget. You need to call `enabled = false;` separately. It does set the data though.
536 
537 		History:
538 			Added November 23, 2021 (dub v10.4)
539 		See_Also: [enabled], [disabledBy]
540 	+/
541 	@property string disabledReason() {
542 		auto w = disabledBy();
543 		return (w is null) ? null : w.disabledReason_;
544 	}
545 
546 	/// ditto
547 	@property void disabledReason(string reason) {
548 		disabledReason_ = reason;
549 	}
550 
551 	/++
552 		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.
553 
554 		History:
555 			Added November 25, 2021 (dub v10.4)
556 		See_Also: [enabled], [disabledReason]
557 	+/
558 	Widget disabledBy() {
559 		Widget p = this;
560 		while(p) {
561 			if(!p._enabled)
562 				return p;
563 			p = p.parent;
564 		}
565 		return null;
566 	}
567 
568 	/// Implementations of [ReflectableProperties] interface. See the interface for details.
569 	SetPropertyResult setPropertyFromString(string name, scope const(char)[] value, bool valueIsJson) {
570 		if(valueIsJson)
571 			return SetPropertyResult.wrongFormat;
572 		switch(name) {
573 			case "name":
574 				this.name = value.idup;
575 				return SetPropertyResult.success;
576 			case "statusTip":
577 				this.statusTip = value.idup;
578 				return SetPropertyResult.success;
579 			default:
580 				return SetPropertyResult.noSuchProperty;
581 		}
582 	}
583 	/// ditto
584 	void getPropertiesList(scope void delegate(string name) sink) const {
585 		sink("name");
586 		sink("statusTip");
587 	}
588 	/// ditto
589 	void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink) {
590 		switch(name) {
591 			case "name":
592 				sink(name, this.name, false);
593 				return;
594 			case "statusTip":
595 				sink(name, this.statusTip, false);
596 				return;
597 			default:
598 				sink(name, null, true);
599 		}
600 	}
601 
602 	/++
603 		Scales the given value to the system-reported DPI for the monitor on which the widget resides.
604 
605 		History:
606 			Added November 25, 2021 (dub v10.5)
607 			`Point` overload added January 12, 2022 (dub v10.6)
608 	+/
609 	int scaleWithDpi(int value, int assumedDpi = 96) {
610 		// avoid potential overflow with common special values
611 		if(value == int.max)
612 			return int.max;
613 		if(value == int.min)
614 			return int.min;
615 		if(value == 0)
616 			return 0;
617 
618 		auto divide = (parentWindow && parentWindow.win) ? parentWindow.win.actualDpi : assumedDpi;
619 		//divide = 138;
620 		// 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.
621 		// this also covers the case when actualDpi returns 0.
622 		if(divide < 96)
623 			divide = 96;
624 		return value * divide / assumedDpi;
625 	}
626 
627 	/// ditto
628 	Point scaleWithDpi(Point value, int assumedDpi = 96) {
629 		return Point(scaleWithDpi(value.x, assumedDpi), scaleWithDpi(value.y, assumedDpi));
630 	}
631 
632 	// avoid this it just forwards to a soon-to-be-deprecated function and is not remotely stable
633 	// I'll think up something better eventually
634 	protected final int defaultLineHeight() {
635 		auto cs = getComputedStyle();
636 		if(cs.font && !cs.font.isNull)
637 			return cs.font.height() * 5 / 4;
638 		else
639 			return scaleWithDpi(Window.lineHeight * 5/4);
640 	}
641 
642 	protected final int defaultTextWidth(const(char)[] text) {
643 		auto cs = getComputedStyle();
644 		if(cs.font && !cs.font.isNull)
645 			return cs.font.stringWidth(text);
646 		else
647 			return scaleWithDpi(Window.lineHeight * cast(int) text.length / 2);
648 	}
649 
650 	/++
651 		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.
652 
653 		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.
654 
655 		History:
656 			Added May 22, 2021
657 	+/
658 	protected bool encapsulatedChildren() {
659 		return false;
660 	}
661 
662 	private void privateDpiChanged() {
663 		dpiChanged();
664 		foreach(child; children)
665 			child.privateDpiChanged();
666 	}
667 
668 	/++
669 		Virtual hook to update any caches or fonts you need on the event of a dpi scaling change.
670 
671 		History:
672 			Added January 12, 2022 (dub v10.6)
673 	+/
674 	protected void dpiChanged() {
675 
676 	}
677 
678 	// Default layout properties {
679 
680 		int minWidth() { return 0; }
681 		int minHeight() {
682 			// default widgets have a vertical layout, therefore the minimum height is the sum of the contents
683 			int sum = this.paddingTop + this.paddingBottom;
684 			foreach(child; children) {
685 				if(child.hidden)
686 					continue;
687 				sum += child.minHeight();
688 				sum += child.marginTop();
689 				sum += child.marginBottom();
690 			}
691 
692 			return sum;
693 		}
694 		int maxWidth() { return int.max; }
695 		int maxHeight() { return int.max; }
696 		int widthStretchiness() { return 4; }
697 		int heightStretchiness() { return 4; }
698 
699 		/++
700 			Where stretchiness will grow from the flex basis, this shrinkiness will let it get smaller if needed to make room for other items.
701 
702 			History:
703 				Added June 15, 2021 (dub v10.1)
704 		+/
705 		int widthShrinkiness() { return 0; }
706 		/// ditto
707 		int heightShrinkiness() { return 0; }
708 
709 		/++
710 			The initial size of the widget for layout calculations. Default is 0.
711 
712 			See_Also: [https://developer.mozilla.org/en-US/docs/Web/CSS/flex-basis|CSS flex-basis]
713 
714 			History:
715 				Added June 15, 2021 (dub v10.1)
716 		+/
717 		int flexBasisWidth() { return 0; }
718 		/// ditto
719 		int flexBasisHeight() { return 0; }
720 
721 		/++
722 			Not stable.
723 
724 			Values are scaled with dpi after assignment. If you override the virtual functions, this may be ignored.
725 
726 			So if you set defaultPadding to 4 and the user is on 150% zoom, it will multiply to return 6.
727 
728 			History:
729 				Added January 5, 2023
730 		+/
731 		Rectangle defaultMargin;
732 		/// ditto
733 		Rectangle defaultPadding;
734 
735 		int marginLeft() { return scaleWithDpi(defaultMargin.left); }
736 		int marginRight() { return scaleWithDpi(defaultMargin.right); }
737 		int marginTop() { return scaleWithDpi(defaultMargin.top); }
738 		int marginBottom() { return scaleWithDpi(defaultMargin.bottom); }
739 		int paddingLeft() { return scaleWithDpi(defaultPadding.left); }
740 		int paddingRight() { return scaleWithDpi(defaultPadding.right); }
741 		int paddingTop() { return scaleWithDpi(defaultPadding.top); }
742 		int paddingBottom() { return scaleWithDpi(defaultPadding.bottom); }
743 		//LinePreference linePreference() { return LinePreference.PreferOwnLine; }
744 
745 		private bool recomputeChildLayoutRequired = true;
746 		private static class RecomputeEvent {}
747 		private __gshared rce = new RecomputeEvent();
748 		protected final void queueRecomputeChildLayout() {
749 			recomputeChildLayoutRequired = true;
750 
751 			if(this.parentWindow) {
752 				auto sw = this.parentWindow.win;
753 				assert(sw !is null);
754 				if(!sw.eventQueued!RecomputeEvent) {
755 					sw.postEvent(rce);
756 					// writeln("redraw requested from ", file,":",line," ", this.parentWindow.win.impl.window);
757 				}
758 			}
759 
760 		}
761 
762 		protected final void recomputeChildLayoutEntry() {
763 			if(recomputeChildLayoutRequired) {
764 				recomputeChildLayout();
765 				recomputeChildLayoutRequired = false;
766 				redraw();
767 			} else {
768 				// I still need to check the tree just in case one of them was queued up
769 				// and the event came up here instead of there.
770 				foreach(child; children)
771 					child.recomputeChildLayoutEntry();
772 			}
773 		}
774 
775 		// this function should (almost) never be called directly anymore... call recomputeChildLayoutEntry when executing it and queueRecomputeChildLayout if you just want it done soon
776 		void recomputeChildLayout() {
777 			.recomputeChildLayout!"height"(this);
778 		}
779 
780 	// }
781 
782 
783 	/++
784 		Returns the style's tag name string this object uses.
785 
786 		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.
787 
788 		This tag may never be used, it is just available for the [VisualTheme.getPropertyString] if it chooses to do something like CSS.
789 
790 		History:
791 			Added May 10, 2021
792 	+/
793 	string styleTagName() const {
794 		string n = typeid(this).name;
795 		foreach_reverse(idx, ch; n)
796 			if(ch == '.') {
797 				n = n[idx + 1 .. $];
798 				break;
799 			}
800 		return n;
801 	}
802 
803 	/// API for the [styleClassList]
804 	static struct ClassList {
805 		private Widget widget;
806 
807 		///
808 		void add(string s) {
809 			widget.styleClassList_ ~= s;
810 		}
811 
812 		///
813 		void remove(string s) {
814 			foreach(idx, s1; widget.styleClassList_)
815 				if(s1 == s) {
816 					widget.styleClassList_[idx] = widget.styleClassList_[$-1];
817 					widget.styleClassList_ = widget.styleClassList_[0 .. $-1];
818 					widget.styleClassList_.assumeSafeAppend();
819 					return;
820 				}
821 		}
822 
823 		/// Returns true if it was added, false if it was removed.
824 		bool toggle(string s) {
825 			if(contains(s)) {
826 				remove(s);
827 				return false;
828 			} else {
829 				add(s);
830 				return true;
831 			}
832 		}
833 
834 		///
835 		bool contains(string s) const {
836 			foreach(s1; widget.styleClassList_)
837 				if(s1 == s)
838 					return true;
839 			return false;
840 
841 		}
842 	}
843 
844 	private string[] styleClassList_;
845 
846 	/++
847 		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.
848 
849 		It has no inherent meaning, it is really just a place to put some metadata tags on individual objects.
850 
851 		History:
852 			Added May 10, 2021
853 	+/
854 	inout(ClassList) styleClassList() inout {
855 		return cast(inout(ClassList)) ClassList(cast() this);
856 	}
857 
858 	/++
859 		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.
860 
861 		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.
862 
863 		The upper 32 bits are available for your own extensions.
864 
865 		History:
866 			Added May 10, 2021
867 	+/
868 	enum DynamicState : ulong {
869 		focus = (1 << 0), /// the widget currently has the keyboard focus
870 		hover = (1 << 1), /// the mouse is currently hovering over the widget (may not always be updated)
871 		valid = (1 << 2), /// the widget's content has been validated and it passed (do not set if not validation has been performed!)
872 		invalid = (1 << 3), /// the widget's content has been validated and it failed (do not set if not validation has been performed!)
873 		checked = (1 << 4), /// the widget is toggleable and currently toggled on
874 		selected = (1 << 5), /// the widget represents one option of many and is currently selected, but is not necessarily focused nor checked.
875 		disabled = (1 << 6), /// the widget is currently unable to perform its designated task
876 		indeterminate = (1 << 7), /// the widget has tri-state and is between checked and not checked
877 		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.
878 
879 		USER_BEGIN = (1UL << 32),
880 	}
881 
882 	// I want to add the primary and cancel styles to buttons at least at some point somehow.
883 
884 	/// ditto
885 	@property ulong dynamicState() { return dynamicState_; }
886 	/// ditto
887 	@property ulong dynamicState(ulong newValue) {
888 		if(dynamicState != newValue) {
889 			auto old = dynamicState_;
890 			dynamicState_ = newValue;
891 
892 			useStyleProperties((scope Widget.Style s) {
893 				if(s.variesWithState(old ^ newValue))
894 					redraw();
895 			});
896 		}
897 		return dynamicState_;
898 	}
899 
900 	/// ditto
901 	void setDynamicState(ulong flags, bool state) {
902 		auto ds = dynamicState_;
903 		if(state)
904 			ds |= flags;
905 		else
906 			ds &= ~flags;
907 
908 		dynamicState = ds;
909 	}
910 
911 	private ulong dynamicState_;
912 
913 	deprecated("Use dynamic styles instead now") {
914 		Color backgroundColor() { return backgroundColor_; }
915 		void backgroundColor(Color c){ this.backgroundColor_ = c; }
916 
917 		MouseCursor cursor() { return GenericCursor.Default; }
918 	} private Color backgroundColor_ = Color.transparent;
919 
920 
921 	/++
922 		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).
923 
924 		It is here so there can be a specificity switch.
925 
926 		See [OverrideStyle] for a helper function to use your own.
927 
928 		History:
929 			Added May 11, 2021
930 	+/
931 	static class Style/* : StyleProperties*/ {
932 		public Widget widget; // public because the mixin template needs access to it
933 
934 		/++
935 			You must override this to trigger automatic redraws if you ever uses the `dynamicState` flag in your style.
936 
937 			History:
938 				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.
939 		+/
940 		bool variesWithState(ulong dynamicStateFlags) {
941 			version(win32_widgets) {
942 				if(widget.hwnd)
943 					return false;
944 			}
945 			return widget.tabStop && ((dynamicStateFlags & DynamicState.focus) ? true : false);
946 		}
947 
948 		///
949 		Color foregroundColor() {
950 			return WidgetPainter.visualTheme.foregroundColor;
951 		}
952 
953 		///
954 		WidgetBackground background() {
955 			// the default is a "transparent" background, which means
956 			// it goes as far up as it can to get the color
957 			if (widget.backgroundColor_ != Color.transparent)
958 				return WidgetBackground(widget.backgroundColor_);
959 			if (widget.parent)
960 				return widget.parent.getComputedStyle.background;
961 			return WidgetBackground(widget.backgroundColor_);
962 		}
963 
964 		private static OperatingSystemFont fontCached_;
965 		private OperatingSystemFont fontCached() {
966 			if(fontCached_ is null)
967 				fontCached_ = font();
968 			return fontCached_;
969 		}
970 
971 		/++
972 			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.
973 		+/
974 		OperatingSystemFont font() {
975 			return null;
976 		}
977 
978 		/++
979 			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.
980 
981 			You can return a member of [GenericCursor] or your own [MouseCursor] instance.
982 
983 			History:
984 				Was previously a method directly on [Widget], moved to [Widget.Style] on May 12, 2021
985 		+/
986 		MouseCursor cursor() {
987 			return GenericCursor.Default;
988 		}
989 
990 		FrameStyle borderStyle() {
991 			return FrameStyle.none;
992 		}
993 
994 		/++
995 		+/
996 		Color borderColor() {
997 			return Color.transparent;
998 		}
999 
1000 		FrameStyle outlineStyle() {
1001 			if(widget.dynamicState & DynamicState.focus)
1002 				return FrameStyle.dotted;
1003 			else
1004 				return FrameStyle.none;
1005 		}
1006 
1007 		Color outlineColor() {
1008 			return foregroundColor;
1009 		}
1010 	}
1011 
1012 	/++
1013 		This mixin overrides the [useStyleProperties] method to direct it toward your own style class.
1014 		The basic usage is simple:
1015 
1016 		---
1017 		static class Style : YourParentClass.Style { /* YourParentClass is frequently Widget, of course, but not always */
1018 			// override style hints as-needed here
1019 		}
1020 		OverrideStyle!Style; // add the method
1021 		---
1022 
1023 		$(TIP
1024 			While the class is not forced to be `static`, for best results, it should be. A non-static class
1025 			can not be inherited by other objects whereas the static one can. A property on the base class,
1026 			called [Widget.Style.widget|widget], is available for you to access its properties.
1027 		)
1028 
1029 		This exists just because [useStyleProperties] has a somewhat convoluted signature and its overrides must
1030 		repeat them. Moreover, its implementation uses a stack class to optimize GC pressure from small fetches
1031 		and that's a little tedious to repeat in your child classes too when you only care about changing the type.
1032 
1033 
1034 		It also has a further facility to pick a wholly differnet class based on the [DynamicState] of the Widget.
1035 		You may also just override `variesWithState` when you use this flag.
1036 
1037 		---
1038 		mixin OverrideStyle!(
1039 			DynamicState.focus, YourFocusedStyle,
1040 			DynamicState.hover, YourHoverStyle,
1041 			YourDefaultStyle
1042 		)
1043 		---
1044 
1045 		It checks if `dynamicState` matches the state and if so, returns the object given.
1046 
1047 		If there is no state mask given, the next one matches everything. The first match given is used.
1048 
1049 		However, since in most cases you'll want check state inside your individual methods, you probably won't
1050 		find much use for this whole-class swap out.
1051 
1052 		History:
1053 			Added May 16, 2021
1054 	+/
1055 	static protected mixin template OverrideStyle(S...) {
1056 		static import amg = arsd.minigui;
1057 		override void useStyleProperties(scope void delegate(scope amg.Widget.Style props) dg) {
1058 			ulong mask = 0;
1059 			foreach(idx, thing; S) {
1060 				static if(is(typeof(thing) : ulong)) {
1061 					mask = thing;
1062 				} else {
1063 					if(!(idx & 1) || (this.dynamicState & mask) == mask) {
1064 						//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.");
1065 						scope amg.Widget.Style s = new thing();
1066 						s.widget = this;
1067 						dg(s);
1068 						return;
1069 					}
1070 				}
1071 			}
1072 		}
1073 	}
1074 	/++
1075 		You can override this by hand, or use the [OverrideStyle] helper which is a bit less verbose.
1076 	+/
1077 	void useStyleProperties(scope void delegate(scope Style props) dg) {
1078 		scope Style s = new Style();
1079 		s.widget = this;
1080 		dg(s);
1081 	}
1082 
1083 
1084 	protected void sendResizeEvent() {
1085 		this.emit!ResizeEvent();
1086 	}
1087 
1088 	Menu contextMenu(int x, int y) { return null; }
1089 
1090 	final bool showContextMenu(int x, int y, int screenX = -2, int screenY = -2) {
1091 		if(parentWindow is null || parentWindow.win is null) return false;
1092 
1093 		auto menu = this.contextMenu(x, y);
1094 		if(menu is null)
1095 			return false;
1096 
1097 		version(win32_widgets) {
1098 			// FIXME: if it is -1, -1, do it at the current selection location instead
1099 			// tho the corner of the window, whcih it does now, isn't the literal worst.
1100 
1101 			if(screenX < 0 && screenY < 0) {
1102 				auto p = this.globalCoordinates();
1103 				if(screenX == -2)
1104 					p.x += x;
1105 				if(screenY == -2)
1106 					p.y += y;
1107 
1108 				screenX = p.x;
1109 				screenY = p.y;
1110 			}
1111 
1112 			if(!TrackPopupMenuEx(menu.handle, 0, screenX, screenY, parentWindow.win.impl.hwnd, null))
1113 				throw new Exception("TrackContextMenuEx");
1114 		} else version(custom_widgets) {
1115 			menu.popup(this, x, y);
1116 		}
1117 
1118 		return true;
1119 	}
1120 
1121 	/++
1122 		Removes this widget from its parent.
1123 
1124 		History:
1125 			`removeWidget` was made `final` on May 11, 2021.
1126 	+/
1127 	@scriptable
1128 	final void removeWidget() {
1129 		auto p = this.parent;
1130 		if(p) {
1131 			int item;
1132 			for(item = 0; item < p._children.length; item++)
1133 				if(p._children[item] is this)
1134 					break;
1135 			auto idx = item;
1136 			for(; item < p._children.length - 1; item++)
1137 				p._children[item] = p._children[item + 1];
1138 			p._children = p._children[0 .. $-1];
1139 
1140 			this.parent.widgetRemoved(idx, this);
1141 			//this.parent = null;
1142 
1143 			p.queueRecomputeChildLayout();
1144 		}
1145 		version(win32_widgets) {
1146 			removeAllChildren();
1147 			if(hwnd) {
1148 				DestroyWindow(hwnd);
1149 				hwnd = null;
1150 			}
1151 		}
1152 	}
1153 
1154 	/++
1155 		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.
1156 
1157 		History:
1158 			Added September 19, 2021
1159 	+/
1160 	protected void widgetRemoved(size_t oldIndex, Widget oldReference) { }
1161 
1162 	/++
1163 		Removes all child widgets from `this`. You should not use the removed widgets again.
1164 
1165 		Note that on Windows, it also destroys the native handles for the removed children recursively.
1166 
1167 		History:
1168 			Added July 1, 2021 (dub v10.2)
1169 	+/
1170 	void removeAllChildren() {
1171 		version(win32_widgets)
1172 		foreach(child; _children) {
1173 			child.removeAllChildren();
1174 			if(child.hwnd) {
1175 				DestroyWindow(child.hwnd);
1176 				child.hwnd = null;
1177 			}
1178 		}
1179 		auto orig = this._children;
1180 		this._children = null;
1181 		foreach(idx, w; orig)
1182 			this.widgetRemoved(idx, w);
1183 
1184 		queueRecomputeChildLayout();
1185 	}
1186 
1187 	/++
1188 		Calls [getByName] with the generic type of Widget. Meant for script interop where instantiating a template is impossible.
1189 	+/
1190 	@scriptable
1191 	Widget getChildByName(string name) {
1192 		return getByName(name);
1193 	}
1194 	/++
1195 		Finds the nearest descendant with the requested type and [name]. May return `this`.
1196 	+/
1197 	final WidgetClass getByName(WidgetClass = Widget)(string name) {
1198 		if(this.name == name)
1199 			if(auto c = cast(WidgetClass) this)
1200 				return c;
1201 		foreach(child; children) {
1202 			auto w = child.getByName(name);
1203 			if(auto c = cast(WidgetClass) w)
1204 				return c;
1205 		}
1206 		return null;
1207 	}
1208 
1209 	/++
1210 		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.
1211 		Names should be unique in a window.
1212 
1213 		See_Also: [getByName], [getChildByName]
1214 	+/
1215 	@scriptable string name;
1216 
1217 	private EventHandler[][string] bubblingEventHandlers;
1218 	private EventHandler[][string] capturingEventHandlers;
1219 
1220 	/++
1221 		Default event handlers. These are called on the appropriate
1222 		event unless [Event.preventDefault] is called on the event at
1223 		some point through the bubbling process.
1224 
1225 
1226 		If you are implementing your own widget and want to add custom
1227 		events, you should follow the same pattern here: create a virtual
1228 		function named `defaultEventHandler_eventname` with the implementation,
1229 		then, override [setupDefaultEventHandlers] and add a wrapped caller to
1230 		`defaultEventHandlers["eventname"]`. It should be wrapped like so:
1231 		`defaultEventHandlers["eventname"] = (Widget t, Event event) { t.defaultEventHandler_name(event); };`.
1232 		This ensures virtual dispatch based on the correct subclass.
1233 
1234 		Also, don't forget to call `super.setupDefaultEventHandlers();` too in your
1235 		overridden version.
1236 
1237 		You only need to do that on parent classes adding NEW event types. If you
1238 		just want to change the default behavior of an existing event type in a subclass,
1239 		you override the function (and optionally call `super.method_name`) like normal.
1240 
1241 	+/
1242 	protected EventHandler[string] defaultEventHandlers;
1243 
1244 	/// ditto
1245 	void setupDefaultEventHandlers() {
1246 		defaultEventHandlers["click"] = (Widget t, Event event) { t.defaultEventHandler_click(cast(ClickEvent) event); };
1247 		defaultEventHandlers["dblclick"] = (Widget t, Event event) { t.defaultEventHandler_dblclick(cast(DoubleClickEvent) event); };
1248 		defaultEventHandlers["keydown"] = (Widget t, Event event) { t.defaultEventHandler_keydown(cast(KeyDownEvent) event); };
1249 		defaultEventHandlers["keyup"] = (Widget t, Event event) { t.defaultEventHandler_keyup(cast(KeyUpEvent) event); };
1250 		defaultEventHandlers["mouseover"] = (Widget t, Event event) { t.defaultEventHandler_mouseover(cast(MouseOverEvent) event); };
1251 		defaultEventHandlers["mouseout"] = (Widget t, Event event) { t.defaultEventHandler_mouseout(cast(MouseOutEvent) event); };
1252 		defaultEventHandlers["mousedown"] = (Widget t, Event event) { t.defaultEventHandler_mousedown(cast(MouseDownEvent) event); };
1253 		defaultEventHandlers["mouseup"] = (Widget t, Event event) { t.defaultEventHandler_mouseup(cast(MouseUpEvent) event); };
1254 		defaultEventHandlers["mouseenter"] = (Widget t, Event event) { t.defaultEventHandler_mouseenter(cast(MouseEnterEvent) event); };
1255 		defaultEventHandlers["mouseleave"] = (Widget t, Event event) { t.defaultEventHandler_mouseleave(cast(MouseLeaveEvent) event); };
1256 		defaultEventHandlers["mousemove"] = (Widget t, Event event) { t.defaultEventHandler_mousemove(cast(MouseMoveEvent) event); };
1257 		defaultEventHandlers["char"] = (Widget t, Event event) { t.defaultEventHandler_char(cast(CharEvent) event); };
1258 		defaultEventHandlers["triggered"] = (Widget t, Event event) { t.defaultEventHandler_triggered(event); };
1259 		defaultEventHandlers["change"] = (Widget t, Event event) { t.defaultEventHandler_change(event); };
1260 		defaultEventHandlers["focus"] = (Widget t, Event event) { t.defaultEventHandler_focus(event); };
1261 		defaultEventHandlers["blur"] = (Widget t, Event event) { t.defaultEventHandler_blur(event); };
1262 		defaultEventHandlers["focusin"] = (Widget t, Event event) { t.defaultEventHandler_focusin(event); };
1263 		defaultEventHandlers["focusout"] = (Widget t, Event event) { t.defaultEventHandler_focusout(event); };
1264 	}
1265 
1266 	/// ditto
1267 	void defaultEventHandler_click(ClickEvent event) {}
1268 	/// ditto
1269 	void defaultEventHandler_dblclick(DoubleClickEvent event) {}
1270 	/// ditto
1271 	void defaultEventHandler_keydown(KeyDownEvent event) {}
1272 	/// ditto
1273 	void defaultEventHandler_keyup(KeyUpEvent event) {}
1274 	/// ditto
1275 	void defaultEventHandler_mousedown(MouseDownEvent event) {
1276 		if(event.button == MouseButton.left) {
1277 			if(this.tabStop)
1278 				this.focus();
1279 		}
1280 	}
1281 	/// ditto
1282 	void defaultEventHandler_mouseover(MouseOverEvent event) {}
1283 	/// ditto
1284 	void defaultEventHandler_mouseout(MouseOutEvent event) {}
1285 	/// ditto
1286 	void defaultEventHandler_mouseup(MouseUpEvent event) {}
1287 	/// ditto
1288 	void defaultEventHandler_mousemove(MouseMoveEvent event) {}
1289 	/// ditto
1290 	void defaultEventHandler_mouseenter(MouseEnterEvent event) {}
1291 	/// ditto
1292 	void defaultEventHandler_mouseleave(MouseLeaveEvent event) {}
1293 	/// ditto
1294 	void defaultEventHandler_char(CharEvent event) {}
1295 	/// ditto
1296 	void defaultEventHandler_triggered(Event event) {}
1297 	/// ditto
1298 	void defaultEventHandler_change(Event event) {}
1299 	/// ditto
1300 	void defaultEventHandler_focus(Event event) {}
1301 	/// ditto
1302 	void defaultEventHandler_blur(Event event) {}
1303 	/// ditto
1304 	void defaultEventHandler_focusin(Event event) {}
1305 	/// ditto
1306 	void defaultEventHandler_focusout(Event event) {}
1307 
1308 	/++
1309 		[Event]s use a Javascript-esque model. See more details on the [Event] page.
1310 
1311 		[addEventListener] returns an opaque handle that you can later pass to [removeEventListener].
1312 
1313 		addDirectEventListener just inserts a check `if(e.target !is this) return;` meaning it opts out
1314 		of participating in handler delegation.
1315 
1316 		$(TIP
1317 			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.
1318 		)
1319 	+/
1320 	EventListener addDirectEventListener(string event, void delegate() handler, bool useCapture = false) {
1321 		return addEventListener(event, (Widget, scope Event e) {
1322 			if(e.srcElement is this)
1323 				handler();
1324 		}, useCapture);
1325 	}
1326 
1327 	/// ditto
1328 	EventListener addDirectEventListener(string event, void delegate(Event) handler, bool useCapture = false) {
1329 		return addEventListener(event, (Widget, Event e) {
1330 			if(e.srcElement is this)
1331 				handler(e);
1332 		}, useCapture);
1333 	}
1334 
1335 	/// ditto
1336 	EventListener addDirectEventListener(Handler)(Handler handler, bool useCapture = false) {
1337 		static if(is(Handler Fn == delegate)) {
1338 		static if(is(Fn Params == __parameters)) {
1339 			return addEventListener(EventString!(Params[0]), (Widget, Event e) {
1340 				if(e.srcElement !is this)
1341 					return;
1342 				auto ty = cast(Params[0]) e;
1343 				if(ty !is null)
1344 					handler(ty);
1345 			}, useCapture);
1346 		} else static assert(0);
1347 		} else static assert(0, "Your handler wasn't usable because it wasn't passed a delegate. Use the delegate keyword at the call site.");
1348 	}
1349 
1350 	/// ditto
1351 	@scriptable
1352 	EventListener addEventListener(string event, void delegate() handler, bool useCapture = false) {
1353 		return addEventListener(event, (Widget, scope Event) { handler(); }, useCapture);
1354 	}
1355 
1356 	/// ditto
1357 	EventListener addEventListener(Handler)(Handler handler, bool useCapture = false) {
1358 		static if(is(Handler Fn == delegate)) {
1359 		static if(is(Fn Params == __parameters)) {
1360 			return addEventListener(EventString!(Params[0]), (Widget, Event e) {
1361 				auto ty = cast(Params[0]) e;
1362 				if(ty !is null)
1363 					handler(ty);
1364 			}, useCapture);
1365 		} else static assert(0);
1366 		} else static assert(0, "Your handler wasn't usable because it wasn't passed a delegate. Use the delegate keyword at the call site.");
1367 	}
1368 
1369 	/// ditto
1370 	EventListener addEventListener(string event, void delegate(Event) handler, bool useCapture = false) {
1371 		return addEventListener(event, (Widget, Event e) { handler(e); }, useCapture);
1372 	}
1373 
1374 	/// ditto
1375 	EventListener addEventListener(string event, EventHandler handler, bool useCapture = false) {
1376 		if(event.length > 2 && event[0..2] == "on")
1377 			event = event[2 .. $];
1378 
1379 		if(useCapture)
1380 			capturingEventHandlers[event] ~= handler;
1381 		else
1382 			bubblingEventHandlers[event] ~= handler;
1383 
1384 		return EventListener(this, event, handler, useCapture);
1385 	}
1386 
1387 	/// ditto
1388 	void removeEventListener(string event, EventHandler handler, bool useCapture = false) {
1389 		if(event.length > 2 && event[0..2] == "on")
1390 			event = event[2 .. $];
1391 
1392 		if(useCapture) {
1393 			if(event in capturingEventHandlers)
1394 			foreach(ref evt; capturingEventHandlers[event])
1395 				if(evt is handler) evt = null;
1396 		} else {
1397 			if(event in bubblingEventHandlers)
1398 			foreach(ref evt; bubblingEventHandlers[event])
1399 				if(evt is handler) evt = null;
1400 		}
1401 	}
1402 
1403 	/// ditto
1404 	void removeEventListener(EventListener listener) {
1405 		removeEventListener(listener.event, listener.handler, listener.useCapture);
1406 	}
1407 
1408 	static if(UsingSimpledisplayX11) {
1409 		void discardXConnectionState() {
1410 			foreach(child; children)
1411 				child.discardXConnectionState();
1412 		}
1413 
1414 		void recreateXConnectionState() {
1415 			foreach(child; children)
1416 				child.recreateXConnectionState();
1417 			redraw();
1418 		}
1419 	}
1420 
1421 	/++
1422 		Returns the coordinates of this widget on the screen, relative to the upper left corner of the whole screen.
1423 
1424 		History:
1425 			`globalCoordinates` was made `final` on May 11, 2021.
1426 	+/
1427 	Point globalCoordinates() {
1428 		int x = this.x;
1429 		int y = this.y;
1430 		auto p = this.parent;
1431 		while(p) {
1432 			x += p.x;
1433 			y += p.y;
1434 			p = p.parent;
1435 		}
1436 
1437 		static if(UsingSimpledisplayX11) {
1438 			auto dpy = XDisplayConnection.get;
1439 			arsd.simpledisplay.Window dummyw;
1440 			XTranslateCoordinates(dpy, this.parentWindow.win.impl.window, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
1441 		} else {
1442 			POINT pt;
1443 			pt.x = x;
1444 			pt.y = y;
1445 			MapWindowPoints(this.parentWindow.win.impl.hwnd, null, &pt, 1);
1446 			x = pt.x;
1447 			y = pt.y;
1448 		}
1449 
1450 		return Point(x, y);
1451 	}
1452 
1453 	version(win32_widgets)
1454 	int handleWmDrawItem(DRAWITEMSTRUCT* dis) { return 0; }
1455 
1456 	version(win32_widgets)
1457 	/// Called when a WM_COMMAND is sent to the associated hwnd.
1458 	void handleWmCommand(ushort cmd, ushort id) {}
1459 
1460 	version(win32_widgets)
1461 	/++
1462 		Called when a WM_NOTIFY is sent to the associated hwnd.
1463 
1464 		History:
1465 	+/
1466 	int handleWmNotify(NMHDR* hdr, int code, out int mustReturn) { return 0; }
1467 
1468 	version(win32_widgets)
1469 	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); }
1470 
1471 	/++
1472 		This tip is displayed in the status bar (if there is one in the containing window) when the mouse moves over this widget.
1473 
1474 		Updates to this variable will only be made visible on the next mouse enter event.
1475 	+/
1476 	@scriptable string statusTip;
1477 	// string toolTip;
1478 	// string helpText;
1479 
1480 	/++
1481 		If true, this widget can be focused via keyboard control with the tab key.
1482 
1483 		If false, it is assumed the widget itself does will never receive the keyboard focus (though its childen are free to).
1484 	+/
1485 	bool tabStop = true;
1486 	/++
1487 		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.)
1488 	+/
1489 	int tabOrder;
1490 
1491 	version(win32_widgets) {
1492 		static Widget[HWND] nativeMapping;
1493 		/// The native handle, if there is one.
1494 		HWND hwnd;
1495 		WNDPROC originalWindowProcedure;
1496 
1497 		SimpleWindow simpleWindowWrappingHwnd;
1498 
1499 		// please note it IGNORES your return value and does NOT forward it to Windows!
1500 		int hookedWndProc(UINT iMessage, WPARAM wParam, LPARAM lParam) {
1501 			return 0;
1502 		}
1503 	}
1504 	private bool implicitlyCreated;
1505 
1506 	/// 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.
1507 	int x;
1508 	/// ditto
1509 	int y;
1510 	private int _width;
1511 	private int _height;
1512 	private Widget[] _children;
1513 	private Widget _parent;
1514 	private Window _parentWindow;
1515 
1516 	/++
1517 		Returns the window to which this widget is attached.
1518 
1519 		History:
1520 			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.
1521 	+/
1522 	final @property inout(Window) parentWindow() inout @nogc nothrow pure { return _parentWindow; }
1523 	private @property void parentWindow(Window parent) {
1524 		_parentWindow = parent;
1525 		foreach(child; children)
1526 			child.parentWindow = parent; // please note that this is recursive
1527 	}
1528 
1529 	/++
1530 		Returns the list of the widget's children.
1531 
1532 		History:
1533 			Prior to May 11, 2021, the `Widget[] children` was directly available. Now, only this property getter is available and the actual store is private.
1534 
1535 			Children should be added by the constructor most the time, but if that's impossible, use [addChild] and [removeWidget] to manage the list.
1536 	+/
1537 	final @property inout(Widget)[] children() inout @nogc nothrow pure { return _children; }
1538 
1539 	/++
1540 		Returns the widget's parent.
1541 
1542 		History:
1543 			Prior to May 11, 2021, the `Widget parent` variable was directly available. Now, only this property getter is permitted.
1544 
1545 			The parent should only be managed by the [addChild] and [removeWidget] method.
1546 	+/
1547 	final @property inout(Widget) parent() inout nothrow @nogc pure @safe return { return _parent; }
1548 
1549 	/// The widget's current size.
1550 	final @scriptable public @property int width() const nothrow @nogc pure @safe { return _width; }
1551 	/// ditto
1552 	final @scriptable public @property int height() const nothrow @nogc pure @safe { return _height; }
1553 
1554 	/// Only the layout manager should be calling these.
1555 	final protected @property int width(int a) @safe { return _width = a; }
1556 	/// ditto
1557 	final protected @property int height(int a) @safe { return _height = a; }
1558 
1559 	/++
1560 		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.
1561 
1562 		It is also responsible for calling [sendResizeEvent] to notify other listeners that the widget has changed size.
1563 	+/
1564 	protected void registerMovement() {
1565 		version(win32_widgets) {
1566 			if(hwnd) {
1567 				auto pos = getChildPositionRelativeToParentHwnd(this);
1568 				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
1569 			}
1570 		}
1571 		sendResizeEvent();
1572 	}
1573 
1574 	/// Creates the widget and adds it to the parent.
1575 	this(Widget parent) {
1576 		if(parent !is null)
1577 			parent.addChild(this);
1578 		setupDefaultEventHandlers();
1579 	}
1580 
1581 	/// 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.
1582 	@scriptable
1583 	bool isFocused() {
1584 		return parentWindow && parentWindow.focusedWidget is this;
1585 	}
1586 
1587 	private bool showing_ = true;
1588 	///
1589 	bool showing() { return showing_; }
1590 	///
1591 	bool hidden() { return !showing_; }
1592 	/++
1593 		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.
1594 	+/
1595 	void showing(bool s, bool recalculate = true) {
1596 		auto so = showing_;
1597 		showing_ = s;
1598 		if(s != so) {
1599 			version(win32_widgets)
1600 			if(hwnd)
1601 				ShowWindow(hwnd, s ? SW_SHOW : SW_HIDE);
1602 
1603 			if(parent && recalculate) {
1604 				parent.queueRecomputeChildLayout();
1605 				parent.redraw();
1606 			}
1607 
1608 			foreach(child; children)
1609 				child.showing(s, false);
1610 
1611 		}
1612 		queueRecomputeChildLayout();
1613 		redraw();
1614 	}
1615 	/// Convenience method for `showing = true`
1616 	@scriptable
1617 	void show() {
1618 		showing = true;
1619 	}
1620 	/// Convenience method for `showing = false`
1621 	@scriptable
1622 	void hide() {
1623 		showing = false;
1624 	}
1625 
1626 	///
1627 	@scriptable
1628 	void focus() {
1629 		assert(parentWindow !is null);
1630 		if(isFocused())
1631 			return;
1632 
1633 		if(parentWindow.focusedWidget) {
1634 			// FIXME: more details here? like from and to
1635 			auto from = parentWindow.focusedWidget;
1636 			parentWindow.focusedWidget.setDynamicState(DynamicState.focus, false);
1637 			parentWindow.focusedWidget = null;
1638 			from.emit!BlurEvent();
1639 			this.emit!FocusOutEvent();
1640 		}
1641 
1642 
1643 		version(win32_widgets) {
1644 			if(this.hwnd !is null)
1645 				SetFocus(this.hwnd);
1646 		}
1647 		//else static if(UsingSimpledisplayX11)
1648 			//this.parentWindow.win.focus();
1649 
1650 		parentWindow.focusedWidget = this;
1651 		parentWindow.focusedWidget.setDynamicState(DynamicState.focus, true);
1652 		this.emit!FocusEvent();
1653 		this.emit!FocusInEvent();
1654 	}
1655 
1656 	/+
1657 	/++
1658 		Unfocuses the widget. This may reset
1659 	+/
1660 	@scriptable
1661 	void blur() {
1662 
1663 	}
1664 	+/
1665 
1666 
1667 	/++
1668 		This is called when the widget is added to a window. It gives you a chance to set up event hooks.
1669 
1670 		Update on May 11, 2021: I'm considering removing this method. You can usually achieve these things through looser-coupled methods.
1671 	+/
1672 	void attachedToWindow(Window w) {}
1673 	/++
1674 		Callback when the widget is added to another widget.
1675 
1676 		Update on May 11, 2021: I'm considering removing this method since I've never actually found it useful.
1677 	+/
1678 	void addedTo(Widget w) {}
1679 
1680 	/++
1681 		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.
1682 
1683 		This is available primarily to be overridden. For example, [MainWindow] overrides it to redirect its children into a central widget.
1684 	+/
1685 	protected void addChild(Widget w, int position = int.max) {
1686 		assert(w._parent !is this, "Child cannot be added twice to the same parent");
1687 		assert(w !is this, "Child cannot be its own parent!");
1688 		w._parent = this;
1689 		if(position == int.max || position == children.length) {
1690 			_children ~= w;
1691 		} else {
1692 			assert(position < _children.length);
1693 			_children.length = _children.length + 1;
1694 			for(int i = cast(int) _children.length - 1; i > position; i--)
1695 				_children[i] = _children[i - 1];
1696 			_children[position] = w;
1697 		}
1698 
1699 		this.parentWindow = this._parentWindow;
1700 
1701 		w.addedTo(this);
1702 
1703 		if(this.hidden)
1704 			w.showing = false;
1705 
1706 		if(parentWindow !is null) {
1707 			w.attachedToWindow(parentWindow);
1708 			parentWindow.queueRecomputeChildLayout();
1709 			parentWindow.redraw();
1710 		}
1711 	}
1712 
1713 	/++
1714 		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.
1715 	+/
1716 	Widget getChildAtPosition(int x, int y) {
1717 		// it goes backward so the last one to show gets picked first
1718 		// might use z-index later
1719 		foreach_reverse(child; children) {
1720 			if(child.hidden)
1721 				continue;
1722 			if(child.x <= x && child.y <= y
1723 				&& ((x - child.x) < child.width)
1724 				&& ((y - child.y) < child.height))
1725 			{
1726 				return child;
1727 			}
1728 		}
1729 
1730 		return null;
1731 	}
1732 
1733 	/++
1734 		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.
1735 
1736 		History:
1737 			Added July 2, 2021 (v10.2)
1738 	+/
1739 	protected void addScrollPosition(ref int x, ref int y) {};
1740 
1741 	/++
1742 		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.
1743 
1744 		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.
1745 
1746 		[paint] is not called for system widgets as the OS library draws them instead.
1747 
1748 
1749 		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.
1750 
1751 		You should also look at [WidgetPainter.visualTheme] to be theme aware.
1752 
1753 		History:
1754 			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.
1755 	+/
1756 	void paint(WidgetPainter painter) {
1757 		version(win32_widgets)
1758 			if(hwnd) {
1759 				return;
1760 			}
1761 		painter.drawThemed(&paintContent); // note this refers to the following overload
1762 	}
1763 
1764 	/++
1765 		Responsible for drawing the content as the theme engine is responsible for other elements.
1766 
1767 		$(WARNING If you override [paint], this method may never be used as it is only called from inside the default implementation of `paint`.)
1768 
1769 		Params:
1770 			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.
1771 
1772 			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.
1773 
1774 			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.
1775 
1776 		Returns:
1777 			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.
1778 
1779 		History:
1780 			Added May 15, 2021
1781 	+/
1782 	Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
1783 		return bounds;
1784 	}
1785 
1786 	deprecated("Change ScreenPainter to WidgetPainter")
1787 	final void paint(ScreenPainter) { assert(0, "Change ScreenPainter to WidgetPainter and recompile your code"); }
1788 
1789 	/// I don't actually like the name of this
1790 	/// this draws a background on it
1791 	void erase(WidgetPainter painter) {
1792 		version(win32_widgets)
1793 			if(hwnd) return; // Windows will do it. I think.
1794 
1795 		auto c = getComputedStyle().background.color;
1796 		painter.fillColor = c;
1797 		painter.outlineColor = c;
1798 
1799 		version(win32_widgets) {
1800 			HANDLE b, p;
1801 			if(c.a == 0 && parent is parentWindow) {
1802 				// I don't remember why I had this really...
1803 				b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
1804 				p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
1805 			}
1806 		}
1807 		painter.drawRectangle(Point(0, 0), width, height);
1808 		version(win32_widgets) {
1809 			if(c.a == 0 && parent is parentWindow) {
1810 				SelectObject(painter.impl.hdc, p);
1811 				SelectObject(painter.impl.hdc, b);
1812 			}
1813 		}
1814 	}
1815 
1816 	///
1817 	WidgetPainter draw() {
1818 		int x = this.x, y = this.y;
1819 		auto parent = this.parent;
1820 		while(parent) {
1821 			x += parent.x;
1822 			y += parent.y;
1823 			parent = parent.parent;
1824 		}
1825 
1826 		auto painter = parentWindow.win.draw(true);
1827 		painter.originX = x;
1828 		painter.originY = y;
1829 		painter.setClipRectangle(Point(0, 0), width, height);
1830 		return WidgetPainter(painter, this);
1831 	}
1832 
1833 	/// 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.
1834 	protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force, bool invalidate) {
1835 		if(hidden)
1836 			return;
1837 
1838 		int paintX = x;
1839 		int paintY = y;
1840 		if(this.useNativeDrawing()) {
1841 			paintX = 0;
1842 			paintY = 0;
1843 			lox = 0;
1844 			loy = 0;
1845 			containment = Rectangle(0, 0, int.max, int.max);
1846 		}
1847 
1848 		painter.originX = lox + paintX;
1849 		painter.originY = loy + paintY;
1850 
1851 		bool actuallyPainted = false;
1852 
1853 		const clip = containment.intersectionOf(Rectangle(Point(lox + paintX, loy + paintY), Size(width, height)));
1854 		if(clip == Rectangle.init) {
1855 			// writeln(this, " clipped out");
1856 			return;
1857 		}
1858 
1859 		bool invalidateChildren = invalidate;
1860 
1861 		if(redrawRequested || force) {
1862 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
1863 
1864 			painter.drawingUpon = this;
1865 
1866 			erase(painter);
1867 			if(painter.visualTheme)
1868 				painter.visualTheme.doPaint(this, painter);
1869 			else
1870 				paint(painter);
1871 
1872 			if(invalidate) {
1873 				// sdpyPrintDebugString("invalidate " ~ typeid(this).name);
1874 				auto region = Rectangle(Point(clip.upperLeft.x - painter.originX, clip.upperRight.y - painter.originY), Size(clip.width, clip.height));
1875 				painter.invalidateRect(region);
1876 				// children are contained inside this, so no need to do extra work
1877 				invalidateChildren = false;
1878 			}
1879 
1880 			redrawRequested = false;
1881 			actuallyPainted = true;
1882 		}
1883 
1884 		foreach(child; children) {
1885 			version(win32_widgets)
1886 				if(child.useNativeDrawing()) continue;
1887 			child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted, invalidateChildren);
1888 		}
1889 
1890 		version(win32_widgets)
1891 		foreach(child; children) {
1892 			if(child.useNativeDrawing) {
1893 				painter = WidgetPainter(child.simpleWindowWrappingHwnd.draw(true), child);
1894 				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
1895 			}
1896 		}
1897 	}
1898 
1899 	protected bool useNativeDrawing() nothrow {
1900 		version(win32_widgets)
1901 			return hwnd !is null;
1902 		else
1903 			return false;
1904 	}
1905 
1906 	private static class RedrawEvent {}
1907 	private __gshared re = new RedrawEvent();
1908 
1909 	private bool redrawRequested;
1910 	///
1911 	final void redraw(string file = __FILE__, size_t line = __LINE__) {
1912 		redrawRequested = true;
1913 
1914 		if(this.parentWindow) {
1915 			auto sw = this.parentWindow.win;
1916 			assert(sw !is null);
1917 			if(!sw.eventQueued!RedrawEvent) {
1918 				sw.postEvent(re);
1919 				//  writeln("redraw requested from ", file,":",line," ", this.parentWindow.win.impl.window);
1920 			}
1921 		}
1922 	}
1923 
1924 	private SimpleWindow drawableWindow;
1925 
1926 	/++
1927 		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.
1928 
1929 		Returns:
1930 			`true` if you should do your default behavior.
1931 
1932 		History:
1933 			Added May 5, 2021
1934 
1935 		Bugs:
1936 			It does not do the static checks on gdc right now.
1937 	+/
1938 	final protected bool emit(EventType, this This, Args...)(Args args) {
1939 		version(GNU) {} else
1940 		static assert(classStaticallyEmits!(This, EventType), "The " ~ This.stringof ~ " class is not declared to emit " ~ EventType.stringof);
1941 		auto e = new EventType(this, args);
1942 		e.dispatch();
1943 		return !e.defaultPrevented;
1944 	}
1945 	/// ditto
1946 	final protected bool emit(string eventString, this This)() {
1947 		auto e = new Event(eventString, this);
1948 		e.dispatch();
1949 		return !e.defaultPrevented;
1950 	}
1951 
1952 	/++
1953 		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.
1954 
1955 		History:
1956 			Added May 5, 2021
1957 	+/
1958 	final public EventListener subscribe(EventType, this This)(void delegate(EventType) handler) {
1959 		static assert(classStaticallyEmits!(This, EventType), "The " ~ This.stringof ~ " class is not declared to emit " ~ EventType.stringof);
1960 		return addEventListener(handler);
1961 	}
1962 
1963 	/++
1964 		Gets the computed style properties from the visual theme.
1965 
1966 		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].)
1967 
1968 		History:
1969 			Added May 8, 2021
1970 	+/
1971 	final StyleInformation getComputedStyle() {
1972 		return StyleInformation(this);
1973 	}
1974 
1975 	int focusableWidgets(scope int delegate(Widget) dg) {
1976 		foreach(widget; WidgetStream(this)) {
1977 			if(widget.tabStop && !widget.hidden) {
1978 				int result = dg(widget);
1979 				if (result)
1980 					return result;
1981 			}
1982 		}
1983 		return 0;
1984 	}
1985 
1986 	/++
1987 		Calculates the border box (that is, the full width/height of the widget, from border edge to border edge)
1988 		for the given content box (the area between the padding)
1989 
1990 		History:
1991 			Added January 4, 2023 (dub v11.0)
1992 	+/
1993 	Rectangle borderBoxForContentBox(Rectangle contentBox) {
1994 		auto cs = getComputedStyle();
1995 
1996 		auto borderWidth = getBorderWidth(cs.borderStyle);
1997 
1998 		auto rect = contentBox;
1999 
2000 		rect.left -= borderWidth;
2001 		rect.right += borderWidth;
2002 		rect.top -= borderWidth;
2003 		rect.bottom += borderWidth;
2004 
2005 		auto insideBorderRect = rect;
2006 
2007 		rect.left -= cs.paddingLeft;
2008 		rect.right += cs.paddingRight;
2009 		rect.top -= cs.paddingTop;
2010 		rect.bottom += cs.paddingBottom;
2011 
2012 		return rect;
2013 	}
2014 
2015 
2016 	// FIXME: I kinda want to hide events from implementation widgets
2017 	// so it just catches them all and stops propagation...
2018 	// i guess i can do it with a event listener on star.
2019 
2020 	mixin Emits!KeyDownEvent; ///
2021 	mixin Emits!KeyUpEvent; ///
2022 	mixin Emits!CharEvent; ///
2023 
2024 	mixin Emits!MouseDownEvent; ///
2025 	mixin Emits!MouseUpEvent; ///
2026 	mixin Emits!ClickEvent; ///
2027 	mixin Emits!DoubleClickEvent; ///
2028 	mixin Emits!MouseMoveEvent; ///
2029 	mixin Emits!MouseOverEvent; ///
2030 	mixin Emits!MouseOutEvent; ///
2031 	mixin Emits!MouseEnterEvent; ///
2032 	mixin Emits!MouseLeaveEvent; ///
2033 
2034 	mixin Emits!ResizeEvent; ///
2035 
2036 	mixin Emits!BlurEvent; ///
2037 	mixin Emits!FocusEvent; ///
2038 
2039 	mixin Emits!FocusInEvent; ///
2040 	mixin Emits!FocusOutEvent; ///
2041 }
2042 
2043 /+
2044 /++
2045 	Interface to indicate that the widget has a simple value property.
2046 
2047 	History:
2048 		Added August 26, 2021
2049 +/
2050 interface HasValue!T {
2051 	/// Getter
2052 	@property T value();
2053 	/// Setter
2054 	@property void value(T);
2055 }
2056 
2057 /++
2058 	Interface to indicate that the widget has a range of possible values for its simple value property.
2059 	This would be present on something like a slider or possibly a number picker.
2060 
2061 	History:
2062 		Added September 11, 2021
2063 +/
2064 interface HasRangeOfValues!T : HasValue!T {
2065 	/// The minimum and maximum values in the range, inclusive.
2066 	@property T minValue();
2067 	@property void minValue(T); /// ditto
2068 	@property T maxValue(); /// ditto
2069 	@property void maxValue(T); /// ditto
2070 
2071 	/// The smallest step the user interface allows. User may still type in values without this limitation.
2072 	@property void step(T);
2073 	@property T step(); /// ditto
2074 }
2075 
2076 /++
2077 	Interface to indicate that the widget has a list of possible values the user can choose from.
2078 	This would be present on something like a drop-down selector.
2079 
2080 	The value is NOT necessarily one of the items on the list. Consider the case of a free-entry
2081 	combobox.
2082 
2083 	History:
2084 		Added September 11, 2021
2085 +/
2086 interface HasListOfValues!T : HasValue!T {
2087 	@property T[] values;
2088 	@property void values(T[]);
2089 
2090 	@property int selectedIndex(); // note it may return -1!
2091 	@property void selectedIndex(int);
2092 }
2093 +/
2094 
2095 /++
2096 	History:
2097 		Added September 2021 (dub v10.4)
2098 +/
2099 class GridLayout : Layout {
2100 
2101 	// 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.
2102 
2103 	/++
2104 		If a widget is too small to fill a grid cell, the graviy tells where it "sticks" to.
2105 	+/
2106 	enum Gravity {
2107 		Center    = 0,
2108 		NorthWest = North | West,
2109 		North     = 0b10_00,
2110 		NorthEast = North | East,
2111 		West      = 0b00_10,
2112 		East      = 0b00_01,
2113 		SouthWest = South | West,
2114 		South     = 0b01_00,
2115 		SouthEast = South | East,
2116 	}
2117 
2118 	/++
2119 		The width and height are in some proportional units and can often just be 12.
2120 	+/
2121 	this(int width, int height, Widget parent) {
2122 		this.gridWidth = width;
2123 		this.gridHeight = height;
2124 		super(parent);
2125 	}
2126 
2127 	/++
2128 		Sets the position of the given child.
2129 
2130 		The units of these arguments are in the proportional grid units you set in the constructor.
2131 	+/
2132 	Widget setChildPosition(return Widget child, int x, int y, int width, int height, Gravity gravity = Gravity.Center) {
2133 		// ensure it is in bounds
2134 		// then ensure no overlaps
2135 
2136 		ChildPosition p = ChildPosition(child, x, y, width, height, gravity);
2137 
2138 		foreach(ref position; positions) {
2139 			if(position.widget is child) {
2140 				position = p;
2141 				goto set;
2142 			}
2143 		}
2144 
2145 		positions ~= p;
2146 
2147 		set:
2148 
2149 		// FIXME: should this batch?
2150 		queueRecomputeChildLayout();
2151 
2152 		return child;
2153 	}
2154 
2155 	override void addChild(Widget w, int position = int.max) {
2156 		super.addChild(w, position);
2157 		//positions ~= ChildPosition(w);
2158 		if(position != int.max) {
2159 			// FIXME: align it so they actually match.
2160 		}
2161 	}
2162 
2163 	override void widgetRemoved(size_t idx, Widget w) {
2164 		// FIXME: keep the positions array aligned
2165 		// positions[idx].widget = null;
2166 	}
2167 
2168 	override void recomputeChildLayout() {
2169 		registerMovement();
2170 		int onGrid = cast(int) positions.length;
2171 		c: foreach(child; children) {
2172 			// just snap it to the grid
2173 			if(onGrid)
2174 			foreach(position; positions)
2175 				if(position.widget is child) {
2176 					child.x = this.width * position.x / this.gridWidth;
2177 					child.y = this.height * position.y / this.gridHeight;
2178 					child.width = this.width * position.width / this.gridWidth;
2179 					child.height = this.height * position.height / this.gridHeight;
2180 
2181 					auto diff = child.width - child.maxWidth();
2182 					// FIXME: gravity?
2183 					if(diff > 0) {
2184 						child.width = child.width - diff;
2185 
2186 						if(position.gravity & Gravity.West) {
2187 							// nothing needed, already aligned
2188 						} else if(position.gravity & Gravity.East) {
2189 							child.x += diff;
2190 						} else {
2191 							child.x += diff / 2;
2192 						}
2193 					}
2194 
2195 					diff = child.height - child.maxHeight();
2196 					// FIXME: gravity?
2197 					if(diff > 0) {
2198 						child.height = child.height - diff;
2199 
2200 						if(position.gravity & Gravity.North) {
2201 							// nothing needed, already aligned
2202 						} else if(position.gravity & Gravity.South) {
2203 							child.y += diff;
2204 						} else {
2205 							child.y += diff / 2;
2206 						}
2207 					}
2208 
2209 
2210 					child.recomputeChildLayout();
2211 					onGrid--;
2212 					continue c;
2213 				}
2214 			// the position isn't given on the grid array, we'll just fill in from where the explicit ones left off.
2215 		}
2216 	}
2217 
2218 	private struct ChildPosition {
2219 		Widget widget;
2220 		int x;
2221 		int y;
2222 		int width;
2223 		int height;
2224 		Gravity gravity;
2225 	}
2226 	private ChildPosition[] positions;
2227 
2228 	int gridWidth = 12;
2229 	int gridHeight = 12;
2230 }
2231 
2232 ///
2233 abstract class ComboboxBase : Widget {
2234 	// if the user can enter arbitrary data, we want to use  2 == CBS_DROPDOWN
2235 	// or to always show the list, we want CBS_SIMPLE == 1
2236 	version(win32_widgets)
2237 		this(uint style, Widget parent) {
2238 			super(parent);
2239 			createWin32Window(this, "ComboBox"w, null, style);
2240 		}
2241 	else version(custom_widgets)
2242 		this(Widget parent) {
2243 			super(parent);
2244 
2245 			addEventListener((KeyDownEvent event) {
2246 				if(event.key == Key.Up) {
2247 					if(selection_ > -1) { // -1 means select blank
2248 						selection_--;
2249 						fireChangeEvent();
2250 					}
2251 					event.preventDefault();
2252 				}
2253 				if(event.key == Key.Down) {
2254 					if(selection_ + 1 < options.length) {
2255 						selection_++;
2256 						fireChangeEvent();
2257 					}
2258 					event.preventDefault();
2259 				}
2260 
2261 			});
2262 
2263 		}
2264 	else static assert(false);
2265 
2266 	/++
2267 		Returns the current list of options in the selection.
2268 
2269 		History:
2270 			Property accessor added March 1, 2022 (dub v10.7). Prior to that, it was private.
2271 	+/
2272 	final @property string[] options() const {
2273 		return cast(string[]) options_;
2274 	}
2275 
2276 	private string[] options_;
2277 	private int selection_ = -1;
2278 
2279 	/++
2280 		Adds an option to the end of options array.
2281 	+/
2282 	void addOption(string s) {
2283 		options_ ~= s;
2284 		version(win32_widgets)
2285 		SendMessageW(hwnd, 323 /*CB_ADDSTRING*/, 0, cast(LPARAM) toWstringzInternal(s));
2286 	}
2287 
2288 	/++
2289 		Gets the current selection as an index into the [options] array. Returns -1 if nothing is selected.
2290 	+/
2291 	int getSelection() {
2292 		return selection_;
2293 	}
2294 
2295 	/++
2296 		Returns the current selection as a string.
2297 
2298 		History:
2299 			Added November 17, 2021
2300 	+/
2301 	string getSelectionString() {
2302 		return selection_ == -1 ? null : options[selection_];
2303 	}
2304 
2305 	/++
2306 		Sets the current selection to an index in the options array, or to the given option if present.
2307 		Please note that the string version may do a linear lookup.
2308 
2309 		Returns:
2310 			the index you passed in
2311 
2312 		History:
2313 			The `string` based overload was added on March 1, 2022 (dub v10.7).
2314 
2315 			The return value was `void` prior to March 1, 2022.
2316 	+/
2317 	int setSelection(int idx) {
2318 		selection_ = idx;
2319 		version(win32_widgets)
2320 		SendMessageW(hwnd, 334 /*CB_SETCURSEL*/, idx, 0);
2321 
2322 		auto t = new SelectionChangedEvent(this, selection_, selection_ == -1 ? null : options[selection_]);
2323 		t.dispatch();
2324 
2325 		return idx;
2326 	}
2327 
2328 	/// ditto
2329 	int setSelection(string s) {
2330 		if(s !is null)
2331 		foreach(idx, item; options)
2332 			if(item == s) {
2333 				return setSelection(cast(int) idx);
2334 			}
2335 		return setSelection(-1);
2336 	}
2337 
2338 	/++
2339 		This event is fired when the selection changes. Note it inherits
2340 		from ChangeEvent!string, meaning you can use that as well, and it also
2341 		fills in [Event.intValue].
2342 	+/
2343 	static class SelectionChangedEvent : ChangeEvent!string {
2344 		this(Widget target, int iv, string sv) {
2345 			super(target, &stringValue);
2346 			this.iv = iv;
2347 			this.sv = sv;
2348 		}
2349 		immutable int iv;
2350 		immutable string sv;
2351 
2352 		override @property string stringValue() { return sv; }
2353 		override @property int intValue() { return iv; }
2354 	}
2355 
2356 	version(win32_widgets)
2357 	override void handleWmCommand(ushort cmd, ushort id) {
2358 		if(cmd == CBN_SELCHANGE) {
2359 			selection_ = cast(int) SendMessageW(hwnd, 327 /* CB_GETCURSEL */, 0, 0);
2360 			fireChangeEvent();
2361 		}
2362 	}
2363 
2364 	private void fireChangeEvent() {
2365 		if(selection_ >= options.length)
2366 			selection_ = -1;
2367 
2368 		auto t = new SelectionChangedEvent(this, selection_, selection_ == -1 ? null : options[selection_]);
2369 		t.dispatch();
2370 	}
2371 
2372 	version(win32_widgets) {
2373 		override int minHeight() { return defaultLineHeight + 6; }
2374 		override int maxHeight() { return defaultLineHeight + 6; }
2375 	} else {
2376 		override int minHeight() { return defaultLineHeight + 4; }
2377 		override int maxHeight() { return defaultLineHeight + 4; }
2378 	}
2379 
2380 	version(custom_widgets) {
2381 
2382 		// FIXME: this should scroll if there's too many elements to reasonably fit on screen
2383 
2384 		SimpleWindow dropDown;
2385 		void popup() {
2386 			auto w = width;
2387 			// FIXME: suggestedDropdownHeight see below
2388 			auto h = cast(int) this.options.length * defaultLineHeight + 8;
2389 
2390 			auto coord = this.globalCoordinates();
2391 			auto dropDown = new SimpleWindow(
2392 				w, h,
2393 				null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.dropdownMenu, WindowFlags.dontAutoShow, parentWindow ? parentWindow.win : null);
2394 
2395 			dropDown.move(coord.x, coord.y + this.height);
2396 
2397 			{
2398 				auto cs = getComputedStyle();
2399 				auto painter = dropDown.draw();
2400 				draw3dFrame(0, 0, w, h, painter, FrameStyle.risen, getComputedStyle().background.color);
2401 				auto p = Point(4, 4);
2402 				painter.outlineColor = cs.foregroundColor;
2403 				foreach(option; options) {
2404 					painter.drawText(p, option);
2405 					p.y += defaultLineHeight;
2406 				}
2407 			}
2408 
2409 			dropDown.setEventHandlers(
2410 				(MouseEvent event) {
2411 					if(event.type == MouseEventType.buttonReleased) {
2412 						dropDown.close();
2413 						auto element = (event.y - 4) / defaultLineHeight;
2414 						if(element >= 0 && element <= options.length) {
2415 							selection_ = element;
2416 
2417 							fireChangeEvent();
2418 						}
2419 					}
2420 				}
2421 			);
2422 
2423 			dropDown.visibilityChanged = (bool visible) {
2424 				if(visible) {
2425 					this.redraw();
2426 					dropDown.grabInput();
2427 				} else {
2428 					dropDown.releaseInputGrab();
2429 				}
2430 			};
2431 
2432 			dropDown.show();
2433 		}
2434 
2435 	}
2436 }
2437 
2438 /++
2439 	A drop-down list where the user must select one of the
2440 	given options. Like `<select>` in HTML.
2441 +/
2442 class DropDownSelection : ComboboxBase {
2443 	this(Widget parent) {
2444 		version(win32_widgets)
2445 			super(3 /* CBS_DROPDOWNLIST */ | WS_VSCROLL, parent);
2446 		else version(custom_widgets) {
2447 			super(parent);
2448 
2449 			addEventListener("focus", () { this.redraw; });
2450 			addEventListener("blur", () { this.redraw; });
2451 			addEventListener(EventType.change, () { this.redraw; });
2452 			addEventListener("mousedown", () { this.focus(); this.popup(); });
2453 			addEventListener((KeyDownEvent event) {
2454 				if(event.key == Key.Space)
2455 					popup();
2456 			});
2457 		} else static assert(false);
2458 	}
2459 
2460 	mixin Padding!q{2};
2461 	static class Style : Widget.Style {
2462 		override FrameStyle borderStyle() { return FrameStyle.risen; }
2463 	}
2464 	mixin OverrideStyle!Style;
2465 
2466 	version(custom_widgets)
2467 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
2468 		auto cs = getComputedStyle();
2469 
2470 		painter.drawText(bounds.upperLeft, selection_ == -1 ? "" : options[selection_]);
2471 
2472 		painter.outlineColor = cs.foregroundColor;
2473 		painter.fillColor = cs.foregroundColor;
2474 		Point[4] triangle;
2475 		enum padding = 6;
2476 		enum paddingV = 7;
2477 		enum triangleWidth = 10;
2478 		triangle[0] = Point(width - padding - triangleWidth, paddingV);
2479 		triangle[1] = Point(width - padding - triangleWidth / 2, height - paddingV);
2480 		triangle[2] = Point(width - padding - 0, paddingV);
2481 		triangle[3] = triangle[0];
2482 		painter.drawPolygon(triangle[]);
2483 
2484 		return bounds;
2485 	}
2486 
2487 	version(win32_widgets)
2488 	override void registerMovement() {
2489 		version(win32_widgets) {
2490 			if(hwnd) {
2491 				auto pos = getChildPositionRelativeToParentHwnd(this);
2492 				// the height given to this from Windows' perspective is supposed
2493 				// to include the drop down's height. so I add to it to give some
2494 				// room for that.
2495 				// FIXME: maybe make the subclass provide a suggestedDropdownHeight thing
2496 				MoveWindow(hwnd, pos[0], pos[1], width, height + 200, true);
2497 			}
2498 		}
2499 		sendResizeEvent();
2500 	}
2501 }
2502 
2503 /++
2504 	A text box with a drop down arrow listing selections.
2505 	The user can choose from the list, or type their own.
2506 +/
2507 class FreeEntrySelection : ComboboxBase {
2508 	this(Widget parent) {
2509 		version(win32_widgets)
2510 			super(2 /* CBS_DROPDOWN */, parent);
2511 		else version(custom_widgets) {
2512 			super(parent);
2513 			auto hl = new HorizontalLayout(this);
2514 			lineEdit = new LineEdit(hl);
2515 
2516 			tabStop = false;
2517 
2518 			lineEdit.addEventListener("focus", &lineEdit.selectAll);
2519 
2520 			auto btn = new class ArrowButton {
2521 				this() {
2522 					super(ArrowDirection.down, hl);
2523 				}
2524 				override int maxHeight() {
2525 					return int.max;
2526 				}
2527 			};
2528 			//btn.addDirectEventListener("focus", &lineEdit.focus);
2529 			btn.addEventListener("triggered", &this.popup);
2530 			addEventListener(EventType.change, (Event event) {
2531 				lineEdit.content = event.stringValue;
2532 				lineEdit.focus();
2533 				redraw();
2534 			});
2535 		}
2536 		else static assert(false);
2537 	}
2538 
2539 	version(custom_widgets) {
2540 		LineEdit lineEdit;
2541 	}
2542 }
2543 
2544 /++
2545 	A combination of free entry with a list below it.
2546 +/
2547 class ComboBox : ComboboxBase {
2548 	this(Widget parent) {
2549 		version(win32_widgets)
2550 			super(1 /* CBS_SIMPLE */ | CBS_NOINTEGRALHEIGHT, parent);
2551 		else version(custom_widgets) {
2552 			super(parent);
2553 			lineEdit = new LineEdit(this);
2554 			listWidget = new ListWidget(this);
2555 			listWidget.multiSelect = false;
2556 			listWidget.addEventListener(EventType.change, delegate(Widget, Event) {
2557 				string c = null;
2558 				foreach(option; listWidget.options)
2559 					if(option.selected) {
2560 						c = option.label;
2561 						break;
2562 					}
2563 				lineEdit.content = c;
2564 			});
2565 
2566 			listWidget.tabStop = false;
2567 			this.tabStop = false;
2568 			listWidget.addEventListener("focus", &lineEdit.focus);
2569 			this.addEventListener("focus", &lineEdit.focus);
2570 
2571 			addDirectEventListener(EventType.change, {
2572 				listWidget.setSelection(selection_);
2573 				if(selection_ != -1)
2574 					lineEdit.content = options[selection_];
2575 				lineEdit.focus();
2576 				redraw();
2577 			});
2578 
2579 			lineEdit.addEventListener("focus", &lineEdit.selectAll);
2580 
2581 			listWidget.addDirectEventListener(EventType.change, {
2582 				int set = -1;
2583 				foreach(idx, opt; listWidget.options)
2584 					if(opt.selected) {
2585 						set = cast(int) idx;
2586 						break;
2587 					}
2588 				if(set != selection_)
2589 					this.setSelection(set);
2590 			});
2591 		} else static assert(false);
2592 	}
2593 
2594 	override int minHeight() { return defaultLineHeight * 3; }
2595 	override int maxHeight() { return cast(int) options.length * defaultLineHeight + defaultLineHeight; }
2596 	override int heightStretchiness() { return 5; }
2597 
2598 	version(custom_widgets) {
2599 		LineEdit lineEdit;
2600 		ListWidget listWidget;
2601 
2602 		override void addOption(string s) {
2603 			listWidget.options ~= ListWidget.Option(s);
2604 			ComboboxBase.addOption(s);
2605 		}
2606 	}
2607 }
2608 
2609 /+
2610 class Spinner : Widget {
2611 	version(win32_widgets)
2612 	this(Widget parent) {
2613 		super(parent);
2614 		parentWindow = parent.parentWindow;
2615 		auto hlayout = new HorizontalLayout(this);
2616 		lineEdit = new LineEdit(hlayout);
2617 		upDownControl = new UpDownControl(hlayout);
2618 	}
2619 
2620 	LineEdit lineEdit;
2621 	UpDownControl upDownControl;
2622 }
2623 
2624 class UpDownControl : Widget {
2625 	version(win32_widgets)
2626 	this(Widget parent) {
2627 		super(parent);
2628 		parentWindow = parent.parentWindow;
2629 		createWin32Window(this, "msctls_updown32"w, null, 4/*UDS_ALIGNRIGHT*/| 2 /* UDS_SETBUDDYINT */ | 16 /* UDS_AUTOBUDDY */ | 32 /* UDS_ARROWKEYS */);
2630 	}
2631 
2632 	override int minHeight() { return defaultLineHeight; }
2633 	override int maxHeight() { return defaultLineHeight * 3/2; }
2634 
2635 	override int minWidth() { return defaultLineHeight * 3/2; }
2636 	override int maxWidth() { return defaultLineHeight * 3/2; }
2637 }
2638 +/
2639 
2640 /+
2641 class DataView : Widget {
2642 	// this is the omnibus data viewer
2643 	// the internal data layout is something like:
2644 	// string[string][] but also each node can have parents
2645 }
2646 +/
2647 
2648 
2649 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb775491(v=vs.85).aspx#PROGRESS_CLASS
2650 
2651 // http://svn.dsource.org/projects/bindings/trunk/win32/commctrl.d
2652 
2653 // FIXME: menus should prolly capture the mouse. ugh i kno.
2654 /*
2655 	TextEdit needs:
2656 
2657 	* caret manipulation
2658 	* selection control
2659 	* convenience functions for appendText, insertText, insertTextAtCaret, etc.
2660 
2661 	For example:
2662 
2663 	connect(paste, &textEdit.insertTextAtCaret);
2664 
2665 	would be nice.
2666 
2667 
2668 
2669 	I kinda want an omnibus dataview that combines list, tree,
2670 	and table - it can be switched dynamically between them.
2671 
2672 	Flattening policy: only show top level, show recursive, show grouped
2673 	List styles: plain list (e.g. <ul>), tiles (some details next to it), icons (like Windows explorer)
2674 
2675 	Single select, multi select, organization, drag+drop
2676 */
2677 
2678 //static if(UsingSimpledisplayX11)
2679 version(win32_widgets) {}
2680 else version(custom_widgets) {
2681 	enum scrollClickRepeatInterval = 50;
2682 
2683 deprecated("Get these properties off `Widget.getComputedStyle` instead. The defaults are now set in the `WidgetPainter.visualTheme`.") {
2684 	enum windowBackgroundColor = Color(212, 212, 212); // used to be 192
2685 	enum activeTabColor = lightAccentColor;
2686 	enum hoveringColor = Color(228, 228, 228);
2687 	enum buttonColor = windowBackgroundColor;
2688 	enum depressedButtonColor = darkAccentColor;
2689 	enum activeListXorColor = Color(255, 255, 127);
2690 	enum progressBarColor = Color(0, 0, 128);
2691 	enum activeMenuItemColor = Color(0, 0, 128);
2692 
2693 }}
2694 else static assert(false);
2695 deprecated("Get these properties off the `visualTheme` instead.") {
2696 	// these are used by horizontal rule so not just custom_widgets. for now at least.
2697 	enum darkAccentColor = Color(172, 172, 172);
2698 	enum lightAccentColor = Color(223, 223, 223); // used to be 223
2699 }
2700 
2701 private const(wchar)* toWstringzInternal(in char[] s) {
2702 	wchar[] str;
2703 	str.reserve(s.length + 1);
2704 	foreach(dchar ch; s)
2705 		str ~= ch;
2706 	str ~= '\0';
2707 	return str.ptr;
2708 }
2709 
2710 static if(SimpledisplayTimerAvailable)
2711 void setClickRepeat(Widget w, int interval, int delay = 250) {
2712 	Timer timer;
2713 	int delayRemaining = delay / interval;
2714 	if(delayRemaining <= 1)
2715 		delayRemaining = 2;
2716 
2717 	immutable originalDelayRemaining = delayRemaining;
2718 
2719 	w.addDirectEventListener((scope MouseDownEvent ev) {
2720 		if(ev.srcElement !is w)
2721 			return;
2722 		if(timer !is null) {
2723 			timer.destroy();
2724 			timer = null;
2725 		}
2726 		delayRemaining = originalDelayRemaining;
2727 		timer = new Timer(interval, () {
2728 			if(delayRemaining > 0)
2729 				delayRemaining--;
2730 			else {
2731 				auto ev = new Event("triggered", w);
2732 				ev.sendDirectly();
2733 			}
2734 		});
2735 	});
2736 
2737 	w.addDirectEventListener((scope MouseUpEvent ev) {
2738 		if(ev.srcElement !is w)
2739 			return;
2740 		if(timer !is null) {
2741 			timer.destroy();
2742 			timer = null;
2743 		}
2744 	});
2745 
2746 	w.addDirectEventListener((scope MouseLeaveEvent ev) {
2747 		if(ev.srcElement !is w)
2748 			return;
2749 		if(timer !is null) {
2750 			timer.destroy();
2751 			timer = null;
2752 		}
2753 	});
2754 
2755 }
2756 else
2757 void setClickRepeat(Widget w, int interval, int delay = 250) {}
2758 
2759 enum FrameStyle {
2760 	none, ///
2761 	risen, /// a 3d pop-out effect (think Windows 95 button)
2762 	sunk, /// a 3d sunken effect (think Windows 95 button as you click on it)
2763 	solid, ///
2764 	dotted, ///
2765 	fantasy, /// a style based on a popular fantasy video game
2766 }
2767 
2768 version(custom_widgets)
2769 deprecated
2770 void draw3dFrame(Widget widget, ScreenPainter painter, FrameStyle style) {
2771 	draw3dFrame(0, 0, widget.width, widget.height, painter, style, WidgetPainter.visualTheme.windowBackgroundColor);
2772 }
2773 
2774 version(custom_widgets)
2775 void draw3dFrame(Widget widget, ScreenPainter painter, FrameStyle style, Color background) {
2776 	draw3dFrame(0, 0, widget.width, widget.height, painter, style, background);
2777 }
2778 
2779 version(custom_widgets)
2780 deprecated
2781 void draw3dFrame(int x, int y, int width, int height, ScreenPainter painter, FrameStyle style) {
2782 	draw3dFrame(x, y, width, height, painter, style, WidgetPainter.visualTheme.windowBackgroundColor);
2783 }
2784 
2785 int getBorderWidth(FrameStyle style) {
2786 	final switch(style) {
2787 		case FrameStyle.sunk, FrameStyle.risen:
2788 			return 2;
2789 		case FrameStyle.none:
2790 			return 0;
2791 		case FrameStyle.solid:
2792 			return 1;
2793 		case FrameStyle.dotted:
2794 			return 1;
2795 		case FrameStyle.fantasy:
2796 			return 3;
2797 	}
2798 }
2799 
2800 int draw3dFrame(int x, int y, int width, int height, ScreenPainter painter, FrameStyle style, Color background, Color border = Color.transparent) {
2801 	int borderWidth = getBorderWidth(style);
2802 	final switch(style) {
2803 		case FrameStyle.sunk, FrameStyle.risen:
2804 			// outer layer
2805 			painter.outlineColor = style == FrameStyle.sunk ? Color.white : Color.black;
2806 		break;
2807 		case FrameStyle.none:
2808 			painter.outlineColor = background;
2809 		break;
2810 		case FrameStyle.solid:
2811 			painter.pen = Pen(border, 1);
2812 		break;
2813 		case FrameStyle.dotted:
2814 			painter.pen = Pen(border, 1, Pen.Style.Dotted);
2815 		break;
2816 		case FrameStyle.fantasy:
2817 			painter.pen = Pen(border, 3);
2818 		break;
2819 	}
2820 
2821 	painter.fillColor = background;
2822 	painter.drawRectangle(Point(x + 0, y + 0), width, height);
2823 
2824 
2825 	if(style == FrameStyle.sunk || style == FrameStyle.risen) {
2826 		// 3d effect
2827 		auto vt = WidgetPainter.visualTheme;
2828 
2829 		painter.outlineColor = (style == FrameStyle.sunk) ? vt.darkAccentColor : vt.lightAccentColor;
2830 		painter.drawLine(Point(x + 0, y + 0), Point(x + width, y + 0));
2831 		painter.drawLine(Point(x + 0, y + 0), Point(x + 0, y + height - 1));
2832 
2833 		// inner layer
2834 		//right, bottom
2835 		painter.outlineColor = (style == FrameStyle.sunk) ? vt.lightAccentColor : vt.darkAccentColor;
2836 		painter.drawLine(Point(x + width - 2, y + 2), Point(x + width - 2, y + height - 2));
2837 		painter.drawLine(Point(x + 2, y + height - 2), Point(x + width - 2, y + height - 2));
2838 		// left, top
2839 		painter.outlineColor = (style == FrameStyle.sunk) ? Color.black : Color.white;
2840 		painter.drawLine(Point(x + 1, y + 1), Point(x + width, y + 1));
2841 		painter.drawLine(Point(x + 1, y + 1), Point(x + 1, y + height - 2));
2842 	} else if(style == FrameStyle.fantasy) {
2843 		painter.pen = Pen(Color.white, 1, Pen.Style.Solid);
2844 		painter.fillColor = Color.transparent;
2845 		painter.drawRectangle(Point(x + 1, y + 1), Point(x + width - 1, y + height - 1));
2846 	}
2847 
2848 	return borderWidth;
2849 }
2850 
2851 /++
2852 	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.
2853 
2854 	See_Also:
2855 		[MenuItem]
2856 		[ToolButton]
2857 		[Menu.addItem]
2858 +/
2859 class Action {
2860 	version(win32_widgets) {
2861 		private int id;
2862 		private static int lastId = 9000;
2863 		private static Action[int] mapping;
2864 	}
2865 
2866 	KeyEvent accelerator;
2867 
2868 	// FIXME: disable message
2869 	// and toggle thing?
2870 	// ??? and trigger arguments too ???
2871 
2872 	/++
2873 		Params:
2874 			label = the textual label
2875 			icon = icon ID. See [GenericIcons]. There is currently no way to do custom icons.
2876 			triggered = initial handler, more can be added via the [triggered] member.
2877 	+/
2878 	this(string label, ushort icon = 0, void delegate() triggered = null) {
2879 		this.label = label;
2880 		this.iconId = icon;
2881 		if(triggered !is null)
2882 			this.triggered ~= triggered;
2883 		version(win32_widgets) {
2884 			id = ++lastId;
2885 			mapping[id] = this;
2886 		}
2887 	}
2888 
2889 	private string label;
2890 	private ushort iconId;
2891 	// icon
2892 
2893 	// when it is triggered, the triggered event is fired on the window
2894 	/// The list of handlers when it is triggered.
2895 	void delegate()[] triggered;
2896 }
2897 
2898 /*
2899 	plan:
2900 		keyboard accelerators
2901 
2902 		* menus (and popups and tooltips)
2903 		* status bar
2904 		* toolbars and buttons
2905 
2906 		sortable table view
2907 
2908 		maybe notification area icons
2909 		basic clipboard
2910 
2911 		* radio box
2912 		splitter
2913 		toggle buttons (optionally mutually exclusive, like in Paint)
2914 		label, rich text display, multi line plain text (selectable)
2915 		* fieldset
2916 		* nestable grid layout
2917 		single line text input
2918 		* multi line text input
2919 		slider
2920 		spinner
2921 		list box
2922 		drop down
2923 		combo box
2924 		auto complete box
2925 		* progress bar
2926 
2927 		terminal window/widget (on unix it might even be a pty but really idk)
2928 
2929 		ok button
2930 		cancel button
2931 
2932 		keyboard hotkeys
2933 
2934 		scroll widget
2935 
2936 		event redirections and network transparency
2937 		script integration
2938 */
2939 
2940 
2941 /*
2942 	MENUS
2943 
2944 	auto bar = new MenuBar(window);
2945 	window.menuBar = bar;
2946 
2947 	auto fileMenu = bar.addItem(new Menu("&File"));
2948 	fileMenu.addItem(new MenuItem("&Exit"));
2949 
2950 
2951 	EVENTS
2952 
2953 	For controls, you should usually use "triggered" rather than "click", etc., because
2954 	triggered handles both keyboard (focus and press as well as hotkeys) and mouse activation.
2955 	This is the case on menus and pushbuttons.
2956 
2957 	"click", on the other hand, currently only fires when it is literally clicked by the mouse.
2958 */
2959 
2960 
2961 /*
2962 enum LinePreference {
2963 	AlwaysOnOwnLine, // always on its own line
2964 	PreferOwnLine, // it will always start a new line, and if max width <= line width, it will expand all the way
2965 	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
2966 }
2967 */
2968 
2969 /++
2970 	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.
2971 
2972 	---
2973 	class MyWidget : Widget {
2974 		this(Widget parent) { super(parent); }
2975 
2976 		// set paddingLeft, paddingRight, paddingTop, and paddingBottom all to `return 4;` in one go:
2977 		mixin Padding!q{4};
2978 
2979 		// set marginLeft, marginRight, marginTop, and marginBottom all to `return 8;` in one go:
2980 		mixin Margin!q{8};
2981 
2982 		// but if I specify one outside, it overrides the override, so now marginLeft is 2,
2983 		// while Top/Bottom/Right remain 8 from the mixin above.
2984 		override int marginLeft() { return 2; }
2985 	}
2986 	---
2987 
2988 
2989 	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]).
2990 
2991 	Padding is the area inside a widget where its background is drawn, but the content avoids.
2992 
2993 	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!).
2994 
2995 	* 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.
2996 +/
2997 mixin template Padding(string code) {
2998 	override int paddingLeft() { return mixin(code);}
2999 	override int paddingRight() { return mixin(code);}
3000 	override int paddingTop() { return mixin(code);}
3001 	override int paddingBottom() { return mixin(code);}
3002 }
3003 
3004 /// ditto
3005 mixin template Margin(string code) {
3006 	override int marginLeft() { return mixin(code);}
3007 	override int marginRight() { return mixin(code);}
3008 	override int marginTop() { return mixin(code);}
3009 	override int marginBottom() { return mixin(code);}
3010 }
3011 
3012 private
3013 void recomputeChildLayout(string relevantMeasure)(Widget parent) {
3014 	enum calcingV = relevantMeasure == "height";
3015 
3016 	parent.registerMovement();
3017 
3018 	if(parent.children.length == 0)
3019 		return;
3020 
3021 	auto parentStyle = parent.getComputedStyle();
3022 
3023 	enum firstThingy = relevantMeasure == "height" ? "Top" : "Left";
3024 	enum secondThingy = relevantMeasure == "height" ? "Bottom" : "Right";
3025 
3026 	enum otherFirstThingy = relevantMeasure == "height" ? "Left" : "Top";
3027 	enum otherSecondThingy = relevantMeasure == "height" ? "Right" : "Bottom";
3028 
3029 	// my own width and height should already be set by the caller of this function...
3030 	int spaceRemaining = mixin("parent." ~ relevantMeasure) -
3031 		mixin("parentStyle.padding"~firstThingy~"()") -
3032 		mixin("parentStyle.padding"~secondThingy~"()");
3033 
3034 	int stretchinessSum;
3035 	int stretchyChildSum;
3036 	int lastMargin = 0;
3037 
3038 	int shrinkinessSum;
3039 	int shrinkyChildSum;
3040 
3041 	// set initial size
3042 	foreach(child; parent.children) {
3043 
3044 		auto childStyle = child.getComputedStyle();
3045 
3046 		if(cast(StaticPosition) child)
3047 			continue;
3048 		if(child.hidden)
3049 			continue;
3050 
3051 		const iw = child.flexBasisWidth();
3052 		const ih = child.flexBasisHeight();
3053 
3054 		static if(calcingV) {
3055 			child.width = parent.width -
3056 				mixin("childStyle.margin"~otherFirstThingy~"()") -
3057 				mixin("childStyle.margin"~otherSecondThingy~"()") -
3058 				mixin("parentStyle.padding"~otherFirstThingy~"()") -
3059 				mixin("parentStyle.padding"~otherSecondThingy~"()");
3060 
3061 			if(child.width < 0)
3062 				child.width = 0;
3063 			if(child.width > childStyle.maxWidth())
3064 				child.width = childStyle.maxWidth();
3065 
3066 			if(iw > 0) {
3067 				auto totalPossible = child.width;
3068 				if(child.width > iw && child.widthStretchiness() == 0)
3069 					child.width = iw;
3070 			}
3071 
3072 			child.height = mymax(childStyle.minHeight(), ih);
3073 		} else {
3074 			// set to take all the space
3075 			child.height = parent.height -
3076 				mixin("childStyle.margin"~firstThingy~"()") -
3077 				mixin("childStyle.margin"~secondThingy~"()") -
3078 				mixin("parentStyle.padding"~firstThingy~"()") -
3079 				mixin("parentStyle.padding"~secondThingy~"()");
3080 
3081 			// then clamp it
3082 			if(child.height < 0)
3083 				child.height = 0;
3084 			if(child.height > childStyle.maxHeight())
3085 				child.height = childStyle.maxHeight();
3086 
3087 			// and if possible, respect the ideal target
3088 			if(ih > 0) {
3089 				auto totalPossible = child.height;
3090 				if(child.height > ih && child.heightStretchiness() == 0)
3091 					child.height = ih;
3092 			}
3093 
3094 			// if we have an ideal, try to respect it, otehrwise, just use the minimum
3095 			child.width = mymax(childStyle.minWidth(), iw);
3096 		}
3097 
3098 		spaceRemaining -= mixin("child." ~ relevantMeasure);
3099 
3100 		int thisMargin = mymax(lastMargin, mixin("childStyle.margin"~firstThingy~"()"));
3101 		auto margin = mixin("childStyle.margin" ~ secondThingy ~ "()");
3102 		lastMargin = margin;
3103 		spaceRemaining -= thisMargin + margin;
3104 
3105 		auto s = mixin("child." ~ relevantMeasure ~ "Stretchiness()");
3106 		stretchinessSum += s;
3107 		if(s > 0)
3108 			stretchyChildSum++;
3109 
3110 		auto s2 = mixin("child." ~ relevantMeasure ~ "Shrinkiness()");
3111 		shrinkinessSum += s2;
3112 		if(s2 > 0)
3113 			shrinkyChildSum++;
3114 	}
3115 
3116 	if(spaceRemaining < 0 && shrinkyChildSum) {
3117 		// shrink to get into the space if it is possible
3118 		auto toRemove = -spaceRemaining;
3119 		auto removalPerItem  = toRemove * shrinkinessSum / shrinkyChildSum;
3120 		auto remainder = toRemove * shrinkinessSum % shrinkyChildSum;
3121 
3122 		// FIXME: wtf why am i shrinking things with no shrinkiness?
3123 
3124 		foreach(child; parent.children) {
3125 			auto childStyle = child.getComputedStyle();
3126 			if(cast(StaticPosition) child)
3127 				continue;
3128 			if(child.hidden)
3129 				continue;
3130 			static if(calcingV) {
3131 				auto maximum = childStyle.maxHeight();
3132 			} else {
3133 				auto maximum = childStyle.maxWidth();
3134 			}
3135 
3136 			mixin("child._" ~ relevantMeasure) -= removalPerItem + remainder; // this is removing more than needed to trigger the next thing. ugh.
3137 
3138 			spaceRemaining += removalPerItem + remainder;
3139 		}
3140 	}
3141 
3142 	// stretch to fill space
3143 	while(spaceRemaining > 0 && stretchinessSum && stretchyChildSum) {
3144 		auto spacePerChild = spaceRemaining / stretchinessSum;
3145 		bool spreadEvenly;
3146 		bool giveToBiggest;
3147 		if(spacePerChild <= 0) {
3148 			spacePerChild = spaceRemaining / stretchyChildSum;
3149 			spreadEvenly = true;
3150 		}
3151 		if(spacePerChild <= 0) {
3152 			giveToBiggest = true;
3153 		}
3154 		int previousSpaceRemaining = spaceRemaining;
3155 		stretchinessSum = 0;
3156 		Widget mostStretchy;
3157 		int mostStretchyS;
3158 		foreach(child; parent.children) {
3159 			auto childStyle = child.getComputedStyle();
3160 			if(cast(StaticPosition) child)
3161 				continue;
3162 			if(child.hidden)
3163 				continue;
3164 			static if(calcingV) {
3165 				auto maximum = childStyle.maxHeight();
3166 			} else {
3167 				auto maximum = childStyle.maxWidth();
3168 			}
3169 
3170 			if(mixin("child." ~ relevantMeasure) >= maximum) {
3171 				auto adj = mixin("child." ~ relevantMeasure) - maximum;
3172 				mixin("child._" ~ relevantMeasure) -= adj;
3173 				spaceRemaining += adj;
3174 				continue;
3175 			}
3176 			auto s = mixin("child." ~ relevantMeasure ~ "Stretchiness()");
3177 			if(s <= 0)
3178 				continue;
3179 			auto spaceAdjustment = spacePerChild * (spreadEvenly ? 1 : s);
3180 			mixin("child._" ~ relevantMeasure) += spaceAdjustment;
3181 			spaceRemaining -= spaceAdjustment;
3182 			if(mixin("child." ~ relevantMeasure) > maximum) {
3183 				auto diff = mixin("child." ~ relevantMeasure) - maximum;
3184 				mixin("child._" ~ relevantMeasure) -= diff;
3185 				spaceRemaining += diff;
3186 			} else if(mixin("child._" ~ relevantMeasure) < maximum) {
3187 				stretchinessSum += mixin("child." ~ relevantMeasure ~ "Stretchiness()");
3188 				if(mostStretchy is null || s >= mostStretchyS) {
3189 					mostStretchy = child;
3190 					mostStretchyS = s;
3191 				}
3192 			}
3193 		}
3194 
3195 		if(giveToBiggest && mostStretchy !is null) {
3196 			auto child = mostStretchy;
3197 			auto childStyle = child.getComputedStyle();
3198 			int spaceAdjustment = spaceRemaining;
3199 
3200 			static if(calcingV)
3201 				auto maximum = childStyle.maxHeight();
3202 			else
3203 				auto maximum = childStyle.maxWidth();
3204 
3205 			mixin("child._" ~ relevantMeasure) += spaceAdjustment;
3206 			spaceRemaining -= spaceAdjustment;
3207 			if(mixin("child._" ~ relevantMeasure) > maximum) {
3208 				auto diff = mixin("child." ~ relevantMeasure) - maximum;
3209 				mixin("child._" ~ relevantMeasure) -= diff;
3210 				spaceRemaining += diff;
3211 			}
3212 		}
3213 
3214 		if(spaceRemaining == previousSpaceRemaining) {
3215 			if(mostStretchy !is null) {
3216 				static if(calcingV)
3217 					auto maximum = mostStretchy.maxHeight();
3218 				else
3219 					auto maximum = mostStretchy.maxWidth();
3220 
3221 				mixin("mostStretchy._" ~ relevantMeasure) += spaceRemaining;
3222 				if(mixin("mostStretchy._" ~ relevantMeasure) > maximum)
3223 					mixin("mostStretchy._" ~ relevantMeasure) = maximum;
3224 			}
3225 			break; // apparently nothing more we can do
3226 		}
3227 
3228 	}
3229 
3230 	// position
3231 	lastMargin = 0;
3232 	int currentPos = mixin("parent.padding"~firstThingy~"()");
3233 	foreach(child; parent.children) {
3234 		auto childStyle = child.getComputedStyle();
3235 		if(cast(StaticPosition) child) {
3236 			child.recomputeChildLayout();
3237 			continue;
3238 		}
3239 		if(child.hidden)
3240 			continue;
3241 		auto margin = mixin("childStyle.margin" ~ secondThingy ~ "()");
3242 		int thisMargin = mymax(lastMargin, mixin("childStyle.margin"~firstThingy~"()"));
3243 		currentPos += thisMargin;
3244 		static if(calcingV) {
3245 			child.x = parentStyle.paddingLeft() + childStyle.marginLeft();
3246 			child.y = currentPos;
3247 		} else {
3248 			child.x = currentPos;
3249 			child.y = parentStyle.paddingTop() + childStyle.marginTop();
3250 
3251 		}
3252 		currentPos += mixin("child." ~ relevantMeasure);
3253 		currentPos += margin;
3254 		lastMargin = margin;
3255 
3256 		child.recomputeChildLayout();
3257 	}
3258 }
3259 
3260 int mymax(int a, int b) { return a > b ? a : b; }
3261 int mymax(int a, int b, int c) {
3262 	auto d = mymax(a, b);
3263 	return c > d ? c : d;
3264 }
3265 
3266 // OK so we need to make getting at the native window stuff possible in simpledisplay.d
3267 // and here, it must be integrable with the layout, the event system, and not be painted over.
3268 version(win32_widgets) {
3269 
3270 	// this function just does stuff that a parent window needs for redirection
3271 	int WindowProcedureHelper(Widget this_, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
3272 		this_.hookedWndProc(msg, wParam, lParam);
3273 
3274 		switch(msg) {
3275 
3276 			case WM_VSCROLL, WM_HSCROLL:
3277 				auto pos = HIWORD(wParam);
3278 				auto m = LOWORD(wParam);
3279 
3280 				auto scrollbarHwnd = cast(HWND) lParam;
3281 
3282 				if(auto widgetp = scrollbarHwnd in Widget.nativeMapping) {
3283 
3284 					//auto smw = cast(ScrollMessageWidget) widgetp.parent;
3285 
3286 					switch(m) {
3287 						/+
3288 						// I don't think those messages are ever actually sent normally by the widget itself,
3289 						// they are more used for the keyboard interface. methinks.
3290 						case SB_BOTTOM:
3291 							// writeln("end");
3292 							auto event = new Event("scrolltoend", *widgetp);
3293 							event.dispatch();
3294 							//if(!event.defaultPrevented)
3295 						break;
3296 						case SB_TOP:
3297 							// writeln("top");
3298 							auto event = new Event("scrolltobeginning", *widgetp);
3299 							event.dispatch();
3300 						break;
3301 						case SB_ENDSCROLL:
3302 							// idk
3303 						break;
3304 						+/
3305 						case SB_LINEDOWN:
3306 							(*widgetp).emitCommand!"scrolltonextline"();
3307 						return 0;
3308 						case SB_LINEUP:
3309 							(*widgetp).emitCommand!"scrolltopreviousline"();
3310 						return 0;
3311 						case SB_PAGEDOWN:
3312 							(*widgetp).emitCommand!"scrolltonextpage"();
3313 						return 0;
3314 						case SB_PAGEUP:
3315 							(*widgetp).emitCommand!"scrolltopreviouspage"();
3316 						return 0;
3317 						case SB_THUMBPOSITION:
3318 							auto ev = new ScrollToPositionEvent(*widgetp, pos);
3319 							ev.dispatch();
3320 						return 0;
3321 						case SB_THUMBTRACK:
3322 							// eh kinda lying but i like the real time update display
3323 							auto ev = new ScrollToPositionEvent(*widgetp, pos);
3324 							ev.dispatch();
3325 
3326 							// the event loop doesn't seem to carry on with a requested redraw..
3327 							// so we request it to get our dirty bit set...
3328 							// then we need to immediately actually redraw it too for instant feedback to user
3329 							SimpleWindow.processAllCustomEvents();
3330 							SimpleWindow.processAllCustomEvents();
3331 							//if(this_.parentWindow)
3332 								//this_.parentWindow.actualRedraw();
3333 
3334 							// and this ensures the WM_PAINT message is sent fairly quickly
3335 							// still seems to lag a little in large windows but meh it basically works.
3336 							if(this_.parentWindow) {
3337 								// FIXME: if painting is slow, this does still lag
3338 								// we probably will want to expose some user hook to ScrollWindowEx
3339 								// or something.
3340 								UpdateWindow(this_.parentWindow.hwnd);
3341 							}
3342 						return 0;
3343 						default:
3344 					}
3345 				}
3346 			break;
3347 
3348 			case WM_CONTEXTMENU:
3349 				auto hwndFrom = cast(HWND) wParam;
3350 
3351 				auto xPos = cast(short) LOWORD(lParam);
3352 				auto yPos = cast(short) HIWORD(lParam);
3353 
3354 				if(auto widgetp = hwndFrom in Widget.nativeMapping) {
3355 					POINT p;
3356 					p.x = xPos;
3357 					p.y = yPos;
3358 					ScreenToClient(hwnd, &p);
3359 					auto clientX = cast(ushort) p.x;
3360 					auto clientY = cast(ushort) p.y;
3361 
3362 					auto wap = widgetAtPoint(*widgetp, clientX, clientY);
3363 
3364 					if(wap.widget.showContextMenu(wap.x, wap.y, xPos, yPos)) {
3365 						return 0;
3366 					}
3367 				}
3368 			break;
3369 
3370 			case WM_DRAWITEM:
3371 				auto dis = cast(DRAWITEMSTRUCT*) lParam;
3372 				if(auto widgetp = dis.hwndItem in Widget.nativeMapping) {
3373 					return (*widgetp).handleWmDrawItem(dis);
3374 				}
3375 			break;
3376 
3377 			case WM_NOTIFY:
3378 				auto hdr = cast(NMHDR*) lParam;
3379 				auto hwndFrom = hdr.hwndFrom;
3380 				auto code = hdr.code;
3381 
3382 				if(auto widgetp = hwndFrom in Widget.nativeMapping) {
3383 					return (*widgetp).handleWmNotify(hdr, code, mustReturn);
3384 				}
3385 			break;
3386 			case WM_COMMAND:
3387 				auto handle = cast(HWND) lParam;
3388 				auto cmd = HIWORD(wParam);
3389 				return processWmCommand(hwnd, handle, cmd, LOWORD(wParam));
3390 
3391 			default:
3392 				// pass it on
3393 		}
3394 		return 0;
3395 	}
3396 
3397 
3398 
3399 	extern(Windows)
3400 	private
3401 	// this is called by native child windows, whereas the other hook is done by simpledisplay windows
3402 	// but can i merge them?!
3403 	LRESULT HookedWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
3404 		// try { writeln(iMessage); } catch(Exception e) {};
3405 
3406 		if(auto te = hWnd in Widget.nativeMapping) {
3407 			try {
3408 
3409 				te.hookedWndProc(iMessage, wParam, lParam);
3410 
3411 				int mustReturn;
3412 				auto ret = WindowProcedureHelper(*te, hWnd, iMessage, wParam, lParam, mustReturn);
3413 				if(mustReturn)
3414 					return ret;
3415 
3416 				if(iMessage == WM_SETFOCUS) {
3417 					auto lol = *te;
3418 					while(lol !is null && lol.implicitlyCreated)
3419 						lol = lol.parent;
3420 					lol.focus();
3421 					//(*te).parentWindow.focusedWidget = lol;
3422 				}
3423 
3424 
3425 				if(iMessage == WM_CTLCOLORBTN || iMessage == WM_CTLCOLORSTATIC) {
3426 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
3427 					return cast(typeof(return)) GetSysColorBrush(COLOR_3DFACE); // this is the window background color...
3428 						//GetStockObject(NULL_BRUSH);
3429 				}
3430 
3431 				auto pos = getChildPositionRelativeToParentOrigin(*te);
3432 				lastDefaultPrevented = false;
3433 				// try { writeln(typeid(*te)); } catch(Exception e) {}
3434 				if(SimpleWindow.triggerEvents(hWnd, iMessage, wParam, lParam, pos[0], pos[1], (*te).parentWindow.win) || !lastDefaultPrevented)
3435 					return CallWindowProcW((*te).originalWindowProcedure, hWnd, iMessage, wParam, lParam);
3436 				else {
3437 					// it was something we recognized, should only call the window procedure if the default was not prevented
3438 				}
3439 			} catch(Exception e) {
3440 				assert(0, e.toString());
3441 			}
3442 			return 0;
3443 		}
3444 		assert(0, "shouldn't be receiving messages for this window....");
3445 		//assert(0, to!string(hWnd) ~ " :: " ~ to!string(TextEdit.nativeMapping)); // not supposed to happen
3446 	}
3447 
3448 	extern(Windows)
3449 	private
3450 	// see for info https://jeffpar.github.io/kbarchive/kb/079/Q79982/
3451 	LRESULT HookedWndProcBSGROUPBOX_HACK(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
3452 		if(iMessage == WM_ERASEBKGND) {
3453 			auto dc = GetDC(hWnd);
3454 			auto b = SelectObject(dc, GetSysColorBrush(COLOR_3DFACE));
3455 			auto p = SelectObject(dc, GetStockObject(NULL_PEN));
3456 			RECT r;
3457 			GetWindowRect(hWnd, &r);
3458 			// since the pen is null, to fill the whole space, we need the +1 on both.
3459 			gdi.Rectangle(dc, 0, 0, r.right - r.left + 1, r.bottom - r.top + 1);
3460 			SelectObject(dc, p);
3461 			SelectObject(dc, b);
3462 			ReleaseDC(hWnd, dc);
3463 			InvalidateRect(hWnd, null, false); // redraw the border
3464 			return 1;
3465 		}
3466 		return HookedWndProc(hWnd, iMessage, wParam, lParam);
3467 	}
3468 
3469 	/++
3470 		Calls MS Windows' CreateWindowExW function to create a native backing for the given widget. It will create
3471 		needed mappings, window procedure hooks, and other private member variables needed to tie it into the rest
3472 		of minigui's expectations.
3473 
3474 		This should be called in your widget's constructor AFTER you call `super(parent);`. The parent window
3475 		member MUST already be initialized for this function to succeed, which is done by [Widget]'s base constructor.
3476 
3477 		It assumes `className` is zero-terminated. It should come from a `"wide string literal"w`.
3478 
3479 		To check if you can use this, use `static if(UsingWin32Widgets)`.
3480 	+/
3481 	void createWin32Window(Widget p, const(wchar)[] className, string windowText, DWORD style, DWORD extStyle = 0) {
3482 		assert(p.parentWindow !is null);
3483 		assert(p.parentWindow.win.impl.hwnd !is null);
3484 
3485 		auto bsgroupbox = style == BS_GROUPBOX;
3486 
3487 		HWND phwnd;
3488 
3489 		auto wtf = p.parent;
3490 		while(wtf) {
3491 			if(wtf.hwnd !is null) {
3492 				phwnd = wtf.hwnd;
3493 				break;
3494 			}
3495 			wtf = wtf.parent;
3496 		}
3497 
3498 		if(phwnd is null)
3499 			phwnd = p.parentWindow.win.impl.hwnd;
3500 
3501 		assert(phwnd !is null);
3502 
3503 		WCharzBuffer wt = WCharzBuffer(windowText);
3504 
3505 		style |= WS_VISIBLE | WS_CHILD;
3506 		//if(className != WC_TABCONTROL)
3507 			style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
3508 		p.hwnd = CreateWindowExW(extStyle, className.ptr, wt.ptr, style,
3509 				CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
3510 				phwnd, null, cast(HINSTANCE) GetModuleHandle(null), null);
3511 
3512 		assert(p.hwnd !is null);
3513 
3514 
3515 		static HFONT font;
3516 		if(font is null) {
3517 			NONCLIENTMETRICS params;
3518 			params.cbSize = params.sizeof;
3519 			if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
3520 				font = CreateFontIndirect(&params.lfMessageFont);
3521 			}
3522 		}
3523 
3524 		if(font)
3525 			SendMessage(p.hwnd, WM_SETFONT, cast(uint) font, true);
3526 
3527 		p.simpleWindowWrappingHwnd = new SimpleWindow(p.hwnd);
3528 		p.simpleWindowWrappingHwnd.beingOpenKeepsAppOpen = false;
3529 		Widget.nativeMapping[p.hwnd] = p;
3530 
3531 		if(bsgroupbox)
3532 		p.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(p.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProcBSGROUPBOX_HACK);
3533 		else
3534 		p.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(p.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc);
3535 
3536 		EnumChildWindows(p.hwnd, &childHandler, cast(LPARAM) cast(void*) p);
3537 
3538 		p.registerMovement();
3539 	}
3540 }
3541 
3542 version(win32_widgets)
3543 private
3544 extern(Windows) BOOL childHandler(HWND hwnd, LPARAM lparam) {
3545 	if(hwnd is null || hwnd in Widget.nativeMapping)
3546 		return true;
3547 	auto parent = cast(Widget) cast(void*) lparam;
3548 	Widget p = new Widget(null);
3549 	p._parent = parent;
3550 	p.parentWindow = parent.parentWindow;
3551 	p.hwnd = hwnd;
3552 	p.implicitlyCreated = true;
3553 	Widget.nativeMapping[p.hwnd] = p;
3554 	p.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(p.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc);
3555 	return true;
3556 }
3557 
3558 /++
3559 	Encapsulates the simpledisplay [ScreenPainter] for use on a [Widget], with [VisualTheme] and invalidated area awareness.
3560 +/
3561 struct WidgetPainter {
3562 	this(ScreenPainter screenPainter, Widget drawingUpon) {
3563 		this.drawingUpon = drawingUpon;
3564 		this.screenPainter = screenPainter;
3565 		if(auto font = visualTheme.defaultFontCached)
3566 			this.screenPainter.setFont(font);
3567 	}
3568 
3569 	/++
3570 		EXPERIMENTAL. subject to change.
3571 
3572 		When you draw a cursor, you can draw this to notify your window of where it is,
3573 		for IME systems to use.
3574 	+/
3575 	void notifyCursorPosition(int x, int y, int width, int height) {
3576 		if(auto a = drawingUpon.parentWindow)
3577 		if(auto w = a.inputProxy) {
3578 			w.setIMEPopupLocation(x + screenPainter.originX + width, y + screenPainter.originY + height);
3579 		}
3580 	}
3581 
3582 
3583 	///
3584 	ScreenPainter screenPainter;
3585 	/// Forward to the screen painter for other methods
3586 	alias screenPainter this;
3587 
3588 	private Widget drawingUpon;
3589 
3590 	/++
3591 		This is the list of rectangles that actually need to be redrawn.
3592 
3593 		Not actually implemented yet.
3594 	+/
3595 	Rectangle[] invalidatedRectangles;
3596 
3597 	private static BaseVisualTheme _visualTheme;
3598 
3599 	/++
3600 		Functions to access the visual theme and helpers to easily use it.
3601 
3602 		These are aware of the current widget's computed style out of the theme.
3603 	+/
3604 	static @property BaseVisualTheme visualTheme() {
3605 		if(_visualTheme is null)
3606 			_visualTheme = new DefaultVisualTheme();
3607 		return _visualTheme;
3608 	}
3609 
3610 	/// ditto
3611 	static @property void visualTheme(BaseVisualTheme theme) {
3612 		_visualTheme = theme;
3613 
3614 		// FIXME: notify all windows about the new theme
3615 	}
3616 
3617 	/// ditto
3618 	Color themeForeground() {
3619 		return drawingUpon.getComputedStyle().foregroundColor();
3620 	}
3621 
3622 	/// ditto
3623 	Color themeBackground() {
3624 		return drawingUpon.getComputedStyle().background.color;
3625 	}
3626 
3627 	int isDarkTheme() {
3628 		return 0; // unspecified, yes, no as enum. FIXME
3629 	}
3630 
3631 	/++
3632 		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.
3633 
3634 		It gives your draw delegate a [Rectangle] representing the coordinates inside your border and padding.
3635 
3636 		If you change teh clip rectangle, you should change it back before you return.
3637 
3638 
3639 		The sequence it uses is:
3640 			background
3641 			content (delegated to you)
3642 			border
3643 			focused outline
3644 			selected overlay
3645 
3646 		Example code:
3647 
3648 		---
3649 		void paint(WidgetPainter painter) {
3650 			painter.drawThemed((bounds) {
3651 				return bounds; // if the selection overlay should be contained, you can return it here.
3652 			});
3653 		}
3654 		---
3655 	+/
3656 	void drawThemed(scope Rectangle delegate(const Rectangle bounds) drawBody) {
3657 		drawThemed((WidgetPainter painter, const Rectangle bounds) {
3658 			return drawBody(bounds);
3659 		});
3660 	}
3661 	// this overload is actually mroe for setting the delegate to a virtual function
3662 	void drawThemed(scope Rectangle delegate(WidgetPainter painter, const Rectangle bounds) drawBody) {
3663 		Rectangle rect = Rectangle(0, 0, drawingUpon.width, drawingUpon.height);
3664 
3665 		auto cs = drawingUpon.getComputedStyle();
3666 
3667 		auto bg = cs.background.color;
3668 
3669 		auto borderWidth = draw3dFrame(0, 0, drawingUpon.width, drawingUpon.height, this, cs.borderStyle, bg, cs.borderColor);
3670 
3671 		rect.left += borderWidth;
3672 		rect.right -= borderWidth;
3673 		rect.top += borderWidth;
3674 		rect.bottom -= borderWidth;
3675 
3676 		auto insideBorderRect = rect;
3677 
3678 		rect.left += cs.paddingLeft;
3679 		rect.right -= cs.paddingRight;
3680 		rect.top += cs.paddingTop;
3681 		rect.bottom -= cs.paddingBottom;
3682 
3683 		this.outlineColor = this.themeForeground;
3684 		this.fillColor = bg;
3685 
3686 		auto widgetFont = cs.fontCached;
3687 		if(widgetFont !is null)
3688 			this.setFont(widgetFont);
3689 
3690 		rect = drawBody(this, rect);
3691 
3692 		if(widgetFont !is null) {
3693 			if(auto vtFont = visualTheme.defaultFontCached)
3694 				this.setFont(vtFont);
3695 			else
3696 				this.setFont(null);
3697 		}
3698 
3699 		if(auto os = cs.outlineStyle()) {
3700 			this.pen = Pen(cs.outlineColor(), 1, os == FrameStyle.dotted ? Pen.Style.Dotted : Pen.Style.Solid);
3701 			this.fillColor = Color.transparent;
3702 			this.drawRectangle(insideBorderRect);
3703 		}
3704 	}
3705 
3706 	/++
3707 		First, draw the background.
3708 		Then draw your content.
3709 		Next, draw the border.
3710 		And the focused indicator.
3711 		And the is-selected box.
3712 
3713 		If it is focused i can draw the outline too...
3714 
3715 		If selected i can even do the xor action but that's at the end.
3716 	+/
3717 	void drawThemeBackground() {
3718 
3719 	}
3720 
3721 	void drawThemeBorder() {
3722 
3723 	}
3724 
3725 	// all this stuff is a dangerous experiment....
3726 	static class ScriptableVersion {
3727 		ScreenPainterImplementation* p;
3728 		int originX, originY;
3729 
3730 		@scriptable:
3731 		void drawRectangle(int x, int y, int width, int height) {
3732 			p.drawRectangle(x + originX, y + originY, width, height);
3733 		}
3734 		void drawLine(int x1, int y1, int x2, int y2) {
3735 			p.drawLine(x1 + originX, y1 + originY, x2 + originX, y2 + originY);
3736 		}
3737 		void drawText(int x, int y, string text) {
3738 			p.drawText(x + originX, y + originY, 100000, 100000, text, 0);
3739 		}
3740 		void setOutlineColor(int r, int g, int b) {
3741 			p.pen = Pen(Color(r,g,b), 1);
3742 		}
3743 		void setFillColor(int r, int g, int b) {
3744 			p.fillColor = Color(r,g,b);
3745 		}
3746 	}
3747 
3748 	ScriptableVersion toArsdJsvar() {
3749 		auto sv = new ScriptableVersion;
3750 		sv.p = this.screenPainter.impl;
3751 		sv.originX = this.screenPainter.originX;
3752 		sv.originY = this.screenPainter.originY;
3753 		return sv;
3754 	}
3755 
3756 	static WidgetPainter fromJsVar(T)(T t) {
3757 		return WidgetPainter.init;
3758 	}
3759 	// done..........
3760 }
3761 
3762 
3763 struct Style {
3764 	static struct helper(string m, T) {
3765 		enum method = m;
3766 		T v;
3767 
3768 		mixin template MethodOverride(typeof(this) v) {
3769 			mixin("override typeof(v.v) "~v.method~"() { return v.v; }");
3770 		}
3771 	}
3772 
3773 	static auto opDispatch(string method, T)(T value) {
3774 		return helper!(method, T)(value);
3775 	}
3776 }
3777 
3778 /++
3779 	Implementation detail of the [ControlledBy] UDA.
3780 
3781 	History:
3782 		Added Oct 28, 2020
3783 +/
3784 struct ControlledBy_(T, Args...) {
3785 	Args args;
3786 
3787 	static if(Args.length)
3788 	this(Args args) {
3789 		this.args = args;
3790 	}
3791 
3792 	private T construct(Widget parent) {
3793 		return new T(args, parent);
3794 	}
3795 }
3796 
3797 /++
3798 	User-defined attribute you can add to struct members contrlled by [addDataControllerWidget] or [dialog] to tell which widget you want created for them.
3799 
3800 	History:
3801 		Added Oct 28, 2020
3802 +/
3803 auto ControlledBy(T, Args...)(Args args) {
3804 	return ControlledBy_!(T, Args)(args);
3805 }
3806 
3807 struct ContainerMeta {
3808 	string name;
3809 	ContainerMeta[] children;
3810 	Widget function(Widget parent) factory;
3811 
3812 	Widget instantiate(Widget parent) {
3813 		auto n = factory(parent);
3814 		n.name = name;
3815 		foreach(child; children)
3816 			child.instantiate(n);
3817 		return n;
3818 	}
3819 }
3820 
3821 /++
3822 	This is a helper for [addDataControllerWidget]. You can use it as a UDA on the type. See
3823 	http://dpldocs.info/this-week-in-d/Blog.Posted_2020_11_02.html for more information.
3824 
3825 	Please note that as of May 28, 2021, a dmd bug prevents this from compiling on module-level
3826 	structures. It works fine on structs declared inside functions though.
3827 
3828 	See: https://issues.dlang.org/show_bug.cgi?id=21984
3829 +/
3830 template Container(CArgs...) {
3831 	static if(CArgs.length && is(CArgs[0] : Widget)) {
3832 		private alias Super = CArgs[0];
3833 		private alias CArgs2 = CArgs[1 .. $];
3834 	} else {
3835 		private alias Super = Layout;
3836 		private alias CArgs2 = CArgs;
3837 	}
3838 
3839 	class Container : Super {
3840 		this(Widget parent) { super(parent); }
3841 
3842 		// just to partially support old gdc versions
3843 		version(GNU) {
3844 			static if(CArgs2.length >= 1) { enum tmp0 = CArgs2[0]; mixin typeof(tmp0).MethodOverride!(CArgs2[0]); }
3845 			static if(CArgs2.length >= 2) { enum tmp1 = CArgs2[1]; mixin typeof(tmp1).MethodOverride!(CArgs2[1]); }
3846 			static if(CArgs2.length >= 3) { enum tmp2 = CArgs2[2]; mixin typeof(tmp2).MethodOverride!(CArgs2[2]); }
3847 			static if(CArgs2.length > 3) static assert(0, "only a few overrides like this supported on your compiler version at this time");
3848 		} else mixin(q{
3849 			static foreach(Arg; CArgs2) {
3850 				mixin Arg.MethodOverride!(Arg);
3851 			}
3852 		});
3853 
3854 		static ContainerMeta opCall(string name, ContainerMeta[] children...) {
3855 			return ContainerMeta(
3856 				name,
3857 				children.dup,
3858 				function (Widget parent) { return new typeof(this)(parent); }
3859 			);
3860 		}
3861 
3862 		static ContainerMeta opCall(ContainerMeta[] children...) {
3863 			return opCall(null, children);
3864 		}
3865 	}
3866 }
3867 
3868 /++
3869 	The data controller widget is created by reflecting over the given
3870 	data type. You can use [ControlledBy] as a UDA on a struct or
3871 	just let it create things automatically.
3872 
3873 	Unlike [dialog], this uses real-time updating of the data and
3874 	you add it to another window yourself.
3875 
3876 	---
3877 		struct Test {
3878 			int x;
3879 			int y;
3880 		}
3881 
3882 		auto window = new Window();
3883 		auto dcw = new DataControllerWidget!Test(new Test, window);
3884 	---
3885 
3886 	The way it works is any public members are given a widget based
3887 	on their data type, and public methods trigger an action button
3888 	if no relevant parameters or a dialog action if it does have
3889 	parameters, similar to the [menu] facility.
3890 
3891 	If you change data programmatically, without going through the
3892 	DataControllerWidget methods, you will have to tell it something
3893 	has changed and it needs to redraw. This is done with the `invalidate`
3894 	method.
3895 
3896 	History:
3897 		Added Oct 28, 2020
3898 +/
3899 /// Group: generating_from_code
3900 class DataControllerWidget(T) : WidgetContainer {
3901 	static if(is(T == class) || is(T == interface) || is(T : const E[], E))
3902 		private alias Tref = T;
3903 	else
3904 		private alias Tref = T*;
3905 
3906 	Tref datum;
3907 
3908 	/++
3909 		See_also: [addDataControllerWidget]
3910 	+/
3911 	this(Tref datum, Widget parent) {
3912 		this.datum = datum;
3913 
3914 		Widget cp = this;
3915 
3916 		super(parent);
3917 
3918 		foreach(attr; __traits(getAttributes, T))
3919 			static if(is(typeof(attr) == ContainerMeta)) {
3920 				cp = attr.instantiate(this);
3921 			}
3922 
3923 		auto def = this.getByName("default");
3924 		if(def !is null)
3925 			cp = def;
3926 
3927 		Widget helper(string name) {
3928 			auto maybe = this.getByName(name);
3929 			if(maybe is null)
3930 				return cp;
3931 			return maybe;
3932 
3933 		}
3934 
3935 		foreach(member; __traits(allMembers, T))
3936 		static if(member != "this") // wtf https://issues.dlang.org/show_bug.cgi?id=22011
3937 		static if(is(typeof(__traits(getMember, this.datum, member))))
3938 		static if(__traits(getProtection, __traits(getMember, this.datum, member)) == "public") {
3939 			void delegate() update;
3940 
3941 			auto w = widgetFor!(__traits(getMember, T, member))(&__traits(getMember, this.datum, member), helper(member), update);
3942 
3943 			if(update)
3944 				updaters ~= update;
3945 
3946 			static if(is(typeof(__traits(getMember, this.datum, member)) == function)) {
3947 				w.addEventListener("triggered", delegate() {
3948 					makeAutomaticHandler!(__traits(getMember, this.datum, member))(&__traits(getMember, this.datum, member))();
3949 					notifyDataUpdated();
3950 				});
3951 			} else static if(is(typeof(w.isChecked) == bool)) {
3952 				w.addEventListener(EventType.change, (Event ev) {
3953 					__traits(getMember, this.datum, member) = w.isChecked;
3954 				});
3955 			} else static if(is(typeof(w.value) == string) || is(typeof(w.content) == string)) {
3956 				w.addEventListener("change", (Event e) { genericSetValue(&__traits(getMember, this.datum, member), e.stringValue); } );
3957 			} else static if(is(typeof(w.value) == int)) {
3958 				w.addEventListener("change", (Event e) { genericSetValue(&__traits(getMember, this.datum, member), e.intValue); } );
3959 			} else static if(is(typeof(w) == DropDownSelection)) {
3960 				// special case for this to kinda support enums and such. coudl be better though
3961 				w.addEventListener("change", (Event e) { genericSetValue(&__traits(getMember, this.datum, member), e.intValue); } );
3962 			} else {
3963 				//static assert(0, "unsupported type " ~ typeof(__traits(getMember, this.datum, member)).stringof ~ " " ~ typeof(w).stringof);
3964 			}
3965 		}
3966 	}
3967 
3968 	/++
3969 		If you modify the data in the structure directly, you need to call this to update the UI and propagate any change messages.
3970 
3971 		History:
3972 			Added May 28, 2021
3973 	+/
3974 	void notifyDataUpdated() {
3975 		foreach(updater; updaters)
3976 			updater();
3977 
3978 		this.emit!(ChangeEvent!void)(delegate{});
3979 	}
3980 
3981 	private Widget[string] memberWidgets;
3982 	private void delegate()[] updaters;
3983 
3984 	mixin Emits!(ChangeEvent!void);
3985 }
3986 
3987 private int saturatedSum(int[] values...) {
3988 	int sum;
3989 	foreach(value; values) {
3990 		if(value == int.max)
3991 			return int.max;
3992 		sum += value;
3993 	}
3994 	return sum;
3995 }
3996 
3997 void genericSetValue(T, W)(T* where, W what) {
3998 	import std.conv;
3999 	*where = to!T(what);
4000 	//*where = cast(T) stringToLong(what);
4001 }
4002 
4003 /++
4004 	Creates a widget for the value `tt`, which is pointed to at runtime by `valptr`, with the given parent.
4005 
4006 	The `update` delegate can be called if you change `*valptr` to reflect those changes in the widget.
4007 
4008 	Note that this creates the widget but does not attach any event handlers to it.
4009 +/
4010 private static auto widgetFor(alias tt, P)(P valptr, Widget parent, out void delegate() update) {
4011 
4012 	string displayName = __traits(identifier, tt).beautify;
4013 
4014 	static if(controlledByCount!tt == 1) {
4015 		foreach(i, attr; __traits(getAttributes, tt)) {
4016 			static if(is(typeof(attr) == ControlledBy_!(T, Args), T, Args...)) {
4017 				auto w = attr.construct(parent);
4018 				static if(__traits(compiles, w.setPosition(*valptr)))
4019 					update = () { w.setPosition(*valptr); };
4020 				else static if(__traits(compiles, w.setValue(*valptr)))
4021 					update = () { w.setValue(*valptr); };
4022 
4023 				if(update)
4024 					update();
4025 				return w;
4026 			}
4027 		}
4028 	} else static if(controlledByCount!tt == 0) {
4029 		static if(is(typeof(tt) == enum)) {
4030 			// FIXME: update
4031 			auto dds = new DropDownSelection(parent);
4032 			foreach(idx, option; __traits(allMembers, typeof(tt))) {
4033 				dds.addOption(option);
4034 				if(__traits(getMember, typeof(tt), option) == *valptr)
4035 					dds.setSelection(cast(int) idx);
4036 			}
4037 			return dds;
4038 		} else static if(is(typeof(tt) == bool)) {
4039 			auto box = new Checkbox(displayName, parent);
4040 			update = () { box.isChecked = *valptr; };
4041 			update();
4042 			return box;
4043 		} else static if(is(typeof(tt) : const long)) {
4044 			auto le = new LabeledLineEdit(displayName, parent);
4045 			update = () { le.content = toInternal!string(*valptr); };
4046 			update();
4047 			return le;
4048 		} else static if(is(typeof(tt) : const double)) {
4049 			auto le = new LabeledLineEdit(displayName, parent);
4050 			import std.conv;
4051 			update = () { le.content = to!string(*valptr); };
4052 			update();
4053 			return le;
4054 		} else static if(is(typeof(tt) : const string)) {
4055 			auto le = new LabeledLineEdit(displayName, parent);
4056 			update = () { le.content = *valptr; };
4057 			update();
4058 			return le;
4059 		} else static if(is(typeof(tt) == function)) {
4060 			auto w = new Button(displayName, parent);
4061 			return w;
4062 		} else static if(is(typeof(tt) == class) || is(typeof(tt) == interface)) {
4063 			return parent.addDataControllerWidget(tt);
4064 		} else static assert(0, typeof(tt).stringof);
4065 	} else static assert(0, "multiple controllers not yet supported");
4066 }
4067 
4068 private template controlledByCount(alias tt) {
4069 	static int helper() {
4070 		int count;
4071 		foreach(i, attr; __traits(getAttributes, tt))
4072 			static if(is(typeof(attr) == ControlledBy_!(T, Args), T, Args...))
4073 				count++;
4074 		return count;
4075 	}
4076 
4077 	enum controlledByCount = helper;
4078 }
4079 
4080 /++
4081 	Intended for UFCS action like `window.addDataControllerWidget(new MyObject());`
4082 
4083 	If you provide a `redrawOnChange` widget, it will automatically register a change event handler that calls that widget's redraw method.
4084 
4085 	History:
4086 		The `redrawOnChange` parameter was added on May 28, 2021.
4087 +/
4088 DataControllerWidget!T addDataControllerWidget(T)(Widget parent, T t, Widget redrawOnChange = null) if(is(T == class) || is(T == interface)) {
4089 	auto dcw = new DataControllerWidget!T(t, parent);
4090 	initializeDataControllerWidget(dcw, redrawOnChange);
4091 	return dcw;
4092 }
4093 
4094 /// ditto
4095 DataControllerWidget!T addDataControllerWidget(T)(Widget parent, T* t, Widget redrawOnChange = null) if(is(T == struct)) {
4096 	auto dcw = new DataControllerWidget!T(t, parent);
4097 	initializeDataControllerWidget(dcw, redrawOnChange);
4098 	return dcw;
4099 }
4100 
4101 private void initializeDataControllerWidget(Widget w, Widget redrawOnChange) {
4102 	if(redrawOnChange !is null)
4103 		w.addEventListener("change", delegate() { redrawOnChange.redraw(); });
4104 }
4105 
4106 /++
4107 	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.
4108 
4109 	History:
4110 		Finalized on June 3, 2021 for the dub v10.0 release
4111 +/
4112 struct StyleInformation {
4113 	private Widget w;
4114 	private BaseVisualTheme visualTheme;
4115 
4116 	private this(Widget w) {
4117 		this.w = w;
4118 		this.visualTheme = WidgetPainter.visualTheme;
4119 	}
4120 
4121 	/++
4122 		Forwards to [Widget.Style]
4123 
4124 		Bugs:
4125 			It is supposed to fall back to the [VisualTheme] if
4126 			the style doesn't override the default, but that is
4127 			not generally implemented. Many of them may end up
4128 			being explicit overloads instead of the generic
4129 			opDispatch fallback, like [font] is now.
4130 	+/
4131 	public @property opDispatch(string name)() {
4132 		typeof(__traits(getMember, Widget.Style.init, name)()) prop;
4133 		w.useStyleProperties((scope Widget.Style props) {
4134 		//visualTheme.useStyleProperties(w, (props) {
4135 			prop = __traits(getMember, props, name);
4136 		});
4137 		return prop;
4138 	}
4139 
4140 	/++
4141 		Returns the cached font object associated with the widget,
4142 		if overridden by the [Widget.Style|Style], or the [VisualTheme] if not.
4143 
4144 		History:
4145 			Prior to March 21, 2022 (dub v10.7), `font` went through
4146 			[opDispatch], which did not use the cache. You can now call it
4147 			repeatedly without guilt.
4148 	+/
4149 	public @property OperatingSystemFont font() {
4150 		OperatingSystemFont prop;
4151 		w.useStyleProperties((scope Widget.Style props) {
4152 			prop = props.fontCached;
4153 		});
4154 		if(prop is null)
4155 			prop = visualTheme.defaultFontCached;
4156 		return prop;
4157 	}
4158 
4159 	@property {
4160 		// Layout helpers. Currently just forwarding since I haven't made up my mind on a better way.
4161 		/** */ int paddingLeft() { return w.paddingLeft(); }
4162 		/** */ int paddingRight() { return w.paddingRight(); }
4163 		/** */ int paddingTop() { return w.paddingTop(); }
4164 		/** */ int paddingBottom() { return w.paddingBottom(); }
4165 
4166 		/** */ int marginLeft() { return w.marginLeft(); }
4167 		/** */ int marginRight() { return w.marginRight(); }
4168 		/** */ int marginTop() { return w.marginTop(); }
4169 		/** */ int marginBottom() { return w.marginBottom(); }
4170 
4171 		/** */ int maxHeight() { return w.maxHeight(); }
4172 		/** */ int minHeight() { return w.minHeight(); }
4173 
4174 		/** */ int maxWidth() { return w.maxWidth(); }
4175 		/** */ int minWidth() { return w.minWidth(); }
4176 
4177 		/** */ int flexBasisWidth() { return w.flexBasisWidth(); }
4178 		/** */ int flexBasisHeight() { return w.flexBasisHeight(); }
4179 
4180 		/** */ int heightStretchiness() { return w.heightStretchiness(); }
4181 		/** */ int widthStretchiness() { return w.widthStretchiness(); }
4182 
4183 		/** */ int heightShrinkiness() { return w.heightShrinkiness(); }
4184 		/** */ int widthShrinkiness() { return w.widthShrinkiness(); }
4185 
4186 		// Global helpers some of these are unstable.
4187 		static:
4188 		/** */ Color windowBackgroundColor() { return WidgetPainter.visualTheme.windowBackgroundColor(); }
4189 		/** */ Color widgetBackgroundColor() { return WidgetPainter.visualTheme.widgetBackgroundColor(); }
4190 		/** */ Color lightAccentColor() { return WidgetPainter.visualTheme.lightAccentColor(); }
4191 		/** */ Color darkAccentColor() { return WidgetPainter.visualTheme.darkAccentColor(); }
4192 		/** */ Color selectionForegroundColor() { return WidgetPainter.visualTheme.selectionForegroundColor(); }
4193 		/** */ Color selectionBackgroundColor() { return WidgetPainter.visualTheme.selectionBackgroundColor(); }
4194 
4195 		/** */ Color activeTabColor() { return lightAccentColor; }
4196 		/** */ Color buttonColor() { return windowBackgroundColor; }
4197 		/** */ Color depressedButtonColor() { return darkAccentColor; }
4198 		/** */ Color hoveringColor() { return lightAccentColor; }
4199 		deprecated("Use selectionForegroundColor and selectionBackgroundColor instead") Color activeListXorColor() {
4200 			auto c = WidgetPainter.visualTheme.selectionColor();
4201 			return Color(c.r ^ 255, c.g ^ 255, c.b ^ 255, c.a);
4202 		}
4203 		/** */ Color progressBarColor() { return WidgetPainter.visualTheme.selectionBackgroundColor(); }
4204 		/** */ Color activeMenuItemColor() { return WidgetPainter.visualTheme.selectionBackgroundColor(); }
4205 	}
4206 
4207 
4208 
4209 	/+
4210 
4211 	private static auto extractStyleProperty(string name)(Widget w) {
4212 		typeof(__traits(getMember, Widget.Style.init, name)()) prop;
4213 		w.useStyleProperties((props) {
4214 			prop = __traits(getMember, props, name);
4215 		});
4216 		return prop;
4217 	}
4218 
4219 	// FIXME: clear this upon a X server disconnect
4220 	private static OperatingSystemFont[string] fontCache;
4221 
4222 	T getProperty(T)(string name, lazy T default_) {
4223 		if(visualTheme !is null) {
4224 			auto str = visualTheme.getPropertyString(w, name);
4225 			if(str is null)
4226 				return default_;
4227 			static if(is(T == Color))
4228 				return Color.fromString(str);
4229 			else static if(is(T == Measurement))
4230 				return Measurement(cast(int) toInternal!int(str));
4231 			else static if(is(T == WidgetBackground))
4232 				return WidgetBackground.fromString(str);
4233 			else static if(is(T == OperatingSystemFont)) {
4234 				if(auto f = str in fontCache)
4235 					return *f;
4236 				else
4237 					return fontCache[str] = new OperatingSystemFont(str);
4238 			} else static if(is(T == FrameStyle)) {
4239 				switch(str) {
4240 					default:
4241 						return FrameStyle.none;
4242 					foreach(style; __traits(allMembers, FrameStyle))
4243 					case style:
4244 						return __traits(getMember, FrameStyle, style);
4245 				}
4246 			} else static assert(0);
4247 		} else
4248 			return default_;
4249 	}
4250 
4251 	static struct Measurement {
4252 		int value;
4253 		alias value this;
4254 	}
4255 
4256 	@property:
4257 
4258 	int paddingLeft() { return getProperty("padding-left", Measurement(w.paddingLeft())); }
4259 	int paddingRight() { return getProperty("padding-right", Measurement(w.paddingRight())); }
4260 	int paddingTop() { return getProperty("padding-top", Measurement(w.paddingTop())); }
4261 	int paddingBottom() { return getProperty("padding-bottom", Measurement(w.paddingBottom())); }
4262 
4263 	int marginLeft() { return getProperty("margin-left", Measurement(w.marginLeft())); }
4264 	int marginRight() { return getProperty("margin-right", Measurement(w.marginRight())); }
4265 	int marginTop() { return getProperty("margin-top", Measurement(w.marginTop())); }
4266 	int marginBottom() { return getProperty("margin-bottom", Measurement(w.marginBottom())); }
4267 
4268 	int maxHeight() { return getProperty("max-height", Measurement(w.maxHeight())); }
4269 	int minHeight() { return getProperty("min-height", Measurement(w.minHeight())); }
4270 
4271 	int maxWidth() { return getProperty("max-width", Measurement(w.maxWidth())); }
4272 	int minWidth() { return getProperty("min-width", Measurement(w.minWidth())); }
4273 
4274 
4275 	WidgetBackground background() { return getProperty("background", extractStyleProperty!"background"(w)); }
4276 	Color foregroundColor() { return getProperty("foreground-color", extractStyleProperty!"foregroundColor"(w)); }
4277 
4278 	OperatingSystemFont font() { return getProperty("font", extractStyleProperty!"fontCached"(w)); }
4279 
4280 	FrameStyle borderStyle() { return getProperty("border-style", extractStyleProperty!"borderStyle"(w)); }
4281 	Color borderColor() { return getProperty("border-color", extractStyleProperty!"borderColor"(w)); }
4282 
4283 	FrameStyle outlineStyle() { return getProperty("outline-style", extractStyleProperty!"outlineStyle"(w)); }
4284 	Color outlineColor() { return getProperty("outline-color", extractStyleProperty!"outlineColor"(w)); }
4285 
4286 
4287 	Color windowBackgroundColor() { return WidgetPainter.visualTheme.windowBackgroundColor(); }
4288 	Color widgetBackgroundColor() { return WidgetPainter.visualTheme.widgetBackgroundColor(); }
4289 	Color lightAccentColor() { return WidgetPainter.visualTheme.lightAccentColor(); }
4290 	Color darkAccentColor() { return WidgetPainter.visualTheme.darkAccentColor(); }
4291 
4292 	Color activeTabColor() { return lightAccentColor; }
4293 	Color buttonColor() { return windowBackgroundColor; }
4294 	Color depressedButtonColor() { return darkAccentColor; }
4295 	Color hoveringColor() { return Color(228, 228, 228); }
4296 	Color activeListXorColor() {
4297 		auto c = WidgetPainter.visualTheme.selectionColor();
4298 		return Color(c.r ^ 255, c.g ^ 255, c.b ^ 255, c.a);
4299 	}
4300 	Color progressBarColor() { return WidgetPainter.visualTheme.selectionColor(); }
4301 	Color activeMenuItemColor() { return WidgetPainter.visualTheme.selectionColor(); }
4302 	+/
4303 }
4304 
4305 
4306 
4307 // pragma(msg, __traits(classInstanceSize, Widget));
4308 
4309 /*private*/ template EventString(E) {
4310 	static if(is(typeof(E.EventString)))
4311 		enum EventString = E.EventString;
4312 	else
4313 		enum EventString = E.mangleof; // FIXME fqn? or something more user friendly
4314 }
4315 
4316 /*private*/ template EventStringIdentifier(E) {
4317 	string helper() {
4318 		auto es = EventString!E;
4319 		char[] id = new char[](es.length * 2);
4320 		size_t idx;
4321 		foreach(char ch; es) {
4322 			id[idx++] = cast(char)('a' + (ch >> 4));
4323 			id[idx++] = cast(char)('a' + (ch & 0x0f));
4324 		}
4325 		return cast(string) id;
4326 	}
4327 
4328 	enum EventStringIdentifier = helper();
4329 }
4330 
4331 
4332 template classStaticallyEmits(This, EventType) {
4333 	static if(is(This Base == super))
4334 		static if(is(Base : Widget))
4335 			enum baseEmits = classStaticallyEmits!(Base, EventType);
4336 		else
4337 			enum baseEmits = false;
4338 	else
4339 		enum baseEmits = false;
4340 
4341 	enum thisEmits = is(typeof(__traits(getMember, This, "emits_" ~ EventStringIdentifier!EventType)) == EventType[0]);
4342 
4343 	enum classStaticallyEmits = thisEmits || baseEmits;
4344 }
4345 
4346 /++
4347 	A helper to make widgets out of other native windows.
4348 
4349 	History:
4350 		Factored out of OpenGlWidget on November 5, 2021
4351 +/
4352 class NestedChildWindowWidget : Widget {
4353 	SimpleWindow win;
4354 
4355 	/++
4356 		Used on X to send focus to the appropriate child window when requested by the window manager.
4357 
4358 		Normally returns its own nested window. Can also return another child or null to revert to the parent
4359 		if you override it in a child class.
4360 
4361 		History:
4362 			Added April 2, 2022 (dub v10.8)
4363 	+/
4364 	SimpleWindow focusableWindow() {
4365 		return win;
4366 	}
4367 
4368 	///
4369 	// win = new SimpleWindow(640, 480, null, OpenGlOptions.yes, Resizability.automaticallyScaleIfPossible, WindowTypes.nestedChild, WindowFlags.normal, getParentWindow(parent));
4370 	this(SimpleWindow win, Widget parent) {
4371 		this.parentWindow = parent.parentWindow;
4372 		this.win = win;
4373 
4374 		super(parent);
4375 		windowsetup(win);
4376 	}
4377 
4378 	static protected SimpleWindow getParentWindow(Widget parent) {
4379 		assert(parent !is null);
4380 		SimpleWindow pwin = parent.parentWindow.win;
4381 
4382 		version(win32_widgets) {
4383 			HWND phwnd;
4384 			auto wtf = parent;
4385 			while(wtf) {
4386 				if(wtf.hwnd) {
4387 					phwnd = wtf.hwnd;
4388 					break;
4389 				}
4390 				wtf = wtf.parent;
4391 			}
4392 			// kinda a hack here just because the ctor below just needs a SimpleWindow wrapper....
4393 			if(phwnd)
4394 				pwin = new SimpleWindow(phwnd);
4395 		}
4396 
4397 		return pwin;
4398 	}
4399 
4400 	/++
4401 		Called upon the nested window being destroyed.
4402 		Remember the window has already been destroyed at
4403 		this point, so don't use the native handle for anything.
4404 
4405 		History:
4406 			Added April 3, 2022 (dub v10.8)
4407 	+/
4408 	protected void dispose() {
4409 
4410 	}
4411 
4412 	protected void windowsetup(SimpleWindow w) {
4413 		/*
4414 		win.onFocusChange = (bool getting) {
4415 			if(getting)
4416 				this.focus();
4417 		};
4418 		*/
4419 
4420 		/+
4421 		win.onFocusChange = (bool getting) {
4422 			if(getting) {
4423 				this.parentWindow.focusedWidget = this;
4424 				this.emit!FocusEvent();
4425 				this.emit!FocusInEvent();
4426 			} else {
4427 				this.emit!BlurEvent();
4428 				this.emit!FocusOutEvent();
4429 			}
4430 		};
4431 		+/
4432 
4433 		win.onDestroyed = () {
4434 			this.dispose();
4435 		};
4436 
4437 		version(win32_widgets) {
4438 			Widget.nativeMapping[win.hwnd] = this;
4439 			this.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(win.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc);
4440 		} else {
4441 			win.setEventHandlers(
4442 				(MouseEvent e) {
4443 					Widget p = this;
4444 					while(p ! is parentWindow) {
4445 						e.x += p.x;
4446 						e.y += p.y;
4447 						p = p.parent;
4448 					}
4449 					parentWindow.dispatchMouseEvent(e);
4450 				},
4451 				(KeyEvent e) {
4452 					//writefln("%s %x   %s", cast(void*) win, cast(uint) e.key, e.key);
4453 					parentWindow.dispatchKeyEvent(e);
4454 				},
4455 				(dchar e) {
4456 					parentWindow.dispatchCharEvent(e);
4457 				},
4458 			);
4459 		}
4460 
4461 	}
4462 
4463 	override void showing(bool s, bool recalc) {
4464 		auto cur = hidden;
4465 		win.hidden = !s;
4466 		if(cur != s && s)
4467 			redraw();
4468 	}
4469 
4470 	/// OpenGL widgets cannot have child widgets. Do not call this.
4471 	/* @disable */ final override void addChild(Widget, int) {
4472 		throw new Error("cannot add children to OpenGL widgets");
4473 	}
4474 
4475 	/// When an opengl widget is laid out, it will adjust the glViewport for you automatically.
4476 	/// Keep in mind that events like mouse coordinates are still relative to your size.
4477 	override void registerMovement() {
4478 		// writefln("%d %d %d %d", x,y,width,height);
4479 		version(win32_widgets)
4480 			auto pos = getChildPositionRelativeToParentHwnd(this);
4481 		else
4482 			auto pos = getChildPositionRelativeToParentOrigin(this);
4483 		win.moveResize(pos[0], pos[1], width, height);
4484 
4485 		registerMovementAdditionalWork();
4486 		sendResizeEvent();
4487 	}
4488 
4489 	abstract void registerMovementAdditionalWork();
4490 }
4491 
4492 /++
4493 	Nests an opengl capable window inside this window as a widget.
4494 
4495 	You may also just want to create an additional [SimpleWindow] with
4496 	[OpenGlOptions.yes] yourself.
4497 
4498 	An OpenGL widget cannot have child widgets. It will throw if you try.
4499 +/
4500 static if(OpenGlEnabled)
4501 class OpenGlWidget : NestedChildWindowWidget {
4502 
4503 	override void registerMovementAdditionalWork() {
4504 		win.setAsCurrentOpenGlContext();
4505 	}
4506 
4507 	///
4508 	this(Widget parent) {
4509 		auto win = new SimpleWindow(640, 480, null, OpenGlOptions.yes, Resizability.automaticallyScaleIfPossible, WindowTypes.nestedChild, WindowFlags.normal, getParentWindow(parent));
4510 		super(win, parent);
4511 	}
4512 
4513 	override void paint(WidgetPainter painter) {
4514 		win.setAsCurrentOpenGlContext();
4515 		glViewport(0, 0, this.width, this.height);
4516 		win.redrawOpenGlSceneNow();
4517 	}
4518 
4519 	void redrawOpenGlScene(void delegate() dg) {
4520 		win.redrawOpenGlScene = dg;
4521 	}
4522 }
4523 
4524 /++
4525 	This demo shows how to draw text in an opengl scene.
4526 +/
4527 unittest {
4528 	import arsd.minigui;
4529 	import arsd.ttf;
4530 
4531 	void main() {
4532 		auto window = new Window();
4533 
4534 		auto widget = new OpenGlWidget(window);
4535 
4536 		// old means non-shader code so compatible with glBegin etc.
4537 		// tbh I haven't implemented new one in font yet...
4538 		// anyway, declaring here, will construct soon.
4539 		OpenGlLimitedFont!(OpenGlFontGLVersion.old) glfont;
4540 
4541 		// this is a little bit awkward, calling some methods through
4542 		// the underlying SimpleWindow `win` method, and you can't do this
4543 		// on a nanovega widget due to conflicts so I should probably fix
4544 		// the api to be a bit easier. But here it will work.
4545 		//
4546 		// Alternatively, you could load the font on the first draw, inside
4547 		// the redrawOpenGlScene, and keep a flag so you don't do it every
4548 		// time. That'd be a bit easier since the lib sets up the context
4549 		// by then guaranteed.
4550 		//
4551 		// But still, I wanna show this.
4552 		widget.win.visibleForTheFirstTime = delegate {
4553 			// must set the opengl context
4554 			widget.win.setAsCurrentOpenGlContext();
4555 
4556 			// if you were doing a OpenGL 3+ shader, this
4557 			// gets especially important to do in order. With
4558 			// old-style opengl, I think you can even do it
4559 			// in main(), but meh, let's show it more correctly.
4560 
4561 			// Anyway, now it is time to load the font from the
4562 			// OS (you can alternatively load one from a .ttf file
4563 			// you bundle with the application), then load the
4564 			// font into texture for drawing.
4565 
4566 			auto osfont = new OperatingSystemFont("DejaVu Sans", 18);
4567 
4568 			assert(!osfont.isNull()); // make sure it actually loaded
4569 
4570 			// using typeof to avoid repeating the long name lol
4571 			glfont = new typeof(glfont)(
4572 				// get the raw data from the font for loading in here
4573 				// since it doesn't use the OS function to draw the
4574 				// text, we gotta treat it more as a file than as
4575 				// a drawing api.
4576 				osfont.getTtfBytes(),
4577 				18, // need to respecify size since opengl world is different coordinate system
4578 
4579 				// these last two numbers are why it is called
4580 				// "Limited" font. It only loads the characters
4581 				// in the given range, since the texture atlas
4582 				// it references is all a big image generated ahead
4583 				// of time. You could maybe do the whole thing but
4584 				// idk how much memory that is.
4585 				//
4586 				// But here, 0-128 represents the ASCII range, so
4587 				// good enough for most English things, numeric labels,
4588 				// etc.
4589 				0,
4590 				128
4591 			);
4592 		};
4593 
4594 		widget.redrawOpenGlScene = () {
4595 			// now we can use the glfont's drawString function
4596 
4597 			// first some opengl setup. You can do this in one place
4598 			// on window first visible too in many cases, just showing
4599 			// here cuz it is easier for me.
4600 
4601 			// gonna need some alpha blending or it just looks awful
4602 			glEnable(GL_BLEND);
4603 			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
4604 			glClearColor(0,0,0,0);
4605 			glDepthFunc(GL_LEQUAL);
4606 
4607 			// Also need to enable 2d textures, since it draws the
4608 			// font characters as images baked in
4609 			glMatrixMode(GL_MODELVIEW);
4610 			glLoadIdentity();
4611 			glDisable(GL_DEPTH_TEST);
4612 			glEnable(GL_TEXTURE_2D);
4613 
4614 			// the orthographic matrix is best for 2d things like text
4615 			// so let's set that up. This matrix makes the coordinates
4616 			// in the opengl scene be one-to-one with the actual pixels
4617 			// on screen. (Not necessarily best, you may wish to scale
4618 			// things, but it does help keep fonts looking normal.)
4619 			glMatrixMode(GL_PROJECTION);
4620 			glLoadIdentity();
4621 			glOrtho(0, widget.width, widget.height, 0, 0, 1);
4622 
4623 			// you can do other glScale, glRotate, glTranslate, etc
4624 			// to the matrix here of course if you want.
4625 
4626 			// note the x,y coordinates here are for the text baseline
4627 			// NOT the upper-left corner. The baseline is like the line
4628 			// in the notebook you write on. Most the letters are actually
4629 			// above it, but some, like p and q, dip a bit below it.
4630 			//
4631 			// So if you're used to the upper left coordinate like the
4632 			// rest of simpledisplay/minigui usually do, do the
4633 			// y + glfont.ascent to bring it down a little. So this
4634 			// example puts the string in the upper left of the window.
4635 			glfont.drawString(0, 0 + glfont.ascent, "Hello!!", Color.green);
4636 
4637 			// re color btw: the function sets a solid color internally,
4638 			// but you actually COULD do your own thing for rainbow effects
4639 			// and the sort if you wanted too, by pulling its guts out.
4640 			// Just view its source for an idea of how it actually draws:
4641 			// http://arsd-official.dpldocs.info/source/arsd.ttf.d.html#L332
4642 
4643 			// it gets a bit complicated with the character positioning,
4644 			// but the opengl parts are fairly simple: bind a texture,
4645 			// set the color, draw a quad for each letter.
4646 
4647 
4648 			// the last optional argument there btw is a bounding box
4649 			// it will/ use to word wrap and return an object you can
4650 			// use to implement scrolling or pagination; it tells how
4651 			// much of the string didn't fit in the box. But for simple
4652 			// labels we can just ignore that.
4653 
4654 
4655 			// I'd suggest drawing text as the last step, after you
4656 			// do your other drawing. You might use the push/pop matrix
4657 			// stuff to keep your place. You, in theory, should be able
4658 			// to do text in a 3d space but I've never actually tried
4659 			// that....
4660 		};
4661 
4662 		window.loop();
4663 	}
4664 }
4665 
4666 version(custom_widgets)
4667 	private alias ListWidgetBase = ScrollableWidget;
4668 else
4669 	private alias ListWidgetBase = Widget;
4670 
4671 /++
4672 	A list widget contains a list of strings that the user can examine and select.
4673 
4674 
4675 	In the future, items in the list may be possible to be more than just strings.
4676 
4677 	See_Also:
4678 		[TableView]
4679 +/
4680 class ListWidget : ListWidgetBase {
4681 	/// 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.
4682 	mixin Emits!(ChangeEvent!void);
4683 
4684 	static struct Option {
4685 		string label;
4686 		bool selected;
4687 		void* tag;
4688 	}
4689 
4690 	/++
4691 		Sets the current selection to the `y`th item in the list. Will emit [ChangeEvent] when complete.
4692 	+/
4693 	void setSelection(int y) {
4694 		if(!multiSelect)
4695 			foreach(ref opt; options)
4696 				opt.selected = false;
4697 		if(y >= 0 && y < options.length)
4698 			options[y].selected = !options[y].selected;
4699 
4700 		this.emit!(ChangeEvent!void)(delegate {});
4701 
4702 		version(custom_widgets)
4703 			redraw();
4704 	}
4705 
4706 	/++
4707 		Gets the index of the selected item. In case of multi select, the index of the first selected item is returned.
4708 		Returns -1 if nothing is selected.
4709 	+/
4710 	int getSelection()
4711 	{
4712 		foreach(i, opt; options) {
4713 			if (opt.selected)
4714 				return cast(int) i;
4715 		}
4716 		return -1;
4717 	}
4718 
4719 	version(custom_widgets)
4720 	override void defaultEventHandler_click(ClickEvent event) {
4721 		this.focus();
4722 		if(event.button == MouseButton.left) {
4723 			auto y = (event.clientY - 4) / defaultLineHeight;
4724 			if(y >= 0 && y < options.length) {
4725 				setSelection(y);
4726 			}
4727 		}
4728 		super.defaultEventHandler_click(event);
4729 	}
4730 
4731 	this(Widget parent) {
4732 		tabStop = false;
4733 		super(parent);
4734 		version(win32_widgets)
4735 			createWin32Window(this, WC_LISTBOX, "",
4736 				0|WS_CHILD|WS_VISIBLE|LBS_NOTIFY, 0);
4737 	}
4738 
4739 	version(win32_widgets)
4740 	override void handleWmCommand(ushort code, ushort id) {
4741 		switch(code) {
4742 			case LBN_SELCHANGE:
4743 				auto sel = SendMessageW(hwnd, LB_GETCURSEL, 0, 0);
4744 				setSelection(cast(int) sel);
4745 			break;
4746 			default:
4747 		}
4748 	}
4749 
4750 
4751 	version(custom_widgets)
4752 	override void paintFrameAndBackground(WidgetPainter painter) {
4753 		draw3dFrame(this, painter, FrameStyle.sunk, painter.visualTheme.widgetBackgroundColor);
4754 	}
4755 
4756 	version(custom_widgets)
4757 	override void paint(WidgetPainter painter) {
4758 		auto cs = getComputedStyle();
4759 		auto pos = Point(4, 4);
4760 		foreach(idx, option; options) {
4761 			painter.fillColor = painter.visualTheme.widgetBackgroundColor;
4762 			painter.outlineColor = painter.visualTheme.widgetBackgroundColor;
4763 			painter.drawRectangle(pos, width - 8, defaultLineHeight);
4764 			if(option.selected) {
4765 				//painter.rasterOp = RasterOp.xor;
4766 				painter.outlineColor = cs.selectionForegroundColor;
4767 				painter.fillColor = cs.selectionBackgroundColor;
4768 				painter.drawRectangle(pos, width - 8, defaultLineHeight);
4769 				//painter.rasterOp = RasterOp.normal;
4770 			}
4771 			painter.outlineColor = option.selected ? cs.selectionForegroundColor : cs.foregroundColor;
4772 			painter.drawText(pos, option.label);
4773 			pos.y += defaultLineHeight;
4774 		}
4775 	}
4776 
4777 	static class Style : Widget.Style {
4778 		override WidgetBackground background() {
4779 			return WidgetBackground(WidgetPainter.visualTheme.widgetBackgroundColor);
4780 		}
4781 	}
4782 	mixin OverrideStyle!Style;
4783 	//mixin Padding!q{2};
4784 
4785 	void addOption(string text, void* tag = null) {
4786 		options ~= Option(text, false, tag);
4787 		version(win32_widgets) {
4788 			WCharzBuffer buffer = WCharzBuffer(text);
4789 			SendMessageW(hwnd, LB_ADDSTRING, 0, cast(LPARAM) buffer.ptr);
4790 		}
4791 		version(custom_widgets) {
4792 			setContentSize(width, cast(int) (options.length * defaultLineHeight));
4793 			redraw();
4794 		}
4795 	}
4796 
4797 	void clear() {
4798 		options = null;
4799 		version(win32_widgets) {
4800 			while(SendMessageW(hwnd, LB_DELETESTRING, 0, 0) > 0)
4801 				{}
4802 
4803 		} else version(custom_widgets) {
4804 			scrollTo(Point(0, 0));
4805 			redraw();
4806 		}
4807 	}
4808 
4809 	Option[] options;
4810 	version(win32_widgets)
4811 		enum multiSelect = false; /// not implemented yet
4812 	else
4813 		bool multiSelect;
4814 
4815 	override int heightStretchiness() { return 6; }
4816 }
4817 
4818 
4819 
4820 /// For [ScrollableWidget], determines when to show the scroll bar to the user.
4821 enum ScrollBarShowPolicy {
4822 	automatic, /// automatically show the scroll bar if it is necessary
4823 	never, /// never show the scroll bar (scrolling must be done programmatically)
4824 	always /// always show the scroll bar, even if it is disabled
4825 }
4826 
4827 /++
4828 	A widget that tries (with, at best, limited success) to offer scrolling that is transparent to the inner.
4829 
4830 	It isn't very good and will very likely be removed. Try [ScrollMessageWidget] or [ScrollableContainerWidget] instead for new code.
4831 +/
4832 // FIXME ScrollBarShowPolicy
4833 // FIXME: use the ScrollMessageWidget in here now that it exists
4834 class ScrollableWidget : Widget {
4835 	// FIXME: make line size configurable
4836 	// FIXME: add keyboard controls
4837 	version(win32_widgets) {
4838 		override int hookedWndProc(UINT msg, WPARAM wParam, LPARAM lParam) {
4839 			if(msg == WM_VSCROLL || msg == WM_HSCROLL) {
4840 				auto pos = HIWORD(wParam);
4841 				auto m = LOWORD(wParam);
4842 
4843 				// FIXME: I can reintroduce the
4844 				// scroll bars now by using this
4845 				// in the top-level window handler
4846 				// to forward comamnds
4847 				auto scrollbarHwnd = lParam;
4848 				switch(m) {
4849 					case SB_BOTTOM:
4850 						if(msg == WM_HSCROLL)
4851 							horizontalScrollTo(contentWidth_);
4852 						else
4853 							verticalScrollTo(contentHeight_);
4854 					break;
4855 					case SB_TOP:
4856 						if(msg == WM_HSCROLL)
4857 							horizontalScrollTo(0);
4858 						else
4859 							verticalScrollTo(0);
4860 					break;
4861 					case SB_ENDSCROLL:
4862 						// idk
4863 					break;
4864 					case SB_LINEDOWN:
4865 						if(msg == WM_HSCROLL)
4866 							horizontalScroll(scaleWithDpi(16));
4867 						else
4868 							verticalScroll(scaleWithDpi(16));
4869 					break;
4870 					case SB_LINEUP:
4871 						if(msg == WM_HSCROLL)
4872 							horizontalScroll(scaleWithDpi(-16));
4873 						else
4874 							verticalScroll(scaleWithDpi(-16));
4875 					break;
4876 					case SB_PAGEDOWN:
4877 						if(msg == WM_HSCROLL)
4878 							horizontalScroll(scaleWithDpi(100));
4879 						else
4880 							verticalScroll(scaleWithDpi(100));
4881 					break;
4882 					case SB_PAGEUP:
4883 						if(msg == WM_HSCROLL)
4884 							horizontalScroll(scaleWithDpi(-100));
4885 						else
4886 							verticalScroll(scaleWithDpi(-100));
4887 					break;
4888 					case SB_THUMBPOSITION:
4889 					case SB_THUMBTRACK:
4890 						if(msg == WM_HSCROLL)
4891 							horizontalScrollTo(pos);
4892 						else
4893 							verticalScrollTo(pos);
4894 
4895 						if(m == SB_THUMBTRACK) {
4896 							// the event loop doesn't seem to carry on with a requested redraw..
4897 							// so we request it to get our dirty bit set...
4898 							redraw();
4899 
4900 							// then we need to immediately actually redraw it too for instant feedback to user
4901 
4902 							SimpleWindow.processAllCustomEvents();
4903 							//if(parentWindow)
4904 								//parentWindow.actualRedraw();
4905 						}
4906 					break;
4907 					default:
4908 				}
4909 			}
4910 			return super.hookedWndProc(msg, wParam, lParam);
4911 		}
4912 	}
4913 	///
4914 	this(Widget parent) {
4915 		this.parentWindow = parent.parentWindow;
4916 
4917 		version(win32_widgets) {
4918 			createWin32Window(this, Win32Class!"arsd_minigui_ScrollableWidget"w, "",
4919 				0|WS_CHILD|WS_VISIBLE|WS_HSCROLL|WS_VSCROLL, 0);
4920 			super(parent);
4921 		} else version(custom_widgets) {
4922 			outerContainer = new InternalScrollableContainerWidget(this, parent);
4923 			super(outerContainer);
4924 		} else static assert(0);
4925 	}
4926 
4927 	version(custom_widgets)
4928 		InternalScrollableContainerWidget outerContainer;
4929 
4930 	override void defaultEventHandler_click(ClickEvent event) {
4931 		if(event.button == MouseButton.wheelUp)
4932 			verticalScroll(scaleWithDpi(-16));
4933 		if(event.button == MouseButton.wheelDown)
4934 			verticalScroll(scaleWithDpi(16));
4935 		super.defaultEventHandler_click(event);
4936 	}
4937 
4938 	override void defaultEventHandler_keydown(KeyDownEvent event) {
4939 		switch(event.key) {
4940 			case Key.Left:
4941 				horizontalScroll(scaleWithDpi(-16));
4942 			break;
4943 			case Key.Right:
4944 				horizontalScroll(scaleWithDpi(16));
4945 			break;
4946 			case Key.Up:
4947 				verticalScroll(scaleWithDpi(-16));
4948 			break;
4949 			case Key.Down:
4950 				verticalScroll(scaleWithDpi(16));
4951 			break;
4952 			case Key.Home:
4953 				verticalScrollTo(0);
4954 			break;
4955 			case Key.End:
4956 				verticalScrollTo(contentHeight);
4957 			break;
4958 			case Key.PageUp:
4959 				verticalScroll(scaleWithDpi(-160));
4960 			break;
4961 			case Key.PageDown:
4962 				verticalScroll(scaleWithDpi(160));
4963 			break;
4964 			default:
4965 		}
4966 		super.defaultEventHandler_keydown(event);
4967 	}
4968 
4969 
4970 	version(win32_widgets)
4971 	override void recomputeChildLayout() {
4972 		super.recomputeChildLayout();
4973 		SCROLLINFO info;
4974 		info.cbSize = info.sizeof;
4975 		info.nPage = viewportHeight;
4976 		info.fMask = SIF_PAGE | SIF_RANGE;
4977 		info.nMin = 0;
4978 		info.nMax = contentHeight_;
4979 		SetScrollInfo(hwnd, SB_VERT, &info, true);
4980 
4981 		info.cbSize = info.sizeof;
4982 		info.nPage = viewportWidth;
4983 		info.fMask = SIF_PAGE | SIF_RANGE;
4984 		info.nMin = 0;
4985 		info.nMax = contentWidth_;
4986 		SetScrollInfo(hwnd, SB_HORZ, &info, true);
4987 	}
4988 
4989 	/*
4990 		Scrolling
4991 		------------
4992 
4993 		You are assigned a width and a height by the layout engine, which
4994 		is your viewport box. However, you may draw more than that by setting
4995 		a contentWidth and contentHeight.
4996 
4997 		If these can be contained by the viewport, no scrollbar is displayed.
4998 		If they cannot fit though, it will automatically show scroll as necessary.
4999 
5000 		If contentWidth == 0, no horizontal scrolling is performed. If contentHeight
5001 		is zero, no vertical scrolling is performed.
5002 
5003 		If scrolling is necessary, the lib will automatically work with the bars.
5004 		When you redraw, the origin and clipping info in the painter is set so if
5005 		you just draw everything, it will work, but you can be more efficient by checking
5006 		the viewportWidth, viewportHeight, and scrollOrigin members.
5007 	*/
5008 
5009 	///
5010 	final @property int viewportWidth() {
5011 		return width - (showingVerticalScroll ? scaleWithDpi(16) : 0);
5012 	}
5013 	///
5014 	final @property int viewportHeight() {
5015 		return height - (showingHorizontalScroll ? scaleWithDpi(16) : 0);
5016 	}
5017 
5018 	// FIXME property
5019 	Point scrollOrigin_;
5020 
5021 	///
5022 	final const(Point) scrollOrigin() {
5023 		return scrollOrigin_;
5024 	}
5025 
5026 	// the user sets these two
5027 	private int contentWidth_ = 0;
5028 	private int contentHeight_ = 0;
5029 
5030 	///
5031 	int contentWidth() { return contentWidth_; }
5032 	///
5033 	int contentHeight() { return contentHeight_; }
5034 
5035 	///
5036 	void setContentSize(int width, int height) {
5037 		contentWidth_ = width;
5038 		contentHeight_ = height;
5039 
5040 		version(custom_widgets) {
5041 			if(showingVerticalScroll || showingHorizontalScroll) {
5042 				outerContainer.recomputeChildLayout();
5043 			}
5044 
5045 			if(showingVerticalScroll())
5046 				outerContainer.verticalScrollBar.redraw();
5047 			if(showingHorizontalScroll())
5048 				outerContainer.horizontalScrollBar.redraw();
5049 		} else version(win32_widgets) {
5050 			recomputeChildLayout();
5051 		} else static assert(0);
5052 	}
5053 
5054 	///
5055 	void verticalScroll(int delta) {
5056 		verticalScrollTo(scrollOrigin.y + delta);
5057 	}
5058 	///
5059 	void verticalScrollTo(int pos) {
5060 		scrollOrigin_.y = pos;
5061 		if(pos == int.max || (scrollOrigin_.y + viewportHeight > contentHeight))
5062 			scrollOrigin_.y = contentHeight - viewportHeight;
5063 
5064 		if(scrollOrigin_.y < 0)
5065 			scrollOrigin_.y = 0;
5066 
5067 		version(win32_widgets) {
5068 			SCROLLINFO info;
5069 			info.cbSize = info.sizeof;
5070 			info.fMask = SIF_POS;
5071 			info.nPos = scrollOrigin_.y;
5072 			SetScrollInfo(hwnd, SB_VERT, &info, true);
5073 		} else version(custom_widgets) {
5074 			outerContainer.verticalScrollBar.setPosition(scrollOrigin_.y);
5075 		} else static assert(0);
5076 
5077 		redraw();
5078 	}
5079 
5080 	///
5081 	void horizontalScroll(int delta) {
5082 		horizontalScrollTo(scrollOrigin.x + delta);
5083 	}
5084 	///
5085 	void horizontalScrollTo(int pos) {
5086 		scrollOrigin_.x = pos;
5087 		if(pos == int.max || (scrollOrigin_.x + viewportWidth > contentWidth))
5088 			scrollOrigin_.x = contentWidth - viewportWidth;
5089 
5090 		if(scrollOrigin_.x < 0)
5091 			scrollOrigin_.x = 0;
5092 
5093 		version(win32_widgets) {
5094 			SCROLLINFO info;
5095 			info.cbSize = info.sizeof;
5096 			info.fMask = SIF_POS;
5097 			info.nPos = scrollOrigin_.x;
5098 			SetScrollInfo(hwnd, SB_HORZ, &info, true);
5099 		} else version(custom_widgets) {
5100 			outerContainer.horizontalScrollBar.setPosition(scrollOrigin_.x);
5101 		} else static assert(0);
5102 
5103 		redraw();
5104 	}
5105 	///
5106 	void scrollTo(Point p) {
5107 		verticalScrollTo(p.y);
5108 		horizontalScrollTo(p.x);
5109 	}
5110 
5111 	///
5112 	void ensureVisibleInScroll(Point p) {
5113 		auto rect = viewportRectangle();
5114 		if(rect.contains(p))
5115 			return;
5116 		if(p.x < rect.left)
5117 			horizontalScroll(p.x - rect.left);
5118 		else if(p.x > rect.right)
5119 			horizontalScroll(p.x - rect.right);
5120 
5121 		if(p.y < rect.top)
5122 			verticalScroll(p.y - rect.top);
5123 		else if(p.y > rect.bottom)
5124 			verticalScroll(p.y - rect.bottom);
5125 	}
5126 
5127 	///
5128 	void ensureVisibleInScroll(Rectangle rect) {
5129 		ensureVisibleInScroll(rect.upperLeft);
5130 		ensureVisibleInScroll(rect.lowerRight);
5131 	}
5132 
5133 	///
5134 	Rectangle viewportRectangle() {
5135 		return Rectangle(scrollOrigin, Size(viewportWidth, viewportHeight));
5136 	}
5137 
5138 	///
5139 	bool showingHorizontalScroll() {
5140 		return contentWidth > width;
5141 	}
5142 	///
5143 	bool showingVerticalScroll() {
5144 		return contentHeight > height;
5145 	}
5146 
5147 	/// This is called before the ordinary paint delegate,
5148 	/// giving you a chance to draw the window frame, etc,
5149 	/// before the scroll clip takes effect
5150 	void paintFrameAndBackground(WidgetPainter painter) {
5151 		version(win32_widgets) {
5152 			auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
5153 			auto p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
5154 			// since the pen is null, to fill the whole space, we need the +1 on both.
5155 			gdi.Rectangle(painter.impl.hdc, 0, 0, this.width + 1, this.height + 1);
5156 			SelectObject(painter.impl.hdc, p);
5157 			SelectObject(painter.impl.hdc, b);
5158 		}
5159 
5160 	}
5161 
5162 	// make space for the scroll bar, and that's it.
5163 	final override int paddingRight() { return scaleWithDpi(16); }
5164 	final override int paddingBottom() { return scaleWithDpi(16); }
5165 
5166 	/*
5167 		END SCROLLING
5168 	*/
5169 
5170 	override WidgetPainter draw() {
5171 		int x = this.x, y = this.y;
5172 		auto parent = this.parent;
5173 		while(parent) {
5174 			x += parent.x;
5175 			y += parent.y;
5176 			parent = parent.parent;
5177 		}
5178 
5179 		//version(win32_widgets) {
5180 			//auto painter = simpleWindowWrappingHwnd ? simpleWindowWrappingHwnd.draw(true) : parentWindow.win.draw(true);
5181 		//} else {
5182 			auto painter = parentWindow.win.draw(true);
5183 		//}
5184 		painter.originX = x;
5185 		painter.originY = y;
5186 
5187 		painter.originX = painter.originX - scrollOrigin.x;
5188 		painter.originY = painter.originY - scrollOrigin.y;
5189 		painter.setClipRectangle(scrollOrigin, viewportWidth(), viewportHeight());
5190 
5191 		return WidgetPainter(painter, this);
5192 	}
5193 
5194 	mixin ScrollableChildren;
5195 }
5196 
5197 // you need to have a Point scrollOrigin in the class somewhere
5198 // and a paintFrameAndBackground
5199 private mixin template ScrollableChildren() {
5200 	override protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force, bool invalidate) {
5201 		if(hidden)
5202 			return;
5203 
5204 		//version(win32_widgets)
5205 			//painter = simpleWindowWrappingHwnd ? simpleWindowWrappingHwnd.draw(true) : parentWindow.win.draw(true);
5206 
5207 		painter.originX = lox + x;
5208 		painter.originY = loy + y;
5209 
5210 		bool actuallyPainted = false;
5211 
5212 		const clip = containment.intersectionOf(Rectangle(Point(lox + x, loy + y), Size(width, height)));
5213 		if(clip == Rectangle.init)
5214 			return;
5215 
5216 		if(force || redrawRequested) {
5217 			//painter.setClipRectangle(scrollOrigin, width, height);
5218 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
5219 			paintFrameAndBackground(painter);
5220 		}
5221 
5222 		painter.originX = painter.originX - scrollOrigin.x;
5223 		painter.originY = painter.originY - scrollOrigin.y;
5224 		if(force || redrawRequested) {
5225 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY) + Point(2, 2) /* border */, clip.width - 4, clip.height - 4);
5226 			//painter.setClipRectangle(scrollOrigin + Point(2, 2) /* border */, width - 4, height - 4);
5227 
5228 			//erase(painter); // we paintFrameAndBackground above so no need
5229 			if(painter.visualTheme)
5230 				painter.visualTheme.doPaint(this, painter);
5231 			else
5232 				paint(painter);
5233 
5234 			if(invalidate) {
5235 				painter.invalidateRect(Rectangle(Point(clip.upperLeft.x - painter.originX, clip.upperRight.y - painter.originY), Size(clip.width, clip.height)));
5236 				// children are contained inside this, so no need to do extra work
5237 				invalidate = false;
5238 			}
5239 
5240 
5241 			actuallyPainted = true;
5242 			redrawRequested = false;
5243 		}
5244 		foreach(child; children) {
5245 			if(cast(FixedPosition) child)
5246 				child.privatePaint(painter, painter.originX + scrollOrigin.x, painter.originY + scrollOrigin.y, clip, actuallyPainted, invalidate);
5247 			else
5248 				child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted, invalidate);
5249 		}
5250 	}
5251 }
5252 
5253 private class InternalScrollableContainerInsideWidget : ContainerWidget {
5254 	ScrollableContainerWidget scw;
5255 
5256 	this(ScrollableContainerWidget parent) {
5257 		scw = parent;
5258 		super(parent);
5259 	}
5260 
5261 	version(custom_widgets)
5262 	override protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force, bool invalidate) {
5263 		if(hidden)
5264 			return;
5265 
5266 		bool actuallyPainted = false;
5267 
5268 		auto scrollOrigin = Point(scw.scrollX_, scw.scrollY_);
5269 
5270 		const clip = containment.intersectionOf(Rectangle(Point(lox + x, loy + y), Size(width + scw.scrollX_, height + scw.scrollY_)));
5271 		if(clip == Rectangle.init)
5272 			return;
5273 
5274 		painter.originX = lox + x - scrollOrigin.x;
5275 		painter.originY = loy + y - scrollOrigin.y;
5276 		if(force || redrawRequested) {
5277 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
5278 
5279 			erase(painter);
5280 			if(painter.visualTheme)
5281 				painter.visualTheme.doPaint(this, painter);
5282 			else
5283 				paint(painter);
5284 
5285 			if(invalidate) {
5286 				painter.invalidateRect(Rectangle(Point(clip.upperLeft.x - painter.originX, clip.upperRight.y - painter.originY), Size(clip.width, clip.height)));
5287 				// children are contained inside this, so no need to do extra work
5288 				invalidate = false;
5289 			}
5290 
5291 			actuallyPainted = true;
5292 			redrawRequested = false;
5293 		}
5294 		foreach(child; children) {
5295 			if(cast(FixedPosition) child)
5296 				child.privatePaint(painter, painter.originX + scrollOrigin.x, painter.originY + scrollOrigin.y, clip, actuallyPainted, invalidate);
5297 			else
5298 				child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted, invalidate);
5299 		}
5300 	}
5301 
5302 	version(custom_widgets)
5303 	override protected void addScrollPosition(ref int x, ref int y) {
5304 		x += scw.scrollX_;
5305 		y += scw.scrollY_;
5306 	}
5307 }
5308 
5309 /++
5310 	A widget meant to contain other widgets that may need to scroll.
5311 
5312 	Currently buggy.
5313 
5314 	History:
5315 		Added July 1, 2021 (dub v10.2)
5316 
5317 		On January 3, 2022, I tried to use it in a few other cases
5318 		and found it only worked well in the original test case. Since
5319 		it still sucks, I think I'm going to rewrite it again.
5320 +/
5321 class ScrollableContainerWidget : ContainerWidget {
5322 	///
5323 	this(Widget parent) {
5324 		super(parent);
5325 
5326 		container = new InternalScrollableContainerInsideWidget(this);
5327 		hsb = new HorizontalScrollbar(this);
5328 		vsb = new VerticalScrollbar(this);
5329 
5330 		tabStop = false;
5331 		container.tabStop = false;
5332 		magic = true;
5333 
5334 
5335 		vsb.addEventListener("scrolltonextline", () {
5336 			scrollBy(0, scaleWithDpi(16));
5337 		});
5338 		vsb.addEventListener("scrolltopreviousline", () {
5339 			scrollBy(0,scaleWithDpi( -16));
5340 		});
5341 		vsb.addEventListener("scrolltonextpage", () {
5342 			scrollBy(0, container.height);
5343 		});
5344 		vsb.addEventListener("scrolltopreviouspage", () {
5345 			scrollBy(0, -container.height);
5346 		});
5347 		vsb.addEventListener((scope ScrollToPositionEvent spe) {
5348 			scrollTo(scrollX_, spe.value);
5349 		});
5350 
5351 		this.addEventListener(delegate (scope ClickEvent e) {
5352 			if(e.button == MouseButton.wheelUp) {
5353 				if(!e.defaultPrevented)
5354 					scrollBy(0, scaleWithDpi(-16));
5355 				e.stopPropagation();
5356 			} else if(e.button == MouseButton.wheelDown) {
5357 				if(!e.defaultPrevented)
5358 					scrollBy(0, scaleWithDpi(16));
5359 				e.stopPropagation();
5360 			}
5361 		});
5362 	}
5363 
5364 	/+
5365 	override void defaultEventHandler_click(ClickEvent e) {
5366 	}
5367 	+/
5368 
5369 	override void removeAllChildren() {
5370 		container.removeAllChildren();
5371 	}
5372 
5373 	void scrollTo(int x, int y) {
5374 		scrollBy(x - scrollX_, y - scrollY_);
5375 	}
5376 
5377 	void scrollBy(int x, int y) {
5378 		auto ox = scrollX_;
5379 		auto oy = scrollY_;
5380 
5381 		auto nx = ox + x;
5382 		auto ny = oy + y;
5383 
5384 		if(nx < 0)
5385 			nx = 0;
5386 		if(ny < 0)
5387 			ny = 0;
5388 
5389 		auto maxX = hsb.max - container.width;
5390 		if(maxX < 0) maxX = 0;
5391 		auto maxY = vsb.max - container.height;
5392 		if(maxY < 0) maxY = 0;
5393 
5394 		if(nx > maxX)
5395 			nx = maxX;
5396 		if(ny > maxY)
5397 			ny = maxY;
5398 
5399 		auto dx = nx - ox;
5400 		auto dy = ny - oy;
5401 
5402 		if(dx || dy) {
5403 			version(win32_widgets)
5404 				ScrollWindowEx(container.hwnd, -dx, -dy, null, null, null, null, SW_SCROLLCHILDREN | SW_INVALIDATE | SW_ERASE);
5405 			else {
5406 				redraw();
5407 			}
5408 
5409 			hsb.setPosition = nx;
5410 			vsb.setPosition = ny;
5411 
5412 			scrollX_ = nx;
5413 			scrollY_ = ny;
5414 		}
5415 	}
5416 
5417 	private int scrollX_;
5418 	private int scrollY_;
5419 
5420 	void setTotalArea(int width, int height) {
5421 		hsb.setMax(width);
5422 		vsb.setMax(height);
5423 	}
5424 
5425 	///
5426 	void setViewableArea(int width, int height) {
5427 		hsb.setViewableArea(width);
5428 		vsb.setViewableArea(height);
5429 	}
5430 
5431 	private bool magic;
5432 	override void addChild(Widget w, int position = int.max) {
5433 		if(magic)
5434 			container.addChild(w, position);
5435 		else
5436 			super.addChild(w, position);
5437 	}
5438 
5439 	override void recomputeChildLayout() {
5440 		if(hsb is null || vsb is null || container is null) return;
5441 
5442 		/+
5443 		writeln(x, " ", y , " ", width, " ", height);
5444 		writeln(this.ContainerWidget.minWidth(), "x", this.ContainerWidget.minHeight());
5445 		+/
5446 
5447 		registerMovement();
5448 
5449 		hsb.height = scaleWithDpi(16); // FIXME? are tese 16s sane?
5450 		hsb.x = 0;
5451 		hsb.y = this.height - hsb.height;
5452 		hsb.width = this.width - scaleWithDpi(16);
5453 		hsb.recomputeChildLayout();
5454 
5455 		vsb.width = scaleWithDpi(16); // FIXME?
5456 		vsb.x = this.width - vsb.width;
5457 		vsb.y = 0;
5458 		vsb.height = this.height - scaleWithDpi(16);
5459 		vsb.recomputeChildLayout();
5460 
5461 		container.x = 0;
5462 		container.y = 0;
5463 		container.width = this.width - vsb.width;
5464 		container.height = this.height - hsb.height;
5465 		container.recomputeChildLayout();
5466 
5467 		scrollX_ = 0;
5468 		scrollY_ = 0;
5469 
5470 		hsb.setPosition(0);
5471 		vsb.setPosition(0);
5472 
5473 		int mw, mh;
5474 		Widget c = container;
5475 		// FIXME: hack here to handle a layout inside...
5476 		if(c.children.length == 1 && cast(Layout) c.children[0])
5477 			c = c.children[0];
5478 		foreach(child; c.children) {
5479 			auto w = child.x + child.width;
5480 			auto h = child.y + child.height;
5481 
5482 			if(w > mw) mw = w;
5483 			if(h > mh) mh = h;
5484 		}
5485 
5486 		setTotalArea(mw, mh);
5487 		setViewableArea(width, height);
5488 	}
5489 
5490 	override int minHeight() { return scaleWithDpi(64); }
5491 
5492 	HorizontalScrollbar hsb;
5493 	VerticalScrollbar vsb;
5494 	ContainerWidget container;
5495 }
5496 
5497 
5498 version(custom_widgets)
5499 private class InternalScrollableContainerWidget : Widget {
5500 
5501 	ScrollableWidget sw;
5502 
5503 	VerticalScrollbar verticalScrollBar;
5504 	HorizontalScrollbar horizontalScrollBar;
5505 
5506 	this(ScrollableWidget sw, Widget parent) {
5507 		this.sw = sw;
5508 
5509 		this.tabStop = false;
5510 
5511 		horizontalScrollBar = new HorizontalScrollbar(this);
5512 		verticalScrollBar = new VerticalScrollbar(this);
5513 
5514 		horizontalScrollBar.showing_ = false;
5515 		verticalScrollBar.showing_ = false;
5516 
5517 		horizontalScrollBar.addEventListener("scrolltonextline", {
5518 			horizontalScrollBar.setPosition(horizontalScrollBar.position + 1);
5519 			sw.horizontalScrollTo(horizontalScrollBar.position);
5520 		});
5521 		horizontalScrollBar.addEventListener("scrolltopreviousline", {
5522 			horizontalScrollBar.setPosition(horizontalScrollBar.position - 1);
5523 			sw.horizontalScrollTo(horizontalScrollBar.position);
5524 		});
5525 		verticalScrollBar.addEventListener("scrolltonextline", {
5526 			verticalScrollBar.setPosition(verticalScrollBar.position + 1);
5527 			sw.verticalScrollTo(verticalScrollBar.position);
5528 		});
5529 		verticalScrollBar.addEventListener("scrolltopreviousline", {
5530 			verticalScrollBar.setPosition(verticalScrollBar.position - 1);
5531 			sw.verticalScrollTo(verticalScrollBar.position);
5532 		});
5533 		horizontalScrollBar.addEventListener("scrolltonextpage", {
5534 			horizontalScrollBar.setPosition(horizontalScrollBar.position + horizontalScrollBar.step_);
5535 			sw.horizontalScrollTo(horizontalScrollBar.position);
5536 		});
5537 		horizontalScrollBar.addEventListener("scrolltopreviouspage", {
5538 			horizontalScrollBar.setPosition(horizontalScrollBar.position - horizontalScrollBar.step_);
5539 			sw.horizontalScrollTo(horizontalScrollBar.position);
5540 		});
5541 		verticalScrollBar.addEventListener("scrolltonextpage", {
5542 			verticalScrollBar.setPosition(verticalScrollBar.position + verticalScrollBar.step_);
5543 			sw.verticalScrollTo(verticalScrollBar.position);
5544 		});
5545 		verticalScrollBar.addEventListener("scrolltopreviouspage", {
5546 			verticalScrollBar.setPosition(verticalScrollBar.position - verticalScrollBar.step_);
5547 			sw.verticalScrollTo(verticalScrollBar.position);
5548 		});
5549 		horizontalScrollBar.addEventListener("scrolltoposition", (Event event) {
5550 			horizontalScrollBar.setPosition(event.intValue);
5551 			sw.horizontalScrollTo(horizontalScrollBar.position);
5552 		});
5553 		verticalScrollBar.addEventListener("scrolltoposition", (Event event) {
5554 			verticalScrollBar.setPosition(event.intValue);
5555 			sw.verticalScrollTo(verticalScrollBar.position);
5556 		});
5557 		horizontalScrollBar.addEventListener("scrolltrack", (Event event) {
5558 			horizontalScrollBar.setPosition(event.intValue);
5559 			sw.horizontalScrollTo(horizontalScrollBar.position);
5560 		});
5561 		verticalScrollBar.addEventListener("scrolltrack", (Event event) {
5562 			verticalScrollBar.setPosition(event.intValue);
5563 		});
5564 
5565 		super(parent);
5566 	}
5567 
5568 	// this is supposed to be basically invisible...
5569 	override int minWidth() { return sw.minWidth; }
5570 	override int minHeight() { return sw.minHeight; }
5571 	override int maxWidth() { return sw.maxWidth; }
5572 	override int maxHeight() { return sw.maxHeight; }
5573 	override int widthStretchiness() { return sw.widthStretchiness; }
5574 	override int heightStretchiness() { return sw.heightStretchiness; }
5575 	override int marginLeft() { return sw.marginLeft; }
5576 	override int marginRight() { return sw.marginRight; }
5577 	override int marginTop() { return sw.marginTop; }
5578 	override int marginBottom() { return sw.marginBottom; }
5579 	override int paddingLeft() { return sw.paddingLeft; }
5580 	override int paddingRight() { return sw.paddingRight; }
5581 	override int paddingTop() { return sw.paddingTop; }
5582 	override int paddingBottom() { return sw.paddingBottom; }
5583 	override void focus() { sw.focus(); }
5584 
5585 
5586 	override void recomputeChildLayout() {
5587 		// The stupid thing needs to calculate if a scroll bar is needed...
5588 		recomputeChildLayoutHelper();
5589 		// then running it again will position things correctly if the bar is NOT needed
5590 		recomputeChildLayoutHelper();
5591 
5592 		// this sucks but meh it barely works
5593 	}
5594 
5595 	private void recomputeChildLayoutHelper() {
5596 		if(sw is null) return;
5597 
5598 		bool both = sw.showingVerticalScroll && sw.showingHorizontalScroll;
5599 		if(horizontalScrollBar && verticalScrollBar) {
5600 			horizontalScrollBar.width = this.width - (both ? verticalScrollBar.minWidth() : 0);
5601 			horizontalScrollBar.height = horizontalScrollBar.minHeight();
5602 			horizontalScrollBar.x = 0;
5603 			horizontalScrollBar.y = this.height - horizontalScrollBar.minHeight();
5604 
5605 			verticalScrollBar.width = verticalScrollBar.minWidth();
5606 			verticalScrollBar.height = this.height - (both ? horizontalScrollBar.minHeight() : 0) - 2 - 2;
5607 			verticalScrollBar.x = this.width - verticalScrollBar.minWidth();
5608 			verticalScrollBar.y = 0 + 2;
5609 
5610 			sw.x = 0;
5611 			sw.y = 0;
5612 			sw.width = this.width - (verticalScrollBar.showing ? verticalScrollBar.width : 0);
5613 			sw.height = this.height - (horizontalScrollBar.showing ? horizontalScrollBar.height : 0);
5614 
5615 			if(sw.contentWidth_ <= this.width)
5616 				sw.scrollOrigin_.x = 0;
5617 			if(sw.contentHeight_ <= this.height)
5618 				sw.scrollOrigin_.y = 0;
5619 
5620 			horizontalScrollBar.recomputeChildLayout();
5621 			verticalScrollBar.recomputeChildLayout();
5622 			sw.recomputeChildLayout();
5623 		}
5624 
5625 		if(sw.contentWidth_ <= this.width)
5626 			sw.scrollOrigin_.x = 0;
5627 		if(sw.contentHeight_ <= this.height)
5628 			sw.scrollOrigin_.y = 0;
5629 
5630 		if(sw.showingHorizontalScroll())
5631 			horizontalScrollBar.showing(true, false);
5632 		else
5633 			horizontalScrollBar.showing(false, false);
5634 		if(sw.showingVerticalScroll())
5635 			verticalScrollBar.showing(true, false);
5636 		else
5637 			verticalScrollBar.showing(false, false);
5638 
5639 		verticalScrollBar.setViewableArea(sw.viewportHeight());
5640 		verticalScrollBar.setMax(sw.contentHeight);
5641 		verticalScrollBar.setPosition(sw.scrollOrigin.y);
5642 
5643 		horizontalScrollBar.setViewableArea(sw.viewportWidth());
5644 		horizontalScrollBar.setMax(sw.contentWidth);
5645 		horizontalScrollBar.setPosition(sw.scrollOrigin.x);
5646 	}
5647 }
5648 
5649 /*
5650 class ScrollableClientWidget : Widget {
5651 	this(Widget parent) {
5652 		super(parent);
5653 	}
5654 	override void paint(WidgetPainter p) {
5655 		parent.paint(p);
5656 	}
5657 }
5658 */
5659 
5660 /++
5661 	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.
5662 +/
5663 abstract class Slider : Widget {
5664 	this(int min, int max, int step, Widget parent) {
5665 		min_ = min;
5666 		max_ = max;
5667 		step_ = step;
5668 		page_ = step;
5669 		super(parent);
5670 	}
5671 
5672 	private int min_;
5673 	private int max_;
5674 	private int step_;
5675 	private int position_;
5676 	private int page_;
5677 
5678 	// selection start and selection end
5679 	// tics
5680 	// tooltip?
5681 	// some way to see and just type the value
5682 	// win32 buddy controls are labels
5683 
5684 	///
5685 	void setMin(int a) {
5686 		min_ = a;
5687 		version(custom_widgets)
5688 			redraw();
5689 		version(win32_widgets)
5690 			SendMessage(hwnd, TBM_SETRANGEMIN, true, a);
5691 	}
5692 	///
5693 	int min() {
5694 		return min_;
5695 	}
5696 	///
5697 	void setMax(int a) {
5698 		max_ = a;
5699 		version(custom_widgets)
5700 			redraw();
5701 		version(win32_widgets)
5702 			SendMessage(hwnd, TBM_SETRANGEMAX, true, a);
5703 	}
5704 	///
5705 	int max() {
5706 		return max_;
5707 	}
5708 	///
5709 	void setPosition(int a) {
5710 		if(a > max)
5711 			a = max;
5712 		if(a < min)
5713 			a = min;
5714 		position_ = a;
5715 		version(custom_widgets)
5716 			setPositionCustom(a);
5717 
5718 		version(win32_widgets)
5719 			setPositionWindows(a);
5720 	}
5721 	version(win32_widgets) {
5722 		protected abstract void setPositionWindows(int a);
5723 	}
5724 
5725 	protected abstract int win32direction();
5726 
5727 	/++
5728 		Alias for [position] for better compatibility with generic code.
5729 
5730 		History:
5731 			Added October 5, 2021
5732 	+/
5733 	@property int value() {
5734 		return position;
5735 	}
5736 
5737 	///
5738 	int position() {
5739 		return position_;
5740 	}
5741 	///
5742 	void setStep(int a) {
5743 		step_ = a;
5744 		version(win32_widgets)
5745 			SendMessage(hwnd, TBM_SETLINESIZE, 0, a);
5746 	}
5747 	///
5748 	int step() {
5749 		return step_;
5750 	}
5751 	///
5752 	void setPageSize(int a) {
5753 		page_ = a;
5754 		version(win32_widgets)
5755 			SendMessage(hwnd, TBM_SETPAGESIZE, 0, a);
5756 	}
5757 	///
5758 	int pageSize() {
5759 		return page_;
5760 	}
5761 
5762 	private void notify() {
5763 		auto event = new ChangeEvent!int(this, &this.position);
5764 		event.dispatch();
5765 	}
5766 
5767 	version(win32_widgets)
5768 	void win32Setup(int style) {
5769 		createWin32Window(this, TRACKBAR_CLASS, "",
5770 			0|WS_CHILD|WS_VISIBLE|style|TBS_TOOLTIPS, 0);
5771 
5772 		// the trackbar sends the same messages as scroll, which
5773 		// our other layer sends as these... just gonna translate
5774 		// here
5775 		this.addDirectEventListener("scrolltoposition", (Event event) {
5776 			event.stopPropagation();
5777 			this.setPosition(this.win32direction > 0 ? event.intValue : max - event.intValue);
5778 			notify();
5779 		});
5780 		this.addDirectEventListener("scrolltonextline", (Event event) {
5781 			event.stopPropagation();
5782 			this.setPosition(this.position + this.step_ * this.win32direction);
5783 			notify();
5784 		});
5785 		this.addDirectEventListener("scrolltopreviousline", (Event event) {
5786 			event.stopPropagation();
5787 			this.setPosition(this.position - this.step_ * this.win32direction);
5788 			notify();
5789 		});
5790 		this.addDirectEventListener("scrolltonextpage", (Event event) {
5791 			event.stopPropagation();
5792 			this.setPosition(this.position + this.page_ * this.win32direction);
5793 			notify();
5794 		});
5795 		this.addDirectEventListener("scrolltopreviouspage", (Event event) {
5796 			event.stopPropagation();
5797 			this.setPosition(this.position - this.page_ * this.win32direction);
5798 			notify();
5799 		});
5800 
5801 		setMin(min_);
5802 		setMax(max_);
5803 		setStep(step_);
5804 		setPageSize(page_);
5805 	}
5806 
5807 	version(custom_widgets) {
5808 		protected MouseTrackingWidget thumb;
5809 
5810 		protected abstract void setPositionCustom(int a);
5811 
5812 		override void defaultEventHandler_keydown(KeyDownEvent event) {
5813 			switch(event.key) {
5814 				case Key.Up:
5815 				case Key.Right:
5816 					setPosition(position() - step() * win32direction);
5817 					changed();
5818 				break;
5819 				case Key.Down:
5820 				case Key.Left:
5821 					setPosition(position() + step() * win32direction);
5822 					changed();
5823 				break;
5824 				case Key.Home:
5825 					setPosition(win32direction > 0 ? min() : max());
5826 					changed();
5827 				break;
5828 				case Key.End:
5829 					setPosition(win32direction > 0 ? max() : min());
5830 					changed();
5831 				break;
5832 				case Key.PageUp:
5833 					setPosition(position() - pageSize() * win32direction);
5834 					changed();
5835 				break;
5836 				case Key.PageDown:
5837 					setPosition(position() + pageSize() * win32direction);
5838 					changed();
5839 				break;
5840 				default:
5841 			}
5842 			super.defaultEventHandler_keydown(event);
5843 		}
5844 
5845 		protected void changed() {
5846 			auto ev = new ChangeEvent!int(this, &position);
5847 			ev.dispatch();
5848 		}
5849 	}
5850 }
5851 
5852 /++
5853 
5854 +/
5855 class VerticalSlider : Slider {
5856 	this(int min, int max, int step, Widget parent) {
5857 		version(custom_widgets)
5858 			initialize();
5859 
5860 		super(min, max, step, parent);
5861 
5862 		version(win32_widgets)
5863 			win32Setup(TBS_VERT | 0x0200 /* TBS_REVERSED */);
5864 	}
5865 
5866 	protected override int win32direction() {
5867 		return -1;
5868 	}
5869 
5870 	version(win32_widgets)
5871 	protected override void setPositionWindows(int a) {
5872 		// the windows thing makes the top 0 and i don't like that.
5873 		SendMessage(hwnd, TBM_SETPOS, true, max - a);
5874 	}
5875 
5876 	version(custom_widgets)
5877 	private void initialize() {
5878 		thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.vertical, this);
5879 
5880 		thumb.tabStop = false;
5881 
5882 		thumb.thumbWidth = width;
5883 		thumb.thumbHeight = scaleWithDpi(16);
5884 
5885 		thumb.addEventListener(EventType.change, () {
5886 			auto sx = thumb.positionY * max() / (thumb.height - scaleWithDpi(16));
5887 			sx = max - sx;
5888 			//informProgramThatUserChangedPosition(sx);
5889 
5890 			position_ = sx;
5891 
5892 			changed();
5893 		});
5894 	}
5895 
5896 	version(custom_widgets)
5897 	override void recomputeChildLayout() {
5898 		thumb.thumbWidth = this.width;
5899 		super.recomputeChildLayout();
5900 		setPositionCustom(position_);
5901 	}
5902 
5903 	version(custom_widgets)
5904 	protected override void setPositionCustom(int a) {
5905 		if(max())
5906 			thumb.positionY = (max - a) * (thumb.height - scaleWithDpi(16)) / max();
5907 		redraw();
5908 	}
5909 }
5910 
5911 /++
5912 
5913 +/
5914 class HorizontalSlider : Slider {
5915 	this(int min, int max, int step, Widget parent) {
5916 		version(custom_widgets)
5917 			initialize();
5918 
5919 		super(min, max, step, parent);
5920 
5921 		version(win32_widgets)
5922 			win32Setup(TBS_HORZ);
5923 	}
5924 
5925 	version(win32_widgets)
5926 	protected override void setPositionWindows(int a) {
5927 		SendMessage(hwnd, TBM_SETPOS, true, a);
5928 	}
5929 
5930 	protected override int win32direction() {
5931 		return 1;
5932 	}
5933 
5934 	version(custom_widgets)
5935 	private void initialize() {
5936 		thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.horizontal, this);
5937 
5938 		thumb.tabStop = false;
5939 
5940 		thumb.thumbWidth = scaleWithDpi(16);
5941 		thumb.thumbHeight = height;
5942 
5943 		thumb.addEventListener(EventType.change, () {
5944 			auto sx = thumb.positionX * max() / (thumb.width - scaleWithDpi(16));
5945 			//informProgramThatUserChangedPosition(sx);
5946 
5947 			position_ = sx;
5948 
5949 			changed();
5950 		});
5951 	}
5952 
5953 	version(custom_widgets)
5954 	override void recomputeChildLayout() {
5955 		thumb.thumbHeight = this.height;
5956 		super.recomputeChildLayout();
5957 		setPositionCustom(position_);
5958 	}
5959 
5960 	version(custom_widgets)
5961 	protected override void setPositionCustom(int a) {
5962 		if(max())
5963 			thumb.positionX = a * (thumb.width - scaleWithDpi(16)) / max();
5964 		redraw();
5965 	}
5966 }
5967 
5968 
5969 ///
5970 abstract class ScrollbarBase : Widget {
5971 	///
5972 	this(Widget parent) {
5973 		super(parent);
5974 		tabStop = false;
5975 		step_ = scaleWithDpi(16);
5976 	}
5977 
5978 	private int viewableArea_;
5979 	private int max_;
5980 	private int step_;// = 16;
5981 	private int position_;
5982 
5983 	///
5984 	bool atEnd() {
5985 		return position_ + viewableArea_ >= max_;
5986 	}
5987 
5988 	///
5989 	bool atStart() {
5990 		return position_ == 0;
5991 	}
5992 
5993 	///
5994 	void setViewableArea(int a) {
5995 		viewableArea_ = a;
5996 		version(custom_widgets)
5997 			redraw();
5998 	}
5999 	///
6000 	void setMax(int a) {
6001 		max_ = a;
6002 		version(custom_widgets)
6003 			redraw();
6004 	}
6005 	///
6006 	int max() {
6007 		return max_;
6008 	}
6009 	///
6010 	void setPosition(int a) {
6011 		auto logicalMax = max_ - viewableArea_;
6012 		if(a == int.max)
6013 			a = logicalMax;
6014 
6015 		if(a > logicalMax)
6016 			a = logicalMax;
6017 		if(a < 0)
6018 			a = 0;
6019 
6020 		position_ = a;
6021 
6022 		version(custom_widgets)
6023 			redraw();
6024 	}
6025 	///
6026 	int position() {
6027 		return position_;
6028 	}
6029 	///
6030 	void setStep(int a) {
6031 		step_ = a;
6032 	}
6033 	///
6034 	int step() {
6035 		return step_;
6036 	}
6037 
6038 	// FIXME: remove this.... maybe
6039 	/+
6040 	protected void informProgramThatUserChangedPosition(int n) {
6041 		position_ = n;
6042 		auto evt = new Event(EventType.change, this);
6043 		evt.intValue = n;
6044 		evt.dispatch();
6045 	}
6046 	+/
6047 
6048 	version(custom_widgets) {
6049 		enum MIN_THUMB_SIZE = 8;
6050 
6051 		abstract protected int getBarDim();
6052 		int thumbSize() {
6053 			if(viewableArea_ >= max_ || max_ == 0)
6054 				return getBarDim();
6055 
6056 			int res = viewableArea_ * getBarDim() / max_;
6057 
6058 			if(res < scaleWithDpi(MIN_THUMB_SIZE))
6059 				res = scaleWithDpi(MIN_THUMB_SIZE);
6060 
6061 			return res;
6062 		}
6063 
6064 		int thumbPosition() {
6065 			/*
6066 				viewableArea_ is the viewport height/width
6067 				position_ is where we are
6068 			*/
6069 			//if(position_ + viewableArea_ >= max_)
6070 				//return getBarDim - thumbSize;
6071 
6072 			auto maximumPossibleValue = getBarDim() - thumbSize;
6073 			auto maximiumLogicalValue = max_ - viewableArea_;
6074 
6075 			auto p = (maximiumLogicalValue > 0) ? cast(int) (cast(long) position_ * maximumPossibleValue / maximiumLogicalValue) : 0;
6076 
6077 			return p;
6078 		}
6079 	}
6080 }
6081 
6082 //public import mgt;
6083 
6084 /++
6085 	A mouse tracking widget is one that follows the mouse when dragged inside it.
6086 
6087 	Concrete subclasses may include a scrollbar thumb and a volume control.
6088 +/
6089 //version(custom_widgets)
6090 class MouseTrackingWidget : Widget {
6091 
6092 	///
6093 	int positionX() { return positionX_; }
6094 	///
6095 	int positionY() { return positionY_; }
6096 
6097 	///
6098 	void positionX(int p) { positionX_ = p; }
6099 	///
6100 	void positionY(int p) { positionY_ = p; }
6101 
6102 	private int positionX_;
6103 	private int positionY_;
6104 
6105 	///
6106 	enum Orientation {
6107 		horizontal, ///
6108 		vertical, ///
6109 		twoDimensional, ///
6110 	}
6111 
6112 	private int thumbWidth_;
6113 	private int thumbHeight_;
6114 
6115 	///
6116 	int thumbWidth() { return thumbWidth_; }
6117 	///
6118 	int thumbHeight() { return thumbHeight_; }
6119 	///
6120 	int thumbWidth(int a) { return thumbWidth_ = a; }
6121 	///
6122 	int thumbHeight(int a) { return thumbHeight_ = a; }
6123 
6124 	private bool dragging;
6125 	private bool hovering;
6126 	private int startMouseX, startMouseY;
6127 
6128 	///
6129 	this(Orientation orientation, Widget parent) {
6130 		super(parent);
6131 
6132 		//assert(parentWindow !is null);
6133 
6134 		addEventListener((MouseDownEvent event) {
6135 			if(event.clientX >= positionX && event.clientX < positionX + thumbWidth && event.clientY >= positionY && event.clientY < positionY + thumbHeight) {
6136 				dragging = true;
6137 				startMouseX = event.clientX - positionX;
6138 				startMouseY = event.clientY - positionY;
6139 				parentWindow.captureMouse(this);
6140 			} else {
6141 				if(orientation == Orientation.horizontal || orientation == Orientation.twoDimensional)
6142 					positionX = event.clientX - thumbWidth / 2;
6143 				if(orientation == Orientation.vertical || orientation == Orientation.twoDimensional)
6144 					positionY = event.clientY - thumbHeight / 2;
6145 
6146 				if(positionX + thumbWidth > this.width)
6147 					positionX = this.width - thumbWidth;
6148 				if(positionY + thumbHeight > this.height)
6149 					positionY = this.height - thumbHeight;
6150 
6151 				if(positionX < 0)
6152 					positionX = 0;
6153 				if(positionY < 0)
6154 					positionY = 0;
6155 
6156 
6157 				// this.emit!(ChangeEvent!void)();
6158 				auto evt = new Event(EventType.change, this);
6159 				evt.sendDirectly();
6160 
6161 				redraw();
6162 
6163 			}
6164 		});
6165 
6166 		addEventListener(EventType.mouseup, (Event event) {
6167 			dragging = false;
6168 			parentWindow.releaseMouseCapture();
6169 		});
6170 
6171 		addEventListener(EventType.mouseout, (Event event) {
6172 			if(!hovering)
6173 				return;
6174 			hovering = false;
6175 			redraw();
6176 		});
6177 
6178 		int lpx, lpy;
6179 
6180 		addEventListener((MouseMoveEvent event) {
6181 			auto oh = hovering;
6182 			if(event.clientX >= positionX && event.clientX < positionX + thumbWidth && event.clientY >= positionY && event.clientY < positionY + thumbHeight) {
6183 				hovering = true;
6184 			} else {
6185 				hovering = false;
6186 			}
6187 			if(!dragging) {
6188 				if(hovering != oh)
6189 					redraw();
6190 				return;
6191 			}
6192 
6193 			if(orientation == Orientation.horizontal || orientation == Orientation.twoDimensional)
6194 				positionX = event.clientX - startMouseX; // FIXME: click could be in the middle of it
6195 			if(orientation == Orientation.vertical || orientation == Orientation.twoDimensional)
6196 				positionY = event.clientY - startMouseY;
6197 
6198 			if(positionX + thumbWidth > this.width)
6199 				positionX = this.width - thumbWidth;
6200 			if(positionY + thumbHeight > this.height)
6201 				positionY = this.height - thumbHeight;
6202 
6203 			if(positionX < 0)
6204 				positionX = 0;
6205 			if(positionY < 0)
6206 				positionY = 0;
6207 
6208 			if(positionX != lpx || positionY != lpy) {
6209 				lpx = positionX;
6210 				lpy = positionY;
6211 
6212 				auto evt = new Event(EventType.change, this);
6213 				evt.sendDirectly();
6214 			}
6215 
6216 			redraw();
6217 		});
6218 	}
6219 
6220 	version(custom_widgets)
6221 	override void paint(WidgetPainter painter) {
6222 		auto cs = getComputedStyle();
6223 		auto c = darken(cs.windowBackgroundColor, 0.2);
6224 		painter.outlineColor = c;
6225 		painter.fillColor = c;
6226 		painter.drawRectangle(Point(0, 0), this.width, this.height);
6227 
6228 		auto color = hovering ? cs.hoveringColor : cs.windowBackgroundColor;
6229 		draw3dFrame(positionX, positionY, thumbWidth, thumbHeight, painter, FrameStyle.risen, color);
6230 	}
6231 }
6232 
6233 //version(custom_widgets)
6234 //private
6235 class HorizontalScrollbar : ScrollbarBase {
6236 
6237 	version(custom_widgets) {
6238 		private MouseTrackingWidget thumb;
6239 
6240 		override int getBarDim() {
6241 			return thumb.width;
6242 		}
6243 	}
6244 
6245 	override void setViewableArea(int a) {
6246 		super.setViewableArea(a);
6247 
6248 		version(win32_widgets) {
6249 			SCROLLINFO info;
6250 			info.cbSize = info.sizeof;
6251 			info.nPage = a + 1;
6252 			info.fMask = SIF_PAGE;
6253 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6254 		} else version(custom_widgets) {
6255 			thumb.positionX = thumbPosition;
6256 			thumb.thumbWidth = thumbSize;
6257 			thumb.redraw();
6258 		} else static assert(0);
6259 
6260 	}
6261 
6262 	override void setMax(int a) {
6263 		super.setMax(a);
6264 		version(win32_widgets) {
6265 			SCROLLINFO info;
6266 			info.cbSize = info.sizeof;
6267 			info.nMin = 0;
6268 			info.nMax = max;
6269 			info.fMask = SIF_RANGE;
6270 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6271 		} else version(custom_widgets) {
6272 			thumb.positionX = thumbPosition;
6273 			thumb.thumbWidth = thumbSize;
6274 			thumb.redraw();
6275 		}
6276 	}
6277 
6278 	override void setPosition(int a) {
6279 		super.setPosition(a);
6280 		version(win32_widgets) {
6281 			SCROLLINFO info;
6282 			info.cbSize = info.sizeof;
6283 			info.fMask = SIF_POS;
6284 			info.nPos = position;
6285 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6286 		} else version(custom_widgets) {
6287 			thumb.positionX = thumbPosition();
6288 			thumb.thumbWidth = thumbSize;
6289 			thumb.redraw();
6290 		} else static assert(0);
6291 	}
6292 
6293 	this(Widget parent) {
6294 		super(parent);
6295 
6296 		version(win32_widgets) {
6297 			createWin32Window(this, "Scrollbar"w, "",
6298 				0|WS_CHILD|WS_VISIBLE|SBS_HORZ|SBS_BOTTOMALIGN, 0);
6299 		} else version(custom_widgets) {
6300 			auto vl = new HorizontalLayout(this);
6301 			auto leftButton = new ArrowButton(ArrowDirection.left, vl);
6302 			leftButton.setClickRepeat(scrollClickRepeatInterval);
6303 			thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.horizontal, vl);
6304 			auto rightButton = new ArrowButton(ArrowDirection.right, vl);
6305 			rightButton.setClickRepeat(scrollClickRepeatInterval);
6306 
6307 			leftButton.tabStop = false;
6308 			rightButton.tabStop = false;
6309 			thumb.tabStop = false;
6310 
6311 			leftButton.addEventListener(EventType.triggered, () {
6312 				this.emitCommand!"scrolltopreviousline"();
6313 				//informProgramThatUserChangedPosition(position - step());
6314 			});
6315 			rightButton.addEventListener(EventType.triggered, () {
6316 				this.emitCommand!"scrolltonextline"();
6317 				//informProgramThatUserChangedPosition(position + step());
6318 			});
6319 
6320 			thumb.thumbWidth = this.minWidth;
6321 			thumb.thumbHeight = scaleWithDpi(16);
6322 
6323 			thumb.addEventListener(EventType.change, () {
6324 				auto maximumPossibleValue = thumb.width - thumb.thumbWidth;
6325 				auto sx = maximumPossibleValue ? cast(int)(cast(long) thumb.positionX * (max()-viewableArea_) / maximumPossibleValue) : 0;
6326 
6327 				//informProgramThatUserChangedPosition(sx);
6328 
6329 				auto ev = new ScrollToPositionEvent(this, sx);
6330 				ev.dispatch();
6331 			});
6332 		}
6333 	}
6334 
6335 	override int minHeight() { return scaleWithDpi(16); }
6336 	override int maxHeight() { return scaleWithDpi(16); }
6337 	override int minWidth() { return scaleWithDpi(48); }
6338 }
6339 
6340 class ScrollToPositionEvent : Event {
6341 	enum EventString = "scrolltoposition";
6342 
6343 	this(Widget target, int value) {
6344 		this.value = value;
6345 		super(EventString, target);
6346 	}
6347 
6348 	immutable int value;
6349 
6350 	override @property int intValue() {
6351 		return value;
6352 	}
6353 }
6354 
6355 //version(custom_widgets)
6356 //private
6357 class VerticalScrollbar : ScrollbarBase {
6358 
6359 	version(custom_widgets) {
6360 		override int getBarDim() {
6361 			return thumb.height;
6362 		}
6363 
6364 		private MouseTrackingWidget thumb;
6365 	}
6366 
6367 	override void setViewableArea(int a) {
6368 		super.setViewableArea(a);
6369 
6370 		version(win32_widgets) {
6371 			SCROLLINFO info;
6372 			info.cbSize = info.sizeof;
6373 			info.nPage = a + 1;
6374 			info.fMask = SIF_PAGE;
6375 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6376 		} else version(custom_widgets) {
6377 			thumb.positionY = thumbPosition;
6378 			thumb.thumbHeight = thumbSize;
6379 			thumb.redraw();
6380 		} else static assert(0);
6381 
6382 	}
6383 
6384 	override void setMax(int a) {
6385 		super.setMax(a);
6386 		version(win32_widgets) {
6387 			SCROLLINFO info;
6388 			info.cbSize = info.sizeof;
6389 			info.nMin = 0;
6390 			info.nMax = max;
6391 			info.fMask = SIF_RANGE;
6392 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6393 		} else version(custom_widgets) {
6394 			thumb.positionY = thumbPosition;
6395 			thumb.thumbHeight = thumbSize;
6396 			thumb.redraw();
6397 		}
6398 	}
6399 
6400 	override void setPosition(int a) {
6401 		super.setPosition(a);
6402 		version(win32_widgets) {
6403 			SCROLLINFO info;
6404 			info.cbSize = info.sizeof;
6405 			info.fMask = SIF_POS;
6406 			info.nPos = position;
6407 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6408 		} else version(custom_widgets) {
6409 			thumb.positionY = thumbPosition;
6410 			thumb.thumbHeight = thumbSize;
6411 			thumb.redraw();
6412 		} else static assert(0);
6413 	}
6414 
6415 	this(Widget parent) {
6416 		super(parent);
6417 
6418 		version(win32_widgets) {
6419 			createWin32Window(this, "Scrollbar"w, "",
6420 				0|WS_CHILD|WS_VISIBLE|SBS_VERT|SBS_RIGHTALIGN, 0);
6421 		} else version(custom_widgets) {
6422 			auto vl = new VerticalLayout(this);
6423 			auto upButton = new ArrowButton(ArrowDirection.up, vl);
6424 			upButton.setClickRepeat(scrollClickRepeatInterval);
6425 			thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.vertical, vl);
6426 			auto downButton = new ArrowButton(ArrowDirection.down, vl);
6427 			downButton.setClickRepeat(scrollClickRepeatInterval);
6428 
6429 			upButton.addEventListener(EventType.triggered, () {
6430 				this.emitCommand!"scrolltopreviousline"();
6431 				//informProgramThatUserChangedPosition(position - step());
6432 			});
6433 			downButton.addEventListener(EventType.triggered, () {
6434 				this.emitCommand!"scrolltonextline"();
6435 				//informProgramThatUserChangedPosition(position + step());
6436 			});
6437 
6438 			thumb.thumbWidth = this.minWidth;
6439 			thumb.thumbHeight = scaleWithDpi(16);
6440 
6441 			thumb.addEventListener(EventType.change, () {
6442 				auto maximumPossibleValue = thumb.height - thumb.thumbHeight;
6443 				auto sy = maximumPossibleValue ? cast(int) (cast(long) thumb.positionY * (max()-viewableArea_) / maximumPossibleValue) : 0;
6444 
6445 				auto ev = new ScrollToPositionEvent(this, sy);
6446 				ev.dispatch();
6447 
6448 				//informProgramThatUserChangedPosition(sy);
6449 			});
6450 
6451 			upButton.tabStop = false;
6452 			downButton.tabStop = false;
6453 			thumb.tabStop = false;
6454 		}
6455 	}
6456 
6457 	override int minWidth() { return scaleWithDpi(16); }
6458 	override int maxWidth() { return scaleWithDpi(16); }
6459 	override int minHeight() { return scaleWithDpi(48); }
6460 }
6461 
6462 
6463 /++
6464 	EXPERIMENTAL
6465 
6466 	A widget specialized for being a container for other widgets.
6467 
6468 	History:
6469 		Added May 29, 2021. Not stabilized at this time.
6470 +/
6471 class WidgetContainer : Widget {
6472 	this(Widget parent) {
6473 		tabStop = false;
6474 		super(parent);
6475 	}
6476 
6477 	override int maxHeight() {
6478 		if(this.children.length == 1) {
6479 			return saturatedSum(this.children[0].maxHeight, this.children[0].marginTop, this.children[0].marginBottom);
6480 		} else {
6481 			return int.max;
6482 		}
6483 	}
6484 
6485 	override int maxWidth() {
6486 		if(this.children.length == 1) {
6487 			return saturatedSum(this.children[0].maxWidth, this.children[0].marginLeft, this.children[0].marginRight);
6488 		} else {
6489 			return int.max;
6490 		}
6491 	}
6492 
6493 	/+
6494 
6495 	override int minHeight() {
6496 		int largest = 0;
6497 		int margins = 0;
6498 		int lastMargin = 0;
6499 		foreach(child; children) {
6500 			auto mh = child.minHeight();
6501 			if(mh > largest)
6502 				largest = mh;
6503 			margins += mymax(lastMargin, child.marginTop());
6504 			lastMargin = child.marginBottom();
6505 		}
6506 		return largest + margins;
6507 	}
6508 
6509 	override int maxHeight() {
6510 		int largest = 0;
6511 		int margins = 0;
6512 		int lastMargin = 0;
6513 		foreach(child; children) {
6514 			auto mh = child.maxHeight();
6515 			if(mh == int.max)
6516 				return int.max;
6517 			if(mh > largest)
6518 				largest = mh;
6519 			margins += mymax(lastMargin, child.marginTop());
6520 			lastMargin = child.marginBottom();
6521 		}
6522 		return largest + margins;
6523 	}
6524 
6525 	override int minWidth() {
6526 		int min;
6527 		foreach(child; children) {
6528 			auto cm = child.minWidth;
6529 			if(cm > min)
6530 				min = cm;
6531 		}
6532 		return min + paddingLeft + paddingRight;
6533 	}
6534 
6535 	override int minHeight() {
6536 		int min;
6537 		foreach(child; children) {
6538 			auto cm = child.minHeight;
6539 			if(cm > min)
6540 				min = cm;
6541 		}
6542 		return min + paddingTop + paddingBottom;
6543 	}
6544 
6545 	override int maxHeight() {
6546 		int largest = 0;
6547 		int margins = 0;
6548 		int lastMargin = 0;
6549 		foreach(child; children) {
6550 			auto mh = child.maxHeight();
6551 			if(mh == int.max)
6552 				return int.max;
6553 			if(mh > largest)
6554 				largest = mh;
6555 			margins += mymax(lastMargin, child.marginTop());
6556 			lastMargin = child.marginBottom();
6557 		}
6558 		return largest + margins;
6559 	}
6560 
6561 	override int heightStretchiness() {
6562 		int max;
6563 		foreach(child; children) {
6564 			auto c = child.heightStretchiness;
6565 			if(c > max)
6566 				max = c;
6567 		}
6568 		return max;
6569 	}
6570 
6571 	override int marginTop() {
6572 		if(this.children.length)
6573 			return this.children[0].marginTop;
6574 		return 0;
6575 	}
6576 	+/
6577 }
6578 
6579 ///
6580 abstract class Layout : Widget {
6581 	this(Widget parent) {
6582 		tabStop = false;
6583 		super(parent);
6584 	}
6585 }
6586 
6587 /++
6588 	Makes all children minimum width and height, placing them down
6589 	left to right, top to bottom.
6590 
6591 	Useful if you want to make a list of buttons that automatically
6592 	wrap to a new line when necessary.
6593 +/
6594 class InlineBlockLayout : Layout {
6595 	///
6596 	this(Widget parent) { super(parent); }
6597 
6598 	override void recomputeChildLayout() {
6599 		registerMovement();
6600 
6601 		int x = this.paddingLeft, y = this.paddingTop;
6602 
6603 		int lineHeight;
6604 		int previousMargin = 0;
6605 		int previousMarginBottom = 0;
6606 
6607 		foreach(child; children) {
6608 			if(child.hidden)
6609 				continue;
6610 			if(cast(FixedPosition) child) {
6611 				child.recomputeChildLayout();
6612 				continue;
6613 			}
6614 			child.width = child.flexBasisWidth();
6615 			if(child.width == 0)
6616 				child.width = child.minWidth();
6617 			if(child.width == 0)
6618 				child.width = 32;
6619 
6620 			child.height = child.flexBasisHeight();
6621 			if(child.height == 0)
6622 				child.height = child.minHeight();
6623 			if(child.height == 0)
6624 				child.height = 32;
6625 
6626 			if(x + child.width + paddingRight > this.width) {
6627 				x = this.paddingLeft;
6628 				y += lineHeight;
6629 				lineHeight = 0;
6630 				previousMargin = 0;
6631 				previousMarginBottom = 0;
6632 			}
6633 
6634 			auto margin = child.marginLeft;
6635 			if(previousMargin > margin)
6636 				margin = previousMargin;
6637 
6638 			x += margin;
6639 
6640 			child.x = x;
6641 			child.y = y;
6642 
6643 			int marginTopApplied;
6644 			if(child.marginTop > previousMarginBottom) {
6645 				child.y += child.marginTop;
6646 				marginTopApplied = child.marginTop;
6647 			}
6648 
6649 			x += child.width;
6650 			previousMargin = child.marginRight;
6651 
6652 			if(child.marginBottom > previousMarginBottom)
6653 				previousMarginBottom = child.marginBottom;
6654 
6655 			auto h = child.height + previousMarginBottom + marginTopApplied;
6656 			if(h > lineHeight)
6657 				lineHeight = h;
6658 
6659 			child.recomputeChildLayout();
6660 		}
6661 
6662 	}
6663 
6664 	override int minWidth() {
6665 		int min;
6666 		foreach(child; children) {
6667 			auto cm = child.minWidth;
6668 			if(cm > min)
6669 				min = cm;
6670 		}
6671 		return min + paddingLeft + paddingRight;
6672 	}
6673 
6674 	override int minHeight() {
6675 		int min;
6676 		foreach(child; children) {
6677 			auto cm = child.minHeight;
6678 			if(cm > min)
6679 				min = cm;
6680 		}
6681 		return min + paddingTop + paddingBottom;
6682 	}
6683 }
6684 
6685 /++
6686 	A TabMessageWidget is a clickable row of tabs followed by a content area, very similar
6687 	to the [TabWidget]. The difference is the TabMessageWidget only sends messages, whereas
6688 	the [TabWidget] will automatically change pages of child widgets.
6689 
6690 	This allows you to react to it however you see fit rather than having to
6691 	be tied to just the new sets of child widgets.
6692 
6693 	It sends the message in the form of `this.emitCommand!"changetab"();`.
6694 
6695 	History:
6696 		Added December 24, 2021 (dub v10.5)
6697 +/
6698 class TabMessageWidget : Widget {
6699 
6700 	protected void tabIndexClicked(int item) {
6701 		this.emitCommand!"changetab"();
6702 	}
6703 
6704 	/++
6705 		Adds the a new tab to the control with the given title.
6706 
6707 		Returns:
6708 			The index of the newly added tab. You will need to know
6709 			this index to refer to it later and to know which tab to
6710 			change to when you get a changetab message.
6711 	+/
6712 	int addTab(string title, int pos = int.max) {
6713 		version(win32_widgets) {
6714 			TCITEM item;
6715 			item.mask = TCIF_TEXT;
6716 			WCharzBuffer buf = WCharzBuffer(title);
6717 			item.pszText = buf.ptr;
6718 			return cast(int) SendMessage(hwnd, TCM_INSERTITEM, pos, cast(LPARAM) &item);
6719 		} else version(custom_widgets) {
6720 			if(pos >= tabs.length) {
6721 				tabs ~= title;
6722 				redraw();
6723 				return cast(int) tabs.length - 1;
6724 			} else if(pos <= 0) {
6725 				tabs = title ~ tabs;
6726 				redraw();
6727 				return 0;
6728 			} else {
6729 				tabs = tabs[0 .. pos] ~ title ~ title[pos .. $];
6730 				redraw();
6731 				return pos;
6732 			}
6733 		}
6734 	}
6735 
6736 	override void addChild(Widget child, int pos = int.max) {
6737 		if(container)
6738 			container.addChild(child, pos);
6739 		else
6740 			super.addChild(child, pos);
6741 	}
6742 
6743 	protected Widget makeContainer() {
6744 		return new Widget(this);
6745 	}
6746 
6747 	private Widget container;
6748 
6749 	override void recomputeChildLayout() {
6750 		version(win32_widgets) {
6751 			this.registerMovement();
6752 
6753 			RECT rect;
6754 			GetWindowRect(hwnd, &rect);
6755 
6756 			auto left = rect.left;
6757 			auto top = rect.top;
6758 
6759 			TabCtrl_AdjustRect(hwnd, false, &rect);
6760 			foreach(child; children) {
6761 				if(!child.showing) continue;
6762 				child.x = rect.left - left;
6763 				child.y = rect.top - top;
6764 				child.width = rect.right - rect.left;
6765 				child.height = rect.bottom - rect.top;
6766 				child.recomputeChildLayout();
6767 			}
6768 		} else version(custom_widgets) {
6769 			this.registerMovement();
6770 			foreach(child; children) {
6771 				if(!child.showing) continue;
6772 				child.x = 2;
6773 				child.y = tabBarHeight + 2; // for the border
6774 				child.width = width - 4; // for the border
6775 				child.height = height - tabBarHeight - 2 - 2; // for the border
6776 				child.recomputeChildLayout();
6777 			}
6778 		} else static assert(0);
6779 	}
6780 
6781 	version(custom_widgets)
6782 		string[] tabs;
6783 
6784 	this(Widget parent) {
6785 		super(parent);
6786 
6787 		tabStop = false;
6788 
6789 		version(win32_widgets) {
6790 			createWin32Window(this, WC_TABCONTROL, "", 0);
6791 		} else version(custom_widgets) {
6792 			addEventListener((ClickEvent event) {
6793 				if(event.target !is this && this.container !is null && event.target !is this.container) return;
6794 				if(event.clientY < tabBarHeight) {
6795 					auto t = (event.clientX / tabWidth);
6796 					if(t >= 0 && t < tabs.length) {
6797 						currentTab_ = t;
6798 						tabIndexClicked(t);
6799 						redraw();
6800 					}
6801 				}
6802 			});
6803 		} else static assert(0);
6804 
6805 		this.container = makeContainer();
6806 	}
6807 
6808 	override int marginTop() { return 4; }
6809 	override int paddingBottom() { return 4; }
6810 
6811 	override int minHeight() {
6812 		int max = 0;
6813 		foreach(child; children)
6814 			max = mymax(child.minHeight, max);
6815 
6816 
6817 		version(win32_widgets) {
6818 			RECT rect;
6819 			rect.right = this.width;
6820 			rect.bottom = max;
6821 			TabCtrl_AdjustRect(hwnd, true, &rect);
6822 
6823 			max = rect.bottom;
6824 		} else {
6825 			max += defaultLineHeight + 4;
6826 		}
6827 
6828 
6829 		return max;
6830 	}
6831 
6832 	version(win32_widgets)
6833 	override int handleWmNotify(NMHDR* hdr, int code, out int mustReturn) {
6834 		switch(code) {
6835 			case TCN_SELCHANGE:
6836 				auto sel = TabCtrl_GetCurSel(hwnd);
6837 				tabIndexClicked(sel);
6838 			break;
6839 			default:
6840 		}
6841 		return 0;
6842 	}
6843 
6844 	version(custom_widgets) {
6845 		private int currentTab_;
6846 		private int tabBarHeight() { return defaultLineHeight; }
6847 		int tabWidth = 80;
6848 	}
6849 
6850 	version(win32_widgets)
6851 	override void paint(WidgetPainter painter) {}
6852 
6853 	version(custom_widgets)
6854 	override void paint(WidgetPainter painter) {
6855 		auto cs = getComputedStyle();
6856 
6857 		draw3dFrame(0, tabBarHeight - 2, width, height - tabBarHeight + 2, painter, FrameStyle.risen, cs.background.color);
6858 
6859 		int posX = 0;
6860 		foreach(idx, title; tabs) {
6861 			auto isCurrent = idx == getCurrentTab();
6862 
6863 			painter.setClipRectangle(Point(posX, 0), tabWidth, tabBarHeight);
6864 
6865 			draw3dFrame(posX, 0, tabWidth, tabBarHeight, painter, isCurrent ? FrameStyle.risen : FrameStyle.sunk, isCurrent ? cs.windowBackgroundColor : darken(cs.windowBackgroundColor, 0.1));
6866 			painter.outlineColor = cs.foregroundColor;
6867 			painter.drawText(Point(posX + 4, 2), title, Point(posX + tabWidth, tabBarHeight - 2), TextAlignment.VerticalCenter);
6868 
6869 			if(isCurrent) {
6870 				painter.outlineColor = cs.windowBackgroundColor;
6871 				painter.fillColor = Color.transparent;
6872 				painter.drawLine(Point(posX + 2, tabBarHeight - 1), Point(posX + tabWidth, tabBarHeight - 1));
6873 				painter.drawLine(Point(posX + 2, tabBarHeight - 2), Point(posX + tabWidth, tabBarHeight - 2));
6874 
6875 				painter.outlineColor = Color.white;
6876 				painter.drawPixel(Point(posX + 1, tabBarHeight - 1));
6877 				painter.drawPixel(Point(posX + 1, tabBarHeight - 2));
6878 				painter.outlineColor = cs.activeTabColor;
6879 				painter.drawPixel(Point(posX, tabBarHeight - 1));
6880 			}
6881 
6882 			posX += tabWidth - 2;
6883 		}
6884 	}
6885 
6886 	///
6887 	@scriptable
6888 	void setCurrentTab(int item) {
6889 		version(win32_widgets)
6890 			TabCtrl_SetCurSel(hwnd, item);
6891 		else version(custom_widgets)
6892 			currentTab_ = item;
6893 		else static assert(0);
6894 
6895 		tabIndexClicked(item);
6896 	}
6897 
6898 	///
6899 	@scriptable
6900 	int getCurrentTab() {
6901 		version(win32_widgets)
6902 			return TabCtrl_GetCurSel(hwnd);
6903 		else version(custom_widgets)
6904 			return currentTab_; // FIXME
6905 		else static assert(0);
6906 	}
6907 
6908 	///
6909 	@scriptable
6910 	void removeTab(int item) {
6911 		if(item && item == getCurrentTab())
6912 			setCurrentTab(item - 1);
6913 
6914 		version(win32_widgets) {
6915 			TabCtrl_DeleteItem(hwnd, item);
6916 		}
6917 
6918 		for(int a = item; a < children.length - 1; a++)
6919 			this._children[a] = this._children[a + 1];
6920 		this._children = this._children[0 .. $-1];
6921 	}
6922 
6923 }
6924 
6925 
6926 /++
6927 	A tab widget is a set of clickable tab buttons followed by a content area.
6928 
6929 
6930 	Tabs can change existing content or can be new pages.
6931 
6932 	When the user picks a different tab, a `change` message is generated.
6933 +/
6934 class TabWidget : TabMessageWidget {
6935 	this(Widget parent) {
6936 		super(parent);
6937 	}
6938 
6939 	override protected Widget makeContainer() {
6940 		return null;
6941 	}
6942 
6943 	override void addChild(Widget child, int pos = int.max) {
6944 		if(auto twp = cast(TabWidgetPage) child) {
6945 			Widget.addChild(child, pos);
6946 			if(pos == int.max)
6947 				pos = cast(int) this.children.length - 1;
6948 
6949 			super.addTab(twp.title, pos); // need to bypass the override here which would get into a loop...
6950 
6951 			if(pos != getCurrentTab) {
6952 				child.showing = false;
6953 			}
6954 		} else {
6955 			assert(0, "Don't add children directly to a tab widget, instead add them to a page (see addPage)");
6956 		}
6957 	}
6958 
6959 	// FIXME: add tab icons at some point, Windows supports them
6960 	/++
6961 		Adds a page and its associated tab with the given label to the widget.
6962 
6963 		Returns:
6964 			The added page object, to which you can add other widgets.
6965 	+/
6966 	@scriptable
6967 	TabWidgetPage addPage(string title) {
6968 		return new TabWidgetPage(title, this);
6969 	}
6970 
6971 	/++
6972 		Gets the page at the given tab index, or `null` if the index is bad.
6973 
6974 		History:
6975 			Added December 24, 2021.
6976 	+/
6977 	TabWidgetPage getPage(int index) {
6978 		if(index < this.children.length)
6979 			return null;
6980 		return cast(TabWidgetPage) this.children[index];
6981 	}
6982 
6983 	/++
6984 		While you can still use the addTab from the parent class,
6985 		*strongly* recommend you use [addPage] insteaad.
6986 
6987 		History:
6988 			Added December 24, 2021 to fulful the interface
6989 			requirement that came from adding [TabMessageWidget].
6990 
6991 			You should not use it though since the [addPage] function
6992 			is much easier to use here.
6993 	+/
6994 	override int addTab(string title, int pos = int.max) {
6995 		auto p = addPage(title);
6996 		foreach(idx, child; this.children)
6997 			if(child is p)
6998 				return cast(int) idx;
6999 		return -1;
7000 	}
7001 
7002 	protected override void tabIndexClicked(int item) {
7003 		foreach(idx, child; children) {
7004 			child.showing(false, false); // batch the recalculates for the end
7005 		}
7006 
7007 		foreach(idx, child; children) {
7008 			if(idx == item) {
7009 				child.showing(true, false);
7010 				if(parentWindow) {
7011 					auto f = parentWindow.getFirstFocusable(child);
7012 					if(f)
7013 						f.focus();
7014 				}
7015 				recomputeChildLayout();
7016 			}
7017 		}
7018 
7019 		version(win32_widgets) {
7020 			InvalidateRect(hwnd, null, true);
7021 		} else version(custom_widgets) {
7022 			this.redraw();
7023 		}
7024 	}
7025 
7026 }
7027 
7028 /++
7029 	A page widget is basically a tab widget with hidden tabs. It is also sometimes called a "StackWidget".
7030 
7031 	You add [TabWidgetPage]s to it.
7032 +/
7033 class PageWidget : Widget {
7034 	this(Widget parent) {
7035 		super(parent);
7036 	}
7037 
7038 	override int minHeight() {
7039 		int max = 0;
7040 		foreach(child; children)
7041 			max = mymax(child.minHeight, max);
7042 
7043 		return max;
7044 	}
7045 
7046 
7047 	override void addChild(Widget child, int pos = int.max) {
7048 		if(auto twp = cast(TabWidgetPage) child) {
7049 			super.addChild(child, pos);
7050 			if(pos == int.max)
7051 				pos = cast(int) this.children.length - 1;
7052 
7053 			if(pos != getCurrentTab) {
7054 				child.showing = false;
7055 			}
7056 		} else {
7057 			assert(0, "Don't add children directly to a page widget, instead add them to a page (see addPage)");
7058 		}
7059 	}
7060 
7061 	override void recomputeChildLayout() {
7062 		this.registerMovement();
7063 		foreach(child; children) {
7064 			child.x = 0;
7065 			child.y = 0;
7066 			child.width = width;
7067 			child.height = height;
7068 			child.recomputeChildLayout();
7069 		}
7070 	}
7071 
7072 	private int currentTab_;
7073 
7074 	///
7075 	@scriptable
7076 	void setCurrentTab(int item) {
7077 		currentTab_ = item;
7078 
7079 		showOnly(item);
7080 	}
7081 
7082 	///
7083 	@scriptable
7084 	int getCurrentTab() {
7085 		return currentTab_;
7086 	}
7087 
7088 	///
7089 	@scriptable
7090 	void removeTab(int item) {
7091 		if(item && item == getCurrentTab())
7092 			setCurrentTab(item - 1);
7093 
7094 		for(int a = item; a < children.length - 1; a++)
7095 			this._children[a] = this._children[a + 1];
7096 		this._children = this._children[0 .. $-1];
7097 	}
7098 
7099 	///
7100 	@scriptable
7101 	TabWidgetPage addPage(string title) {
7102 		return new TabWidgetPage(title, this);
7103 	}
7104 
7105 	private void showOnly(int item) {
7106 		foreach(idx, child; children)
7107 			if(idx == item) {
7108 				child.show();
7109 				child.recomputeChildLayout();
7110 			} else {
7111 				child.hide();
7112 			}
7113 	}
7114 
7115 }
7116 
7117 /++
7118 
7119 +/
7120 class TabWidgetPage : Widget {
7121 	string title;
7122 	this(string title, Widget parent) {
7123 		this.title = title;
7124 		this.tabStop = false;
7125 		super(parent);
7126 
7127 		///*
7128 		version(win32_widgets) {
7129 			createWin32Window(this, Win32Class!"arsd_minigui_TabWidgetPage"w, "", 0);
7130 		}
7131 		//*/
7132 	}
7133 
7134 	override int minHeight() {
7135 		int sum = 0;
7136 		foreach(child; children)
7137 			sum += child.minHeight();
7138 		return sum;
7139 	}
7140 }
7141 
7142 version(none)
7143 /++
7144 	A collapsable sidebar is a container that shows if its assigned width is greater than its minimum and otherwise shows as a button.
7145 
7146 	I think I need to modify the layout algorithms to support this.
7147 +/
7148 class CollapsableSidebar : Widget {
7149 
7150 }
7151 
7152 /// Stacks the widgets vertically, taking all the available width for each child.
7153 class VerticalLayout : Layout {
7154 	// most of this is intentionally blank - widget's default is vertical layout right now
7155 	///
7156 	this(Widget parent) { super(parent); }
7157 
7158 	/++
7159 		Sets a max width for the layout so you don't have to subclass. The max width
7160 		is in device-independent pixels, meaning pixels at 96 dpi that are auto-scaled.
7161 
7162 		History:
7163 			Added November 29, 2021 (dub v10.5)
7164 	+/
7165 	this(int maxWidth, Widget parent) {
7166 		this.mw = maxWidth;
7167 		super(parent);
7168 	}
7169 
7170 	private int mw = int.max;
7171 
7172 	override int maxWidth() { return scaleWithDpi(mw); }
7173 }
7174 
7175 /// Stacks the widgets horizontally, taking all the available height for each child.
7176 class HorizontalLayout : Layout {
7177 	///
7178 	this(Widget parent) { super(parent); }
7179 
7180 	/++
7181 		Sets a max height for the layout so you don't have to subclass. The max height
7182 		is in device-independent pixels, meaning pixels at 96 dpi that are auto-scaled.
7183 
7184 		History:
7185 			Added November 29, 2021 (dub v10.5)
7186 	+/
7187 	this(int maxHeight, Widget parent) {
7188 		this.mh = maxHeight;
7189 		super(parent);
7190 	}
7191 
7192 	private int mh = 0;
7193 
7194 
7195 
7196 	override void recomputeChildLayout() {
7197 		.recomputeChildLayout!"width"(this);
7198 	}
7199 
7200 	override int minHeight() {
7201 		int largest = 0;
7202 		int margins = 0;
7203 		int lastMargin = 0;
7204 		foreach(child; children) {
7205 			auto mh = child.minHeight();
7206 			if(mh > largest)
7207 				largest = mh;
7208 			margins += mymax(lastMargin, child.marginTop());
7209 			lastMargin = child.marginBottom();
7210 		}
7211 		return largest + margins;
7212 	}
7213 
7214 	override int maxHeight() {
7215 		if(mh != 0)
7216 			return mymax(minHeight, scaleWithDpi(mh));
7217 
7218 		int largest = 0;
7219 		int margins = 0;
7220 		int lastMargin = 0;
7221 		foreach(child; children) {
7222 			auto mh = child.maxHeight();
7223 			if(mh == int.max)
7224 				return int.max;
7225 			if(mh > largest)
7226 				largest = mh;
7227 			margins += mymax(lastMargin, child.marginTop());
7228 			lastMargin = child.marginBottom();
7229 		}
7230 		return largest + margins;
7231 	}
7232 
7233 	override int heightStretchiness() {
7234 		int max;
7235 		foreach(child; children) {
7236 			auto c = child.heightStretchiness;
7237 			if(c > max)
7238 				max = c;
7239 		}
7240 		return max;
7241 	}
7242 
7243 }
7244 
7245 version(win32_widgets)
7246 private
7247 extern(Windows)
7248 LRESULT DoubleBufferWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) nothrow {
7249 	Widget* pwin = hwnd in Widget.nativeMapping;
7250 	if(pwin is null)
7251 		return DefWindowProc(hwnd, message, wparam, lparam);
7252 	SimpleWindow win = pwin.simpleWindowWrappingHwnd;
7253 	if(win is null)
7254 		return DefWindowProc(hwnd, message, wparam, lparam);
7255 
7256 	switch(message) {
7257 		case WM_SIZE:
7258 			auto width = LOWORD(lparam);
7259 			auto height = HIWORD(lparam);
7260 
7261 			auto hdc = GetDC(hwnd);
7262 			auto hdcBmp = CreateCompatibleDC(hdc);
7263 
7264 			// FIXME: could this be more efficient? it never relinquishes a large bitmap
7265 			if(width > win.bmpWidth || height > win.bmpHeight) {
7266 				auto oldBuffer = win.buffer;
7267 				win.buffer = CreateCompatibleBitmap(hdc, width, height);
7268 
7269 				if(oldBuffer)
7270 					DeleteObject(oldBuffer);
7271 
7272 				win.bmpWidth = width;
7273 				win.bmpHeight = height;
7274 			}
7275 
7276 			// just always erase it upon resizing so minigui can draw over with a clean slate
7277 			auto oldBmp = SelectObject(hdcBmp, win.buffer);
7278 
7279 			auto brush = GetSysColorBrush(COLOR_3DFACE);
7280 			RECT r;
7281 			r.left = 0;
7282 			r.top = 0;
7283 			r.right = width;
7284 			r.bottom = height;
7285 			FillRect(hdcBmp, &r, brush);
7286 
7287 			SelectObject(hdcBmp, oldBmp);
7288 			DeleteDC(hdcBmp);
7289 			ReleaseDC(hwnd, hdc);
7290 		break;
7291 		case WM_PAINT:
7292 			if(win.buffer is null)
7293 				goto default;
7294 
7295 			BITMAP bm;
7296 			PAINTSTRUCT ps;
7297 
7298 			HDC hdc = BeginPaint(hwnd, &ps);
7299 
7300 			HDC hdcMem = CreateCompatibleDC(hdc);
7301 			HBITMAP hbmOld = SelectObject(hdcMem, win.buffer);
7302 
7303 			GetObject(win.buffer, bm.sizeof, &bm);
7304 
7305 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
7306 
7307 			SelectObject(hdcMem, hbmOld);
7308 			DeleteDC(hdcMem);
7309 			EndPaint(hwnd, &ps);
7310 		break;
7311 		default:
7312 			return DefWindowProc(hwnd, message, wparam, lparam);
7313 	}
7314 
7315 	return 0;
7316 }
7317 
7318 private wstring Win32Class(wstring name)() {
7319 	static bool classRegistered;
7320 	if(!classRegistered) {
7321 		HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
7322 		WNDCLASSEX wc;
7323 		wc.cbSize = wc.sizeof;
7324 		wc.hInstance = hInstance;
7325 		wc.hbrBackground = cast(HBRUSH) (COLOR_3DFACE+1); // GetStockObject(WHITE_BRUSH);
7326 		wc.lpfnWndProc = &DoubleBufferWndProc;
7327 		wc.lpszClassName = name.ptr;
7328 		if(!RegisterClassExW(&wc))
7329 			throw new Exception("RegisterClass ");// ~ to!string(GetLastError()));
7330 		classRegistered = true;
7331 	}
7332 
7333 		return name;
7334 }
7335 
7336 /+
7337 version(win32_widgets)
7338 extern(Windows)
7339 private
7340 LRESULT CustomDrawWindowProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
7341 	switch(iMessage) {
7342 		case WM_PAINT:
7343 			if(auto te = hWnd in Widget.nativeMapping) {
7344 				try {
7345 					//te.redraw();
7346 					writeln(te, " drawing");
7347 				} catch(Exception) {}
7348 			}
7349 			return DefWindowProc(hWnd, iMessage, wParam, lParam);
7350 		default:
7351 			return DefWindowProc(hWnd, iMessage, wParam, lParam);
7352 	}
7353 }
7354 +/
7355 
7356 
7357 /++
7358 	A widget specifically designed to hold other widgets.
7359 
7360 	History:
7361 		Added July 1, 2021
7362 +/
7363 class ContainerWidget : Widget {
7364 	this(Widget parent) {
7365 		super(parent);
7366 		this.tabStop = false;
7367 
7368 		version(win32_widgets) {
7369 			createWin32Window(this, Win32Class!"arsd_minigui_ContainerWidget"w, "", 0);
7370 		}
7371 	}
7372 }
7373 
7374 /++
7375 	A widget that takes your widget, puts scroll bars around it, and sends
7376 	messages to it when the user scrolls. Unlike [ScrollableWidget], it makes
7377 	no effort to automatically scroll or clip its child widgets - it just sends
7378 	the messages.
7379 
7380 
7381 	A ScrollMessageWidget notifies you with a [ScrollEvent] that it has changed.
7382 	The scroll coordinates are all given in a unit you interpret as you wish. One
7383 	of these units is moved on each press of the arrow buttons and represents the
7384 	smallest amount the user can scroll. The intention is for this to be one line,
7385 	one item in a list, one row in a table, etc. Whatever makes sense for your widget
7386 	in each direction that the user might be interested in.
7387 
7388 	You can set a "page size" with the [step] property. (Yes, I regret the name...)
7389 	This is the amount it jumps when the user pressed page up and page down, or clicks
7390 	in the exposed part of the scroll bar.
7391 
7392 	You should add child content to the ScrollMessageWidget. However, it is important to
7393 	note that the coordinates are always independent of the scroll position! It is YOUR
7394 	responsibility to do any necessary transforms, clipping, etc., while drawing the
7395 	content and interpreting mouse events if they are supposed to change with the scroll.
7396 	This is in contrast to the (likely to be deprecated) [ScrollableWidget], which tries
7397 	to maintain the illusion that there's an infinite space. The [ScrollMessageWidget] gives
7398 	you more control (which can be considerably more efficient and adapted to your actual data)
7399 	at the expense of you also needing to be aware of its reality.
7400 
7401 	Please note that it does NOT react to mouse wheel events or various keyboard events as of
7402 	version 10.3. Maybe this will change in the future.... but for now you must call
7403 	[addDefaultKeyboardListeners] and/or [addDefaultWheelListeners] or set something up yourself.
7404 +/
7405 class ScrollMessageWidget : Widget {
7406 	this(Widget parent) {
7407 		super(parent);
7408 
7409 		container = new Widget(this);
7410 		hsb = new HorizontalScrollbar(this);
7411 		vsb = new VerticalScrollbar(this);
7412 
7413 		hsb.addEventListener("scrolltonextline", {
7414 			hsb.setPosition(hsb.position + movementPerButtonClickH_);
7415 			notify();
7416 		});
7417 		hsb.addEventListener("scrolltopreviousline", {
7418 			hsb.setPosition(hsb.position - movementPerButtonClickH_);
7419 			notify();
7420 		});
7421 		vsb.addEventListener("scrolltonextline", {
7422 			vsb.setPosition(vsb.position + movementPerButtonClickV_);
7423 			notify();
7424 		});
7425 		vsb.addEventListener("scrolltopreviousline", {
7426 			vsb.setPosition(vsb.position - movementPerButtonClickV_);
7427 			notify();
7428 		});
7429 		hsb.addEventListener("scrolltonextpage", {
7430 			hsb.setPosition(hsb.position + hsb.step_);
7431 			notify();
7432 		});
7433 		hsb.addEventListener("scrolltopreviouspage", {
7434 			hsb.setPosition(hsb.position - hsb.step_);
7435 			notify();
7436 		});
7437 		vsb.addEventListener("scrolltonextpage", {
7438 			vsb.setPosition(vsb.position + vsb.step_);
7439 			notify();
7440 		});
7441 		vsb.addEventListener("scrolltopreviouspage", {
7442 			vsb.setPosition(vsb.position - vsb.step_);
7443 			notify();
7444 		});
7445 		hsb.addEventListener("scrolltoposition", (Event event) {
7446 			hsb.setPosition(event.intValue);
7447 			notify();
7448 		});
7449 		vsb.addEventListener("scrolltoposition", (Event event) {
7450 			vsb.setPosition(event.intValue);
7451 			notify();
7452 		});
7453 
7454 
7455 		tabStop = false;
7456 		container.tabStop = false;
7457 		magic = true;
7458 	}
7459 
7460 	private int movementPerButtonClickH_ = 1;
7461 	private int movementPerButtonClickV_ = 1;
7462 	public void movementPerButtonClick(int h, int v) {
7463 		movementPerButtonClickH_ = h;
7464 		movementPerButtonClickV_ = v;
7465 	}
7466 
7467 	/++
7468 		Add default event listeners for keyboard and mouse wheel scrolling shortcuts.
7469 
7470 
7471 		The defaults for [addDefaultWheelListeners] are:
7472 
7473 			$(LIST
7474 				* Mouse wheel scrolls vertically
7475 				* Alt key + mouse wheel scrolls horiontally
7476 				* Shift + mouse wheel scrolls faster.
7477 				* Any mouse click or wheel event will focus the inner widget if it has `tabStop = true`
7478 			)
7479 
7480 		The defaults for [addDefaultKeyboardListeners] are:
7481 
7482 			$(LIST
7483 				* Arrow keys scroll by the given amounts
7484 				* Shift+arrow keys scroll by the given amounts times the given shiftMultiplier
7485 				* Page up and down scroll by the vertical viewable area
7486 				* Home and end scroll to the start and end of the verticle viewable area.
7487 				* Alt + page up / page down / home / end will horizonally scroll instead of vertical.
7488 			)
7489 
7490 		My recommendation is to change the scroll amounts if you are scrolling by pixels, but otherwise keep them at one line.
7491 
7492 		Params:
7493 			horizontalArrowScrollAmount =
7494 			verticalArrowScrollAmount =
7495 			verticalWheelScrollAmount = how much should be scrolled vertically on each tick of the mouse wheel
7496 			horizontalWheelScrollAmount = how much should be scrolled horizontally when alt is held on each tick of the mouse wheel
7497 			shiftMultiplier = multiplies the scroll amount by this when shift is held
7498 	+/
7499 	void addDefaultKeyboardListeners(int verticalArrowScrollAmount = 1, int horizontalArrowScrollAmount = 1, int shiftMultiplier = 3) {
7500 		auto _this = this;
7501 
7502 		container.addEventListener((scope KeyDownEvent ke) {
7503 			switch(ke.key) {
7504 				case Key.Left:
7505 					_this.scrollLeft(horizontalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7506 				break;
7507 				case Key.Right:
7508 					_this.scrollRight(horizontalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7509 				break;
7510 				case Key.Up:
7511 					_this.scrollUp(verticalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7512 				break;
7513 				case Key.Down:
7514 					_this.scrollDown(verticalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7515 				break;
7516 				case Key.PageUp:
7517 					if(ke.altKey)
7518 						_this.scrollLeft(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7519 					else
7520 						_this.scrollUp(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7521 				break;
7522 				case Key.PageDown:
7523 					if(ke.altKey)
7524 						_this.scrollRight(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7525 					else
7526 						_this.scrollDown(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7527 				break;
7528 				case Key.Home:
7529 					if(ke.altKey)
7530 						_this.scrollLeft(short.max * 16);
7531 					else
7532 						_this.scrollUp(short.max * 16);
7533 				break;
7534 				case Key.End:
7535 					if(ke.altKey)
7536 						_this.scrollRight(short.max * 16);
7537 					else
7538 						_this.scrollDown(short.max * 16);
7539 				break;
7540 
7541 				default:
7542 					// ignore, not for us.
7543 			}
7544 
7545 		});
7546 	}
7547 
7548 	/// ditto
7549 	void addDefaultWheelListeners(int verticalWheelScrollAmount = 1, int horizontalWheelScrollAmount = 1, int shiftMultiplier = 3) {
7550 		auto _this = this;
7551 		container.addEventListener((scope ClickEvent ce) {
7552 
7553 			if(ce.target && ce.target.tabStop)
7554 				ce.target.focus();
7555 
7556 			// ctrl is reserved for the application
7557 			if(ce.ctrlKey)
7558 				return;
7559 
7560 			if(horizontalWheelScrollAmount == 0 && ce.altKey)
7561 				return;
7562 
7563 			if(shiftMultiplier == 0 && ce.shiftKey)
7564 				return;
7565 
7566 			if(ce.button == MouseButton.wheelDown) {
7567 				if(ce.altKey)
7568 					_this.scrollRight(horizontalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7569 				else
7570 					_this.scrollDown(verticalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7571 			} else if(ce.button == MouseButton.wheelUp) {
7572 				if(ce.altKey)
7573 					_this.scrollLeft(horizontalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7574 				else
7575 					_this.scrollUp(verticalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7576 			}
7577 		});
7578 	}
7579 
7580 	/++
7581 		Scrolls the given amount.
7582 
7583 		History:
7584 			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.
7585 	+/
7586 	void scrollUp(int amount = 1) {
7587 		vsb.setPosition(vsb.position - amount);
7588 		notify();
7589 	}
7590 	/// ditto
7591 	void scrollDown(int amount = 1) {
7592 		vsb.setPosition(vsb.position + amount);
7593 		notify();
7594 	}
7595 	/// ditto
7596 	void scrollLeft(int amount = 1) {
7597 		hsb.setPosition(hsb.position - amount);
7598 		notify();
7599 	}
7600 	/// ditto
7601 	void scrollRight(int amount = 1) {
7602 		hsb.setPosition(hsb.position + amount);
7603 		notify();
7604 	}
7605 
7606 	///
7607 	VerticalScrollbar verticalScrollBar() { return vsb; }
7608 	///
7609 	HorizontalScrollbar horizontalScrollBar() { return hsb; }
7610 
7611 	void notify() {
7612 		static bool insideNotify;
7613 
7614 		if(insideNotify)
7615 			return; // avoid the recursive call, even if it isn't strictly correct
7616 
7617 		insideNotify = true;
7618 		scope(exit) insideNotify = false;
7619 
7620 		this.emit!ScrollEvent();
7621 	}
7622 
7623 	mixin Emits!ScrollEvent;
7624 
7625 	///
7626 	Point position() {
7627 		return Point(hsb.position, vsb.position);
7628 	}
7629 
7630 	///
7631 	void setPosition(int x, int y) {
7632 		hsb.setPosition(x);
7633 		vsb.setPosition(y);
7634 	}
7635 
7636 	///
7637 	void setPageSize(int unitsX, int unitsY) {
7638 		hsb.setStep(unitsX);
7639 		vsb.setStep(unitsY);
7640 	}
7641 
7642 	/// Always call this BEFORE setViewableArea
7643 	void setTotalArea(int width, int height) {
7644 		hsb.setMax(width);
7645 		vsb.setMax(height);
7646 	}
7647 
7648 	/++
7649 		Always set the viewable area AFTER setitng the total area if you are going to change both.
7650 		NEVER call this from inside a scroll event. This includes through recomputeChildLayout.
7651 		If you need to do that, use [queueRecomputeChildLayout].
7652 	+/
7653 	void setViewableArea(int width, int height) {
7654 
7655 		// actually there IS A need to dothis cuz the max might have changed since then
7656 		//if(width == hsb.viewableArea_ && height == vsb.viewableArea_)
7657 			//return; // no need to do what is already done
7658 		hsb.setViewableArea(width);
7659 		vsb.setViewableArea(height);
7660 
7661 		bool needsNotify = false;
7662 
7663 		// FIXME: if at any point the rhs is outside the scrollbar, we need
7664 		// to reset to 0. but it should remember the old position in case the
7665 		// window resizes again, so it can kinda return ot where it was.
7666 		//
7667 		// so there's an inner position and a exposed position. the exposed one is always in bounds and thus may be (0,0)
7668 		if(width >= hsb.max) {
7669 			// there's plenty of room to display it all so we need to reset to zero
7670 			// FIXME: adjust so it matches the note above
7671 			hsb.setPosition(0);
7672 			needsNotify = true;
7673 		}
7674 		if(height >= vsb.max) {
7675 			// there's plenty of room to display it all so we need to reset to zero
7676 			// FIXME: adjust so it matches the note above
7677 			vsb.setPosition(0);
7678 			needsNotify = true;
7679 		}
7680 		if(needsNotify)
7681 			notify();
7682 	}
7683 
7684 	private bool magic;
7685 	override void addChild(Widget w, int position = int.max) {
7686 		if(magic)
7687 			container.addChild(w, position);
7688 		else
7689 			super.addChild(w, position);
7690 	}
7691 
7692 	override void recomputeChildLayout() {
7693 		if(hsb is null || vsb is null || container is null) return;
7694 
7695 		registerMovement();
7696 
7697 		enum BUTTON_SIZE = 16;
7698 
7699 		hsb.height = scaleWithDpi(BUTTON_SIZE); // FIXME? are tese 16s sane?
7700 		hsb.x = 0;
7701 		hsb.y = this.height - hsb.height;
7702 
7703 		vsb.width = scaleWithDpi(BUTTON_SIZE); // FIXME?
7704 		vsb.x = this.width - vsb.width;
7705 		vsb.y = 0;
7706 
7707 		auto vsb_width = vsb.showing ? vsb.width : 0;
7708 		auto hsb_height = hsb.showing ? hsb.height : 0;
7709 
7710 		hsb.width = this.width - vsb_width;
7711 		vsb.height = this.height - hsb_height;
7712 
7713 		hsb.recomputeChildLayout();
7714 		vsb.recomputeChildLayout();
7715 
7716 		if(this.header is null) {
7717 			container.x = 0;
7718 			container.y = 0;
7719 			container.width = this.width - vsb_width;
7720 			container.height = this.height - hsb_height;
7721 			container.recomputeChildLayout();
7722 		} else {
7723 			header.x = 0;
7724 			header.y = 0;
7725 			header.width = this.width - vsb_width;
7726 			header.height = scaleWithDpi(BUTTON_SIZE); // size of the button
7727 			header.recomputeChildLayout();
7728 
7729 			container.x = 0;
7730 			container.y = scaleWithDpi(BUTTON_SIZE);
7731 			container.width = this.width - vsb_width;
7732 			container.height = this.height - hsb_height - scaleWithDpi(BUTTON_SIZE);
7733 			container.recomputeChildLayout();
7734 		}
7735 	}
7736 
7737 	private HorizontalScrollbar hsb;
7738 	private VerticalScrollbar vsb;
7739 	Widget container;
7740 	private Widget header;
7741 
7742 	/++
7743 		Adds a fixed-size "header" widget. This will be positioned to align with the scroll up button.
7744 
7745 		History:
7746 			Added September 27, 2021 (dub v10.3)
7747 	+/
7748 	Widget getHeader() {
7749 		if(this.header is null) {
7750 			magic = false;
7751 			scope(exit) magic = true;
7752 			this.header = new Widget(this);
7753 			recomputeChildLayout();
7754 		}
7755 		return this.header;
7756 	}
7757 
7758 	/++
7759 		Makes an effort to ensure as much of `rect` is visible as possible, scrolling if necessary.
7760 
7761 		History:
7762 			Added January 3, 2023 (dub v11.0)
7763 	+/
7764 	void scrollIntoView(Rectangle rect) {
7765 		Rectangle viewRectangle = Rectangle(position, Size(hsb.viewableArea_, vsb.viewableArea_));
7766 
7767 		// import std.stdio;writeln(viewRectangle, "\n", rect, " ", viewRectangle.contains(rect.lowerRight - Point(1, 1)));
7768 
7769 		// the lower right is exclusive normally
7770 		auto test = rect.lowerRight;
7771 		if(test.x > 0) test.x--;
7772 		if(test.y > 0) test.y--;
7773 
7774 		if(!viewRectangle.contains(test) || !viewRectangle.contains(rect.upperLeft)) {
7775 			// try to scroll only one dimension at a time if we can
7776 			if(!viewRectangle.contains(Point(test.x, position.y)) || !viewRectangle.contains(Point(rect.upperLeft.x, position.y)))
7777 				setPosition(rect.upperLeft.x, position.y);
7778 			if(!viewRectangle.contains(Point(position.x, test.y)) || !viewRectangle.contains(Point(position.x, rect.upperLeft.y)))
7779 				setPosition(position.x, rect.upperLeft.y);
7780 		}
7781 
7782 	}
7783 
7784 	override int minHeight() {
7785 		int min = container ? container.minHeight : 0;
7786 		if(header !is null)
7787 			min += header.minHeight;
7788 		if(horizontalScrollBar.showing)
7789 			min += horizontalScrollBar.minHeight;
7790 		return min;
7791 	}
7792 
7793 	override int maxHeight() {
7794 		int max = container ? container.maxHeight : int.max;
7795 		if(max == int.max)
7796 			return max;
7797 		if(horizontalScrollBar.showing)
7798 			max += horizontalScrollBar.minHeight;
7799 		return max;
7800 	}
7801 }
7802 
7803 /++
7804 	$(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")
7805 	$(IMG //arsdnet.net/minigui-screenshots/linux/ScrollMessageWidget.png, Same thing, but in the default Linux theme.)
7806 +/
7807 version(minigui_screenshots)
7808 @Screenshot("ScrollMessageWidget")
7809 unittest {
7810 	auto window = new Window("ScrollMessageWidget");
7811 
7812 	auto smw = new ScrollMessageWidget(window);
7813 	smw.addDefaultKeyboardListeners();
7814 	smw.addDefaultWheelListeners();
7815 
7816 	window.loop();
7817 }
7818 
7819 /++
7820 	Bypasses automatic layout for its children, using manual positioning and sizing only.
7821 	While you need to manually position them, you must ensure they are inside the StaticLayout's
7822 	bounding box to avoid undefined behavior.
7823 
7824 	You should almost never use this.
7825 +/
7826 class StaticLayout : Layout {
7827 	///
7828 	this(Widget parent) { super(parent); }
7829 	override void recomputeChildLayout() {
7830 		registerMovement();
7831 		foreach(child; children)
7832 			child.recomputeChildLayout();
7833 	}
7834 }
7835 
7836 /++
7837 	Bypasses automatic positioning when being laid out. It is your responsibility to make
7838 	room for this widget in the parent layout.
7839 
7840 	Its children are laid out normally, unless there is exactly one, in which case it takes
7841 	on the full size of the `StaticPosition` object (if you plan to put stuff on the edge, you
7842 	can do that with `padding`).
7843 +/
7844 class StaticPosition : Layout {
7845 	///
7846 	this(Widget parent) { super(parent); }
7847 
7848 	override void recomputeChildLayout() {
7849 		registerMovement();
7850 		if(this.children.length == 1) {
7851 			auto child = children[0];
7852 			child.x = 0;
7853 			child.y = 0;
7854 			child.width = this.width;
7855 			child.height = this.height;
7856 			child.recomputeChildLayout();
7857 		} else
7858 		foreach(child; children)
7859 			child.recomputeChildLayout();
7860 	}
7861 
7862 	alias width = typeof(super).width;
7863 	alias height = typeof(super).height;
7864 
7865 	@property int width(int w) @nogc pure @safe nothrow {
7866 		return this._width = w;
7867 	}
7868 
7869 	@property int height(int w) @nogc pure @safe nothrow {
7870 		return this._height = w;
7871 	}
7872 
7873 }
7874 
7875 /++
7876 	FixedPosition is like [StaticPosition], but its coordinates
7877 	are always relative to the viewport, meaning they do not scroll with
7878 	the parent content.
7879 +/
7880 class FixedPosition : StaticPosition {
7881 	///
7882 	this(Widget parent) { super(parent); }
7883 }
7884 
7885 version(win32_widgets)
7886 int processWmCommand(HWND parentWindow, HWND handle, ushort cmd, ushort idm) {
7887 	if(true) {
7888 		// cmd == 0 = menu, cmd == 1 = accelerator
7889 		if(auto item = idm in Action.mapping) {
7890 			foreach(handler; (*item).triggered)
7891 				handler();
7892 		/*
7893 			auto event = new Event("triggered", *item);
7894 			event.button = idm;
7895 			event.dispatch();
7896 		*/
7897 			return 0;
7898 		}
7899 	}
7900 	if(handle)
7901 	if(auto widgetp = handle in Widget.nativeMapping) {
7902 		(*widgetp).handleWmCommand(cmd, idm);
7903 		return 0;
7904 	}
7905 	return 1;
7906 }
7907 
7908 
7909 ///
7910 class Window : Widget {
7911 	int mouseCaptureCount = 0;
7912 	Widget mouseCapturedBy;
7913 	void captureMouse(Widget byWhom) {
7914 		assert(mouseCapturedBy is null || byWhom is mouseCapturedBy);
7915 		mouseCaptureCount++;
7916 		mouseCapturedBy = byWhom;
7917 		win.grabInput();
7918 	}
7919 	void releaseMouseCapture() {
7920 		mouseCaptureCount--;
7921 		mouseCapturedBy = null;
7922 		win.releaseInputGrab();
7923 	}
7924 
7925 	/++
7926 		Sets the window icon which is often seen in title bars and taskbars.
7927 
7928 		History:
7929 			Added April 5, 2022 (dub v10.8)
7930 	+/
7931 	@property void icon(MemoryImage icon) {
7932 		if(win && icon)
7933 			win.icon = icon;
7934 	}
7935 
7936 	///
7937 	@scriptable
7938 	@property bool focused() {
7939 		return win.focused;
7940 	}
7941 
7942 	static class Style : Widget.Style {
7943 		override WidgetBackground background() {
7944 			version(custom_widgets)
7945 				return WidgetBackground(WidgetPainter.visualTheme.windowBackgroundColor);
7946 			else version(win32_widgets)
7947 				return WidgetBackground(Color.transparent);
7948 			else static assert(0);
7949 		}
7950 	}
7951 	mixin OverrideStyle!Style;
7952 
7953 	/++
7954 		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.
7955 	+/
7956 	static int lineHeight() {
7957 		OperatingSystemFont font;
7958 		if(auto vt = WidgetPainter.visualTheme) {
7959 			font = vt.defaultFontCached();
7960 		}
7961 
7962 		if(font is null) {
7963 			static int defaultHeightCache;
7964 			if(defaultHeightCache == 0) {
7965 				font = new OperatingSystemFont;
7966 				font.loadDefault;
7967 				defaultHeightCache = font.height();// * 5 / 4;
7968 			}
7969 			return defaultHeightCache;
7970 		}
7971 
7972 		return font.height();// * 5 / 4;
7973 	}
7974 
7975 	Widget focusedWidget;
7976 
7977 	private SimpleWindow win_;
7978 
7979 	@property {
7980 		/++
7981 			Provides access to the underlying [SimpleWindow]. Note that changing properties on this window may disconnect minigui's event dispatchers.
7982 
7983 			History:
7984 				Prior to June 21, 2021, it was a public (but undocumented) member. Now it a semi-protected property.
7985 		+/
7986 		public SimpleWindow win() {
7987 			return win_;
7988 		}
7989 		///
7990 		protected void win(SimpleWindow w) {
7991 			win_ = w;
7992 		}
7993 	}
7994 
7995 	/// YOU ALMOST CERTAINLY SHOULD NOT USE THIS. This is really only for special purposes like pseudowindows or popup windows doing their own thing.
7996 	this(Widget p) {
7997 		tabStop = false;
7998 		super(p);
7999 	}
8000 
8001 	private void actualRedraw() {
8002 		if(recomputeChildLayoutRequired)
8003 			recomputeChildLayoutEntry();
8004 		if(!showing) return;
8005 
8006 		assert(parentWindow !is null);
8007 
8008 		auto w = drawableWindow;
8009 		if(w is null)
8010 			w = parentWindow.win;
8011 
8012 		if(w.closed())
8013 			return;
8014 
8015 		auto ugh = this.parent;
8016 		int lox, loy;
8017 		while(ugh) {
8018 			lox += ugh.x;
8019 			loy += ugh.y;
8020 			ugh = ugh.parent;
8021 		}
8022 		auto painter = w.draw(true);
8023 		privatePaint(WidgetPainter(painter, this), lox, loy, Rectangle(0, 0, int.max, int.max), false, willDraw());
8024 		// RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
8025 	}
8026 
8027 
8028 	private bool skipNextChar = false;
8029 
8030 	/++
8031 		Creates a window from an existing [SimpleWindow]. This constructor attaches various event handlers to the SimpleWindow object which may overwrite your existing handlers.
8032 
8033 		This constructor is intended primarily for internal use and may be changed to `protected` later.
8034 	+/
8035 	this(SimpleWindow win) {
8036 
8037 		static if(UsingSimpledisplayX11) {
8038 			win.discardAdditionalConnectionState = &discardXConnectionState;
8039 			win.recreateAdditionalConnectionState = &recreateXConnectionState;
8040 		}
8041 
8042 		tabStop = false;
8043 		super(null);
8044 		this.win = win;
8045 
8046 		win.addEventListener((Widget.RedrawEvent) {
8047 			if(win.eventQueued!RecomputeEvent) {
8048 				// writeln("skipping");
8049 				return; // let the recompute event do the actual redraw
8050 			}
8051 			this.actualRedraw();
8052 		});
8053 
8054 		win.addEventListener((Widget.RecomputeEvent) {
8055 			recomputeChildLayoutEntry();
8056 			if(win.eventQueued!RedrawEvent)
8057 				return; // let the queued one do it
8058 			else {
8059 				// writeln("drawing");
8060 				this.actualRedraw(); // if not queued, it needs to be done now anyway
8061 			}
8062 		});
8063 
8064 		this.width = win.width;
8065 		this.height = win.height;
8066 		this.parentWindow = this;
8067 
8068 		win.closeQuery = () {
8069 			if(this.emit!ClosingEvent())
8070 				win.close();
8071 		};
8072 		win.onClosing = () {
8073 			this.emit!ClosedEvent();
8074 		};
8075 
8076 		win.windowResized = (int w, int h) {
8077 			this.width = w;
8078 			this.height = h;
8079 			recomputeChildLayout();
8080 			// this causes a HUGE performance problem for no apparent benefit, hence the commenting
8081 			//version(win32_widgets)
8082 				//InvalidateRect(hwnd, null, true);
8083 			redraw();
8084 		};
8085 
8086 		win.onFocusChange = (bool getting) {
8087 			if(this.focusedWidget) {
8088 				if(getting) {
8089 					this.focusedWidget.emit!FocusEvent();
8090 					this.focusedWidget.emit!FocusInEvent();
8091 				} else {
8092 					this.focusedWidget.emit!BlurEvent();
8093 					this.focusedWidget.emit!FocusOutEvent();
8094 				}
8095 			}
8096 
8097 			if(getting) {
8098 				this.emit!FocusEvent();
8099 				this.emit!FocusInEvent();
8100 			} else {
8101 				this.emit!BlurEvent();
8102 				this.emit!FocusOutEvent();
8103 			}
8104 		};
8105 
8106 		win.onDpiChanged = {
8107 			this.queueRecomputeChildLayout();
8108 			auto event = new DpiChangedEvent(this);
8109 			event.sendDirectly();
8110 
8111 			privateDpiChanged();
8112 		};
8113 
8114 		win.setEventHandlers(
8115 			(MouseEvent e) {
8116 				dispatchMouseEvent(e);
8117 			},
8118 			(KeyEvent e) {
8119 				//writefln("%x   %s", cast(uint) e.key, e.key);
8120 				dispatchKeyEvent(e);
8121 			},
8122 			(dchar e) {
8123 				if(e == 13) e = 10; // hack?
8124 				if(e == 127) return; // linux sends this, windows doesn't. we don't want it.
8125 				dispatchCharEvent(e);
8126 			},
8127 		);
8128 
8129 		addEventListener("char", (Widget, Event ev) {
8130 			if(skipNextChar) {
8131 				ev.preventDefault();
8132 				skipNextChar = false;
8133 			}
8134 		});
8135 
8136 		version(win32_widgets)
8137 		win.handleNativeEvent = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
8138 			if(hwnd !is this.win.impl.hwnd)
8139 				return 1; // we don't care... pass it on
8140 			auto ret = WindowProcedureHelper(this, hwnd, msg, wParam, lParam, mustReturn);
8141 			if(mustReturn)
8142 				return ret;
8143 			return 1; // pass it on
8144 		};
8145 
8146 		if(Window.newWindowCreated)
8147 			Window.newWindowCreated(this);
8148 	}
8149 
8150 	version(custom_widgets)
8151 	override void defaultEventHandler_click(ClickEvent event) {
8152 		if(event.target && event.target.tabStop)
8153 			event.target.focus();
8154 	}
8155 
8156 	private static void delegate(Window) newWindowCreated;
8157 
8158 	version(win32_widgets)
8159 	override void paint(WidgetPainter painter) {
8160 		/*
8161 		RECT rect;
8162 		rect.right = this.width;
8163 		rect.bottom = this.height;
8164 		DrawThemeBackground(theme, painter.impl.hdc, 4, 1, &rect, null);
8165 		*/
8166 		// 3dface is used as window backgrounds by Windows too, so that's why I'm using it here
8167 		auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
8168 		auto p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
8169 		// since the pen is null, to fill the whole space, we need the +1 on both.
8170 		gdi.Rectangle(painter.impl.hdc, 0, 0, this.width + 1, this.height + 1);
8171 		SelectObject(painter.impl.hdc, p);
8172 		SelectObject(painter.impl.hdc, b);
8173 	}
8174 	version(custom_widgets)
8175 	override void paint(WidgetPainter painter) {
8176 		auto cs = getComputedStyle();
8177 		painter.fillColor = cs.windowBackgroundColor;
8178 		painter.outlineColor = cs.windowBackgroundColor;
8179 		painter.drawRectangle(Point(0, 0), this.width, this.height);
8180 	}
8181 
8182 
8183 	override void defaultEventHandler_keydown(KeyDownEvent event) {
8184 		Widget _this = event.target;
8185 
8186 		if(event.key == Key.Tab) {
8187 			/* Window tab ordering is a recursive thingy with each group */
8188 
8189 			// FIXME inefficient
8190 			Widget[] helper(Widget p) {
8191 				if(p.hidden)
8192 					return null;
8193 				Widget[] childOrdering;
8194 
8195 				auto children = p.children.dup;
8196 
8197 				while(true) {
8198 					// UIs should be generally small, so gonna brute force it a little
8199 					// note that it must be a stable sort here; if all are index 0, it should be in order of declaration
8200 
8201 					Widget smallestTab;
8202 					foreach(ref c; children) {
8203 						if(c is null) continue;
8204 						if(smallestTab is null || c.tabOrder < smallestTab.tabOrder) {
8205 							smallestTab = c;
8206 							c = null;
8207 						}
8208 					}
8209 					if(smallestTab !is null) {
8210 						if(smallestTab.tabStop && !smallestTab.hidden)
8211 							childOrdering ~= smallestTab;
8212 						if(!smallestTab.hidden)
8213 							childOrdering ~= helper(smallestTab);
8214 					} else
8215 						break;
8216 
8217 				}
8218 
8219 				return childOrdering;
8220 			}
8221 
8222 			Widget[] tabOrdering = helper(this);
8223 
8224 			Widget recipient;
8225 
8226 			if(tabOrdering.length) {
8227 				bool seenThis = false;
8228 				Widget previous;
8229 				foreach(idx, child; tabOrdering) {
8230 					if(child is focusedWidget) {
8231 
8232 						if(event.shiftKey) {
8233 							if(idx == 0)
8234 								recipient = tabOrdering[$-1];
8235 							else
8236 								recipient = tabOrdering[idx - 1];
8237 							break;
8238 						}
8239 
8240 						seenThis = true;
8241 						if(idx + 1 == tabOrdering.length) {
8242 							// we're at the end, either move to the next group
8243 							// or start back over
8244 							recipient = tabOrdering[0];
8245 						}
8246 						continue;
8247 					}
8248 					if(seenThis) {
8249 						recipient = child;
8250 						break;
8251 					}
8252 					previous = child;
8253 				}
8254 			}
8255 
8256 			if(recipient !is null) {
8257 				//  writeln(typeid(recipient));
8258 				recipient.focus();
8259 
8260 				skipNextChar = true;
8261 			}
8262 		}
8263 
8264 		debug if(event.key == Key.F12) {
8265 			if(devTools) {
8266 				devTools.close();
8267 				devTools = null;
8268 			} else {
8269 				devTools = new DevToolWindow(this);
8270 				devTools.show();
8271 			}
8272 		}
8273 	}
8274 
8275 	debug DevToolWindow devTools;
8276 
8277 
8278 	/++
8279 		Creates a window. Please note windows are created in a hidden state, so you must call [show] or [loop] to get it to display.
8280 
8281 		History:
8282 			Prior to May 12, 2021, the default title was "D Application" (simpledisplay.d's default). After that, the default is `Runtime.args[0]` instead.
8283 
8284 			The width and height arguments were added to the overload that takes `string` first on June 21, 2021.
8285 	+/
8286 	this(int width = 500, int height = 500, string title = null) {
8287 		if(title is null) {
8288 			import core.runtime;
8289 			if(Runtime.args.length)
8290 				title = Runtime.args[0];
8291 		}
8292 		win = new SimpleWindow(width, height, title, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.normal, WindowFlags.dontAutoShow | WindowFlags.managesChildWindowFocus);
8293 
8294 		static if(UsingSimpledisplayX11) {
8295 		///+
8296 		// for input proxy
8297 		auto display = XDisplayConnection.get;
8298 		auto inputProxy = XCreateSimpleWindow(display, win.window, -1, -1, 1, 1, 0, 0, 0);
8299 		XSelectInput(display, inputProxy, EventMask.KeyPressMask | EventMask.KeyReleaseMask | EventMask.FocusChangeMask);
8300 		XMapWindow(display, inputProxy);
8301 		// writefln("input proxy: 0x%0x", inputProxy);
8302 		this.inputProxy = new SimpleWindow(inputProxy);
8303 
8304 		XEvent lastEvent;
8305 		this.inputProxy.handleNativeEvent = (XEvent ev) {
8306 			lastEvent = ev;
8307 			return 1;
8308 		};
8309 		this.inputProxy.setEventHandlers(
8310 			(MouseEvent e) {
8311 				dispatchMouseEvent(e);
8312 			},
8313 			(KeyEvent e) {
8314 				//writefln("%x   %s", cast(uint) e.key, e.key);
8315 				if(dispatchKeyEvent(e)) {
8316 					// FIXME: i should trap error
8317 					if(auto nw = cast(NestedChildWindowWidget) focusedWidget) {
8318 						auto thing = nw.focusableWindow();
8319 						if(thing && thing.window) {
8320 							lastEvent.xkey.window = thing.window;
8321 							// writeln("sending event ", lastEvent.xkey);
8322 							trapXErrors( {
8323 								XSendEvent(XDisplayConnection.get, thing.window, false, 0, &lastEvent);
8324 							});
8325 						}
8326 					}
8327 				}
8328 			},
8329 			(dchar e) {
8330 				if(e == 13) e = 10; // hack?
8331 				if(e == 127) return; // linux sends this, windows doesn't. we don't want it.
8332 				dispatchCharEvent(e);
8333 			},
8334 		);
8335 
8336 		this.inputProxy.populateXic();
8337 		// done
8338 		//+/
8339 		}
8340 
8341 
8342 
8343 		win.setRequestedInputFocus = &this.setRequestedInputFocus;
8344 
8345 		this(win);
8346 	}
8347 
8348 	SimpleWindow inputProxy;
8349 
8350 	private SimpleWindow setRequestedInputFocus() {
8351 		return inputProxy;
8352 	}
8353 
8354 	/// ditto
8355 	this(string title, int width = 500, int height = 500) {
8356 		this(width, height, title);
8357 	}
8358 
8359 	///
8360 	@property string title() { return parentWindow.win.title; }
8361 	///
8362 	@property void title(string title) { parentWindow.win.title = title; }
8363 
8364 	///
8365 	@scriptable
8366 	void close() {
8367 		win.close();
8368 		// I synchronize here upon window closing to ensure all child windows
8369 		// get updated too before the event loop. This avoids some random X errors.
8370 		static if(UsingSimpledisplayX11) {
8371 			runInGuiThread( {
8372 				XSync(XDisplayConnection.get, false);
8373 			});
8374 		}
8375 	}
8376 
8377 	bool dispatchKeyEvent(KeyEvent ev) {
8378 		auto wid = focusedWidget;
8379 		if(wid is null)
8380 			wid = this;
8381 		KeyEventBase event = ev.pressed ? new KeyDownEvent(wid) : new KeyUpEvent(wid);
8382 		event.originalKeyEvent = ev;
8383 		event.key = ev.key;
8384 		event.state = ev.modifierState;
8385 		event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false;
8386 		event.altKey = (ev.modifierState & ModifierState.alt) ? true : false;
8387 		event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false;
8388 		event.dispatch();
8389 
8390 		return !event.propagationStopped;
8391 	}
8392 
8393 	// returns true if propagation should continue into nested things.... prolly not a great thing to do.
8394 	bool dispatchCharEvent(dchar ch) {
8395 		if(focusedWidget) {
8396 			auto event = new CharEvent(focusedWidget, ch);
8397 			event.dispatch();
8398 			return !event.propagationStopped;
8399 		}
8400 		return true;
8401 	}
8402 
8403 	Widget mouseLastOver;
8404 	Widget mouseLastDownOn;
8405 	bool lastWasDoubleClick;
8406 	bool dispatchMouseEvent(MouseEvent ev) {
8407 		auto eleR = widgetAtPoint(this, ev.x, ev.y);
8408 		auto ele = eleR.widget;
8409 
8410 		auto captureEle = ele;
8411 
8412 		if(mouseCapturedBy !is null) {
8413 			if(ele !is mouseCapturedBy && !mouseCapturedBy.isAParentOf(ele))
8414 				captureEle = mouseCapturedBy;
8415 		}
8416 
8417 		// a hack to get it relative to the widget.
8418 		eleR.x = ev.x;
8419 		eleR.y = ev.y;
8420 		auto pain = captureEle;
8421 		while(pain) {
8422 			eleR.x -= pain.x;
8423 			eleR.y -= pain.y;
8424 			pain.addScrollPosition(eleR.x, eleR.y);
8425 			pain = pain.parent;
8426 		}
8427 
8428 		void populateMouseEventBase(MouseEventBase event) {
8429 			event.button = ev.button;
8430 			event.buttonLinear = ev.buttonLinear;
8431 			event.state = ev.modifierState;
8432 			event.clientX = eleR.x;
8433 			event.clientY = eleR.y;
8434 
8435 			event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false;
8436 			event.altKey = (ev.modifierState & ModifierState.alt) ? true : false;
8437 			event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false;
8438 		}
8439 
8440 		if(ev.type == MouseEventType.buttonPressed) {
8441 			{
8442 				auto event = new MouseDownEvent(captureEle);
8443 				populateMouseEventBase(event);
8444 				event.dispatch();
8445 			}
8446 
8447 			if(ev.button != MouseButton.wheelDown && ev.button != MouseButton.wheelUp && mouseLastDownOn is ele && ev.doubleClick) {
8448 				auto event = new DoubleClickEvent(captureEle);
8449 				populateMouseEventBase(event);
8450 				event.dispatch();
8451 				lastWasDoubleClick = ev.doubleClick;
8452 			} else {
8453 				lastWasDoubleClick = false;
8454 			}
8455 
8456 			mouseLastDownOn = ele;
8457 		} else if(ev.type == MouseEventType.buttonReleased) {
8458 			{
8459 				auto event = new MouseUpEvent(captureEle);
8460 				populateMouseEventBase(event);
8461 				event.dispatch();
8462 			}
8463 			if(!lastWasDoubleClick && mouseLastDownOn is ele) {
8464 				auto event = new ClickEvent(captureEle);
8465 				populateMouseEventBase(event);
8466 				event.dispatch();
8467 			}
8468 		} else if(ev.type == MouseEventType.motion) {
8469 			// motion
8470 			{
8471 				auto event = new MouseMoveEvent(captureEle);
8472 				populateMouseEventBase(event); // fills in button which is meaningless but meh
8473 				event.dispatch();
8474 			}
8475 
8476 			if(mouseLastOver !is ele) {
8477 				if(ele !is null) {
8478 					if(!isAParentOf(ele, mouseLastOver)) {
8479 						ele.setDynamicState(DynamicState.hover, true);
8480 						auto event = new MouseEnterEvent(ele);
8481 						event.relatedTarget = mouseLastOver;
8482 						event.sendDirectly();
8483 
8484 						ele.useStyleProperties((scope Widget.Style s) {
8485 							ele.parentWindow.win.cursor = s.cursor;
8486 						});
8487 					}
8488 				}
8489 
8490 				if(mouseLastOver !is null) {
8491 					if(!isAParentOf(mouseLastOver, ele)) {
8492 						mouseLastOver.setDynamicState(DynamicState.hover, false);
8493 						auto event = new MouseLeaveEvent(mouseLastOver);
8494 						event.relatedTarget = ele;
8495 						event.sendDirectly();
8496 					}
8497 				}
8498 
8499 				if(ele !is null) {
8500 					auto event = new MouseOverEvent(ele);
8501 					event.relatedTarget = mouseLastOver;
8502 					event.dispatch();
8503 				}
8504 
8505 				if(mouseLastOver !is null) {
8506 					auto event = new MouseOutEvent(mouseLastOver);
8507 					event.relatedTarget = ele;
8508 					event.dispatch();
8509 				}
8510 
8511 				mouseLastOver = ele;
8512 			}
8513 		}
8514 
8515 		return true; // FIXME: the event default prevented?
8516 	}
8517 
8518 	/++
8519 		Shows the window and runs the application event loop.
8520 
8521 		Blocks until this window is closed.
8522 
8523 		Bugs:
8524 
8525 		$(PITFALL
8526 			You should always have one event loop live for your application.
8527 			If you make two windows in sequence, the second call to loop (or
8528 			simpledisplay's [SimpleWindow.eventLoop], upon which this is built)
8529 			might fail:
8530 
8531 			---
8532 			// don't do this!
8533 			auto window = new Window();
8534 			window.loop();
8535 
8536 			// or new Window or new MainWindow, all the same
8537 			auto window2 = new SimpleWindow();
8538 			window2.eventLoop(0); // problematic! might crash
8539 			---
8540 
8541 			simpledisplay's current implementation assumes that final cleanup is
8542 			done when the event loop refcount reaches zero. So after the first
8543 			eventLoop returns, when there isn't already another one active, it assumes
8544 			the program will exit soon and cleans up.
8545 
8546 			This is arguably a bug that it doesn't reinitialize, and I'll probably change
8547 			it eventually, but in the mean time, there's an easy solution:
8548 
8549 			---
8550 			// do this
8551 			EventLoop mainEventLoop = EventLoop.get; // just add this line
8552 
8553 			auto window = new Window();
8554 			window.loop();
8555 
8556 			// or any other type of Window etc.
8557 			auto window2 = new Window();
8558 			window2.loop(); // perfectly fine since mainEventLoop still alive
8559 			---
8560 
8561 			By adding a top-level reference to the event loop, it ensures the final cleanup
8562 			is not performed until it goes out of scope too, letting the individual window loops
8563 			work without trouble despite the bug.
8564 		)
8565 
8566 		History:
8567 			The [BlockingMode] parameter was added on December 8, 2021.
8568 			The default behavior is to block until the application quits
8569 			(so all windows have been closed), unless another minigui or
8570 			simpledisplay event loop is already running, in which case it
8571 			will block until this window closes specifically.
8572 	+/
8573 	@scriptable
8574 	void loop(BlockingMode bm = BlockingMode.automatic) {
8575 		if(win.closed)
8576 			return; // otherwise show will throw
8577 		show();
8578 		win.eventLoopWithBlockingMode(bm, 0);
8579 	}
8580 
8581 	private bool firstShow = true;
8582 
8583 	@scriptable
8584 	override void show() {
8585 		bool rd = false;
8586 		if(firstShow) {
8587 			firstShow = false;
8588 			recomputeChildLayout();
8589 			auto f = getFirstFocusable(this); // FIXME: autofocus?
8590 			if(f)
8591 				f.focus();
8592 			redraw();
8593 		}
8594 		win.show();
8595 		super.show();
8596 	}
8597 	@scriptable
8598 	override void hide() {
8599 		win.hide();
8600 		super.hide();
8601 	}
8602 
8603 	static Widget getFirstFocusable(Widget start) {
8604 		if(start is null)
8605 			return null;
8606 
8607 		foreach(widget; &start.focusableWidgets) {
8608 			return widget;
8609 		}
8610 
8611 		return null;
8612 	}
8613 
8614 	static Widget getLastFocusable(Widget start) {
8615 		if(start is null)
8616 			return null;
8617 
8618 		Widget last;
8619 		foreach(widget; &start.focusableWidgets) {
8620 			last = widget;
8621 		}
8622 
8623 		return last;
8624 	}
8625 
8626 
8627 	mixin Emits!ClosingEvent;
8628 	mixin Emits!ClosedEvent;
8629 }
8630 
8631 /++
8632 	History:
8633 		Added January 12, 2022
8634 +/
8635 class DpiChangedEvent : Event {
8636 	enum EventString = "dpichanged";
8637 
8638 	this(Widget target) {
8639 		super(EventString, target);
8640 	}
8641 }
8642 
8643 debug private class DevToolWindow : Window {
8644 	Window p;
8645 
8646 	TextEdit parentList;
8647 	TextEdit logWindow;
8648 	TextLabel clickX, clickY;
8649 
8650 	this(Window p) {
8651 		this.p = p;
8652 		super(400, 300, "Developer Toolbox");
8653 
8654 		logWindow = new TextEdit(this);
8655 		parentList = new TextEdit(this);
8656 
8657 		auto hl = new HorizontalLayout(this);
8658 		clickX = new TextLabel("", TextAlignment.Right, hl);
8659 		clickY = new TextLabel("", TextAlignment.Right, hl);
8660 
8661 		parentListeners ~= p.addEventListener("*", (Event ev) {
8662 			log(typeid(ev.source).name, " emitted ", typeid(ev).name);
8663 		});
8664 
8665 		parentListeners ~= p.addEventListener((ClickEvent ev) {
8666 			auto s = ev.srcElement;
8667 
8668 			string list;
8669 
8670 			void addInfo(Widget s) {
8671 				list ~= s.toString();
8672 				list ~= "\n\tminHeight: " ~ toInternal!string(s.minHeight);
8673 				list ~= "\n\tmaxHeight: " ~ toInternal!string(s.maxHeight);
8674 				list ~= "\n\theightStretchiness: " ~ toInternal!string(s.heightStretchiness);
8675 				list ~= "\n\theight: " ~ toInternal!string(s.height);
8676 				list ~= "\n\tmarginTop: " ~ toInternal!string(s.marginTop);
8677 				list ~= "\n\tmarginBottom: " ~ toInternal!string(s.marginBottom);
8678 			}
8679 
8680 			addInfo(s);
8681 
8682 			s = s.parent;
8683 			while(s) {
8684 				list ~= "\n";
8685 				addInfo(s);
8686 				s = s.parent;
8687 			}
8688 			parentList.content = list;
8689 
8690 			clickX.label = toInternal!string(ev.clientX);
8691 			clickY.label = toInternal!string(ev.clientY);
8692 		});
8693 	}
8694 
8695 	EventListener[] parentListeners;
8696 
8697 	override void close() {
8698 		assert(p !is null);
8699 		foreach(p; parentListeners)
8700 			p.disconnect();
8701 		parentListeners = null;
8702 		p.devTools = null;
8703 		p = null;
8704 		super.close();
8705 	}
8706 
8707 	override void defaultEventHandler_keydown(KeyDownEvent ev) {
8708 		if(ev.key == Key.F12) {
8709 			this.close();
8710 			if(p)
8711 				p.devTools = null;
8712 		} else {
8713 			super.defaultEventHandler_keydown(ev);
8714 		}
8715 	}
8716 
8717 	void log(T...)(T t) {
8718 		string str;
8719 		import std.conv;
8720 		foreach(i; t)
8721 			str ~= to!string(i);
8722 		str ~= "\n";
8723 		logWindow.addText(str);
8724 
8725 		//version(custom_widgets)
8726 		//logWindow.ensureVisibleInScroll(logWindow.textLayout.caretBoundingBox());
8727 	}
8728 }
8729 
8730 /++
8731 	A dialog is a transient window that intends to get information from
8732 	the user before being dismissed.
8733 +/
8734 abstract class Dialog : Window {
8735 	///
8736 	this(int width, int height, string title = null) {
8737 		super(width, height, title);
8738 	}
8739 
8740 	///
8741 	abstract void OK();
8742 
8743 	///
8744 	void Cancel() {
8745 		this.close();
8746 	}
8747 }
8748 
8749 /++
8750 	A custom widget similar to the HTML5 <details> tag.
8751 +/
8752 version(none)
8753 class DetailsView : Widget {
8754 
8755 }
8756 
8757 // FIXME: maybe i should expose the other list views Windows offers too
8758 
8759 /++
8760 	A TableView is a widget made to display a table of data strings.
8761 
8762 
8763 	Future_Directions:
8764 		Each item should be able to take an icon too and maybe I'll allow more of the view modes Windows offers.
8765 
8766 		I will add a selection changed event at some point, as well as item clicked events.
8767 	History:
8768 		Added September 24, 2021. Initial api stabilized in dub v10.4, but it isn't completely feature complete yet.
8769 	See_Also:
8770 		[ListWidget] which displays a list of strings without additional columns.
8771 +/
8772 class TableView : Widget {
8773 	/++
8774 
8775 	+/
8776 	this(Widget parent) {
8777 		super(parent);
8778 
8779 		version(win32_widgets) {
8780 			createWin32Window(this, WC_LISTVIEW, "", LVS_REPORT | LVS_OWNERDATA);//| LVS_OWNERDRAWFIXED);
8781 		} else version(custom_widgets) {
8782 			auto smw = new ScrollMessageWidget(this);
8783 			smw.addDefaultKeyboardListeners();
8784 			smw.addDefaultWheelListeners(1, scaleWithDpi(16));
8785 			tvwi = new TableViewWidgetInner(this, smw);
8786 		}
8787 	}
8788 
8789 	// FIXME: auto-size columns on double click of header thing like in Windows
8790 	// it need only make the currently displayed things fit well.
8791 
8792 
8793 	private ColumnInfo[] columns;
8794 	private int itemCount;
8795 
8796 	version(custom_widgets) private {
8797 		TableViewWidgetInner tvwi;
8798 	}
8799 
8800 	/// Passed to [setColumnInfo]
8801 	static struct ColumnInfo {
8802 		const(char)[] name; /// the name displayed in the header
8803 		/++
8804 			The default width, in pixels. As a special case, you can set this to -1
8805 			if you want the system to try to automatically size the width to fit visible
8806 			content. If it can't, it will try to pick a sensible default size.
8807 
8808 			Any other negative value is not allowed and may lead to unpredictable results.
8809 
8810 			History:
8811 				The -1 behavior was specified on December 3, 2021. It actually worked before
8812 				anyway on Win32 but now it is a formal feature with partial Linux support.
8813 
8814 			Bugs:
8815 				It doesn't actually attempt to calculate a best-fit width on Linux as of
8816 				December 3, 2021. I do plan to fix this in the future, but Windows is the
8817 				priority right now. At least it doesn't break things when you use it now.
8818 		+/
8819 		int width;
8820 
8821 		/++
8822 			Alignment of the text in the cell. Applies to the header as well as all data in this
8823 			column.
8824 
8825 			Bugs:
8826 				On Windows, the first column ignores this member and is always left aligned.
8827 				You can work around this by inserting a dummy first column with width = 0
8828 				then putting your actual data in the second column, which does respect the
8829 				alignment.
8830 
8831 				This is a quirk of the operating system's implementation going back a very
8832 				long time and is unlikely to ever be fixed.
8833 		+/
8834 		TextAlignment alignment;
8835 
8836 		/++
8837 			After all the pixel widths have been assigned, any left over
8838 			space is divided up among all columns and distributed to according
8839 			to the widthPercent field.
8840 
8841 
8842 			For example, if you have two fields, both with width 50 and one with
8843 			widthPercent of 25 and the other with widthPercent of 75, and the
8844 			container is 200 pixels wide, first both get their width of 50.
8845 			then the 100 remaining pixels are split up, so the one gets a total
8846 			of 75 pixels and the other gets a total of 125.
8847 
8848 			This is automatically applied as the window is resized.
8849 
8850 			If there is not enough space - that is, when a horizontal scrollbar
8851 			needs to appear - there are 0 pixels divided up, and thus everyone
8852 			gets 0. This can cause a column to shrink out of proportion when
8853 			passing the scroll threshold.
8854 
8855 			It is important to still set a fixed width (that is, to populate the
8856 			`width` field) even if you use the percents because that will be the
8857 			default minimum in the event of a scroll bar appearing.
8858 
8859 			The percents total in the column can never exceed 100 or be less than 0.
8860 			Doing this will trigger an assert error.
8861 
8862 			Implementation note:
8863 
8864 			Please note that percentages are only recalculated 1) upon original
8865 			construction and 2) upon resizing the control. If the user adjusts the
8866 			width of a column, the percentage items will not be updated.
8867 
8868 			On the other hand, if the user adjusts the width of a percentage column
8869 			then resizes the window, it is recalculated, meaning their hand adjustment
8870 			is discarded. This specific behavior may change in the future as it is
8871 			arguably a bug, but I'm not certain yet.
8872 
8873 			History:
8874 				Added November 10, 2021 (dub v10.4)
8875 		+/
8876 		int widthPercent;
8877 
8878 
8879 		private int calculatedWidth;
8880 	}
8881 	/++
8882 		Sets the number of columns along with information about the headers.
8883 
8884 		Please note: on Windows, the first column ignores your alignment preference
8885 		and is always left aligned.
8886 	+/
8887 	void setColumnInfo(ColumnInfo[] columns...) {
8888 
8889 		foreach(ref c; columns) {
8890 			c.name = c.name.idup;
8891 		}
8892 		this.columns = columns.dup;
8893 
8894 		updateCalculatedWidth(false);
8895 
8896 		version(custom_widgets) {
8897 			tvwi.header.updateHeaders();
8898 			tvwi.updateScrolls();
8899 		} else version(win32_widgets)
8900 		foreach(i, column; this.columns) {
8901 			LVCOLUMN lvColumn;
8902 			lvColumn.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
8903 			lvColumn.cx = column.width == -1 ? -1 : column.calculatedWidth;
8904 
8905 			auto bfr = WCharzBuffer(column.name);
8906 			lvColumn.pszText = bfr.ptr;
8907 
8908 			if(column.alignment & TextAlignment.Center)
8909 				lvColumn.fmt = LVCFMT_CENTER;
8910 			else if(column.alignment & TextAlignment.Right)
8911 				lvColumn.fmt = LVCFMT_RIGHT;
8912 			else
8913 				lvColumn.fmt = LVCFMT_LEFT;
8914 
8915 			if(SendMessage(hwnd, LVM_INSERTCOLUMN, cast(WPARAM) i, cast(LPARAM) &lvColumn) == -1)
8916 				throw new WindowsApiException("Insert Column Fail", GetLastError());
8917 		}
8918 	}
8919 
8920 	private int getActualSetSize(size_t i, bool askWindows) {
8921 		version(win32_widgets)
8922 			if(askWindows)
8923 				return cast(int) SendMessage(hwnd, LVM_GETCOLUMNWIDTH, cast(WPARAM) i, 0);
8924 		auto w = columns[i].width;
8925 		if(w == -1)
8926 			return 50; // idk, just give it some space so the percents aren't COMPLETELY off FIXME
8927 		return w;
8928 	}
8929 
8930 	private void updateCalculatedWidth(bool informWindows) {
8931 		int padding;
8932 		version(win32_widgets)
8933 			padding = 4;
8934 		int remaining = this.width;
8935 		foreach(i, column; columns)
8936 			remaining -= this.getActualSetSize(i, informWindows && column.widthPercent == 0) + padding;
8937 		remaining -= padding;
8938 		if(remaining < 0)
8939 			remaining = 0;
8940 
8941 		int percentTotal;
8942 		foreach(i, ref column; columns) {
8943 			percentTotal += column.widthPercent;
8944 
8945 			auto c = this.getActualSetSize(i, informWindows && column.widthPercent == 0) + (remaining * column.widthPercent) / 100;
8946 
8947 			column.calculatedWidth = c;
8948 
8949 			version(win32_widgets)
8950 			if(informWindows)
8951 				SendMessage(hwnd, LVM_SETCOLUMNWIDTH, i, c); // LVSCW_AUTOSIZE or LVSCW_AUTOSIZE_USEHEADER are amazing omg
8952 		}
8953 
8954 		assert(percentTotal >= 0, "The total percents in your column definitions were negative. They must add up to something between 0 and 100.");
8955 		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).");
8956 
8957 
8958 	}
8959 
8960 	override void registerMovement() {
8961 		super.registerMovement();
8962 
8963 		updateCalculatedWidth(true);
8964 	}
8965 
8966 	/++
8967 		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.
8968 	+/
8969 	void setItemCount(int count) {
8970 		this.itemCount = count;
8971 		version(custom_widgets) {
8972 			tvwi.updateScrolls();
8973 			redraw();
8974 		} else version(win32_widgets) {
8975 			SendMessage(hwnd, LVM_SETITEMCOUNT, count, 0);
8976 		}
8977 	}
8978 
8979 	/++
8980 		Clears all items;
8981 	+/
8982 	void clear() {
8983 		this.itemCount = 0;
8984 		this.columns = null;
8985 		version(custom_widgets) {
8986 			tvwi.header.updateHeaders();
8987 			tvwi.updateScrolls();
8988 			redraw();
8989 		} else version(win32_widgets) {
8990 			SendMessage(hwnd, LVM_DELETEALLITEMS, 0, 0);
8991 		}
8992 	}
8993 
8994 	/+
8995 	version(win32_widgets)
8996 	override int handleWmDrawItem(DRAWITEMSTRUCT* dis)
8997 		auto itemId = dis.itemID;
8998 		auto hdc = dis.hDC;
8999 		auto rect = dis.rcItem;
9000 		switch(dis.itemAction) {
9001 			case ODA_DRAWENTIRE:
9002 
9003 				// FIXME: do other items
9004 				// FIXME: do the focus rectangle i guess
9005 				// FIXME: alignment
9006 				// FIXME: column width
9007 				// FIXME: padding left
9008 				// FIXME: check dpi scaling
9009 				// FIXME: don't owner draw unless it is necessary.
9010 
9011 				auto padding = GetSystemMetrics(SM_CXEDGE); // FIXME: for dpi
9012 				RECT itemRect;
9013 				itemRect.top = 1; // subitem idx, 1-based
9014 				itemRect.left = LVIR_BOUNDS;
9015 
9016 				SendMessage(hwnd, LVM_GETSUBITEMRECT, itemId, cast(LPARAM) &itemRect);
9017 				itemRect.left += padding;
9018 
9019 				getData(itemId, 0, (in char[] data) {
9020 					auto wdata = WCharzBuffer(data);
9021 					DrawTextW(hdc, wdata.ptr, wdata.length, &itemRect, DT_RIGHT| DT_END_ELLIPSIS);
9022 
9023 				});
9024 			goto case;
9025 			case ODA_FOCUS:
9026 				if(dis.itemState & ODS_FOCUS)
9027 					DrawFocusRect(hdc, &rect);
9028 			break;
9029 			case ODA_SELECT:
9030 				// itemState & ODS_SELECTED
9031 			break;
9032 			default:
9033 		}
9034 		return 1;
9035 	}
9036 	+/
9037 
9038 	version(win32_widgets) {
9039 		CellStyle last;
9040 		COLORREF defaultColor;
9041 		COLORREF defaultBackground;
9042 	}
9043 
9044 	version(win32_widgets)
9045 	override int handleWmNotify(NMHDR* hdr, int code, out int mustReturn) {
9046 		switch(code) {
9047 			case NM_CUSTOMDRAW:
9048 				auto s = cast(NMLVCUSTOMDRAW*) hdr;
9049 				switch(s.nmcd.dwDrawStage) {
9050 					case CDDS_PREPAINT:
9051 						if(getCellStyle is null)
9052 							return 0;
9053 
9054 						mustReturn = true;
9055 						return CDRF_NOTIFYITEMDRAW;
9056 					case CDDS_ITEMPREPAINT:
9057 						mustReturn = true;
9058 						return CDRF_NOTIFYSUBITEMDRAW;
9059 					case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
9060 						mustReturn = true;
9061 
9062 						if(getCellStyle is null) // this SHOULD never happen...
9063 							return 0;
9064 
9065 						if(s.iSubItem == 0) {
9066 							// Windows resets it per row so we'll use item 0 as a chance
9067 							// to capture these for later
9068 							defaultColor = s.clrText;
9069 							defaultBackground = s.clrTextBk;
9070 						}
9071 
9072 						auto style = getCellStyle(cast(int) s.nmcd.dwItemSpec, cast(int) s.iSubItem);
9073 						// if no special style and no reset needed...
9074 						if(style == CellStyle.init && (s.iSubItem == 0 || last == CellStyle.init))
9075 							return 0; // allow default processing to continue
9076 
9077 						last = style;
9078 
9079 						// might still need to reset or use the preference.
9080 
9081 						if(style.flags & CellStyle.Flags.textColorSet)
9082 							s.clrText = style.textColor.asWindowsColorRef;
9083 						else
9084 							s.clrText = defaultColor; // reset in case it was set from last iteration not a fan
9085 						if(style.flags & CellStyle.Flags.backgroundColorSet)
9086 							s.clrTextBk = style.backgroundColor.asWindowsColorRef;
9087 						else
9088 							s.clrTextBk = defaultBackground; // need to reset it... not a fan of this
9089 
9090 						return CDRF_NEWFONT;
9091 					default:
9092 						return 0;
9093 
9094 				}
9095 			case NM_RETURN: // no need since i subclass keydown
9096 			break;
9097 			case LVN_COLUMNCLICK:
9098 				auto info = cast(LPNMLISTVIEW) hdr;
9099 				this.emit!HeaderClickedEvent(info.iSubItem);
9100 			break;
9101 			case NM_CLICK:
9102 			case NM_DBLCLK:
9103 			case NM_RCLICK:
9104 			case NM_RDBLCLK:
9105 				// the item/subitem is set here and that can be a useful notification
9106 				// even beyond the normal click notification
9107 			break;
9108 			case LVN_GETDISPINFO:
9109 				LV_DISPINFO* info = cast(LV_DISPINFO*) hdr;
9110 				if(info.item.mask & LVIF_TEXT) {
9111 					if(getData) {
9112 						getData(info.item.iItem, info.item.iSubItem, (in char[] dataReceived) {
9113 							auto bfr = WCharzBuffer(dataReceived);
9114 							auto len = info.item.cchTextMax;
9115 							if(bfr.length < len)
9116 								len = cast(typeof(len)) bfr.length;
9117 							info.item.pszText[0 .. len] = bfr.ptr[0 .. len];
9118 							info.item.pszText[len] = 0;
9119 						});
9120 					} else {
9121 						info.item.pszText[0] = 0;
9122 					}
9123 					//info.item.iItem
9124 					//if(info.item.iSubItem)
9125 				}
9126 			break;
9127 			default:
9128 		}
9129 		return 0;
9130 	}
9131 
9132 	override bool encapsulatedChildren() {
9133 		return true;
9134 	}
9135 
9136 	/++
9137 		Informs the control that content has changed.
9138 
9139 		History:
9140 			Added November 10, 2021 (dub v10.4)
9141 	+/
9142 	void update() {
9143 		version(custom_widgets)
9144 			redraw();
9145 		else {
9146 			SendMessage(hwnd, LVM_REDRAWITEMS, 0, SendMessage(hwnd, LVM_GETITEMCOUNT, 0, 0));
9147 			UpdateWindow(hwnd);
9148 		}
9149 
9150 
9151 	}
9152 
9153 	/++
9154 		Called by the system to request the text content of an individual cell. You
9155 		should pass the text into the provided `sink` delegate. This function will be
9156 		called for each visible cell as-needed when drawing.
9157 	+/
9158 	void delegate(int row, int column, scope void delegate(in char[]) sink) getData;
9159 
9160 	/++
9161 		Available per-cell style customization options. Use one of the constructors
9162 		provided to set the values conveniently, or default construct it and set individual
9163 		values yourself. Just remember to set the `flags` so your values are actually used.
9164 		If the flag isn't set, the field is ignored and the system default is used instead.
9165 
9166 		This is returned by the [getCellStyle] delegate.
9167 
9168 		Examples:
9169 			---
9170 			// assumes you have a variables called `my_data` which is an array of arrays of numbers
9171 			auto table = new TableView(window);
9172 			// snip: you would set up columns here
9173 
9174 			// this is how you provide data to the table view class
9175 			table.getData = delegate(int row, int column, scope void delegate(in char[]) sink) {
9176 				import std.conv;
9177 				sink(to!string(my_data[row][column]));
9178 			};
9179 
9180 			// and this is how you customize the colors
9181 			table.getCellStyle = delegate(int row, int column) {
9182 				return (my_data[row][column] < 0) ?
9183 					TableView.CellStyle(Color.red); // make negative numbers red
9184 					: TableView.CellStyle.init; // leave the rest alone
9185 			};
9186 			// snip: you would call table.setItemCount here then continue with the rest of your window setup work
9187 			---
9188 
9189 		History:
9190 			Added November 27, 2021 (dub v10.4)
9191 	+/
9192 	struct CellStyle {
9193 		/// 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.
9194 		this(Color textColor) {
9195 			this.textColor = textColor;
9196 			this.flags |= Flags.textColorSet;
9197 		}
9198 		/// Sets a custom text and background color.
9199 		this(Color textColor, Color backgroundColor) {
9200 			this.textColor = textColor;
9201 			this.backgroundColor = backgroundColor;
9202 			this.flags |= Flags.textColorSet | Flags.backgroundColorSet;
9203 		}
9204 
9205 		Color textColor;
9206 		Color backgroundColor;
9207 		int flags; /// bitmask of [Flags]
9208 		/// available options to combine into [flags]
9209 		enum Flags {
9210 			textColorSet = 1 << 0,
9211 			backgroundColorSet = 1 << 1,
9212 		}
9213 	}
9214 	/++
9215 		Companion delegate to [getData] that allows you to custom style each
9216 		cell of the table.
9217 
9218 		Returns:
9219 			A [CellStyle] structure that describes the desired style for the
9220 			given cell. `return CellStyle.init` if you want the default style.
9221 
9222 		History:
9223 			Added November 27, 2021 (dub v10.4)
9224 	+/
9225 	CellStyle delegate(int row, int column) getCellStyle;
9226 
9227 	// i want to be able to do things like draw little colored things to show red for negative numbers
9228 	// or background color indicators or even in-cell charts
9229 	// void delegate(int row, int column, WidgetPainter painter, int width, int height, in char[] text) drawCell;
9230 
9231 	/++
9232 		When the user clicks on a header, this event is emitted. It has a meber to identify which header (by index) was clicked.
9233 	+/
9234 	mixin Emits!HeaderClickedEvent;
9235 }
9236 
9237 /++
9238 	This is emitted by the [TableView] when a user clicks on a column header.
9239 
9240 	Its member `columnIndex` has the zero-based index of the column that was clicked.
9241 
9242 	The default behavior of this event is to do nothing, so `preventDefault` has no effect.
9243 
9244 	History:
9245 		Added November 27, 2021 (dub v10.4)
9246 +/
9247 class HeaderClickedEvent : Event {
9248 	enum EventString = "HeaderClicked";
9249 	this(Widget target, int columnIndex) {
9250 		this.columnIndex = columnIndex;
9251 		super(EventString, target);
9252 	}
9253 
9254 	/// The index of the column
9255 	int columnIndex;
9256 
9257 	///
9258 	override @property int intValue() {
9259 		return columnIndex;
9260 	}
9261 }
9262 
9263 version(custom_widgets)
9264 private class TableViewWidgetInner : Widget {
9265 
9266 // wrap this thing in a ScrollMessageWidget
9267 
9268 	TableView tvw;
9269 	ScrollMessageWidget smw;
9270 	HeaderWidget header;
9271 
9272 	this(TableView tvw, ScrollMessageWidget smw) {
9273 		this.tvw = tvw;
9274 		this.smw = smw;
9275 		super(smw);
9276 
9277 		this.tabStop = true;
9278 
9279 		header = new HeaderWidget(this, smw.getHeader());
9280 
9281 		smw.addEventListener("scroll", () {
9282 			this.redraw();
9283 			header.redraw();
9284 		});
9285 
9286 
9287 		// I need headers outside the scroll area but rendered on the same line as the up arrow
9288 		// FIXME: add a fixed header to the SMW
9289 	}
9290 
9291 	enum padding = 3;
9292 
9293 	void updateScrolls() {
9294 		int w;
9295 		foreach(idx, column; tvw.columns) {
9296 			if(column.width == 0) continue;
9297 			w += tvw.getActualSetSize(idx, false);// + padding;
9298 		}
9299 		smw.setTotalArea(w, tvw.itemCount);
9300 		columnsWidth = w;
9301 	}
9302 
9303 	private int columnsWidth;
9304 
9305 	private int lh() { return scaleWithDpi(16); } // FIXME lineHeight
9306 
9307 	override void registerMovement() {
9308 		super.registerMovement();
9309 		// FIXME: actual column width. it might need to be done per-pixel instead of per-colum
9310 		smw.setViewableArea(this.width, this.height / lh);
9311 	}
9312 
9313 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
9314 		int x;
9315 		int y;
9316 
9317 		int row = smw.position.y;
9318 
9319 		foreach(lol; 0 .. this.height / lh) {
9320 			if(row >= tvw.itemCount)
9321 				break;
9322 			x = 0;
9323 			foreach(columnNumber, column; tvw.columns) {
9324 				auto x2 = x + column.calculatedWidth;
9325 				auto smwx = smw.position.x;
9326 
9327 				if(x2 > smwx /* if right side of it is visible at all */ || (x >= smwx && x < smwx + this.width) /* left side is visible at all*/) {
9328 					auto startX = x;
9329 					auto endX = x + column.calculatedWidth;
9330 					switch (column.alignment & (TextAlignment.Left | TextAlignment.Center | TextAlignment.Right)) {
9331 						case TextAlignment.Left: startX += padding; break;
9332 						case TextAlignment.Center: startX += padding; endX -= padding; break;
9333 						case TextAlignment.Right: endX -= padding; break;
9334 						default: /* broken */ break;
9335 					}
9336 					if(column.width != 0) // no point drawing an invisible column
9337 					tvw.getData(row, cast(int) columnNumber, (in char[] info) {
9338 						// auto clip = painter.setClipRectangle(
9339 
9340 						void dotext(WidgetPainter painter) {
9341 							painter.drawText(Point(startX - smw.position.x, y), info, Point(endX - smw.position.x, y + lh), column.alignment);
9342 						}
9343 
9344 						if(tvw.getCellStyle !is null) {
9345 							auto style = tvw.getCellStyle(row, cast(int) columnNumber);
9346 
9347 							if(style.flags & TableView.CellStyle.Flags.backgroundColorSet) {
9348 								auto tempPainter = painter;
9349 								tempPainter.fillColor = style.backgroundColor;
9350 								tempPainter.outlineColor = style.backgroundColor;
9351 
9352 								tempPainter.drawRectangle(Point(startX - smw.position.x, y),
9353 									Point(endX - smw.position.x, y + lh));
9354 							}
9355 							auto tempPainter = painter;
9356 							if(style.flags & TableView.CellStyle.Flags.textColorSet)
9357 								tempPainter.outlineColor = style.textColor;
9358 
9359 							dotext(tempPainter);
9360 						} else {
9361 							dotext(painter);
9362 						}
9363 					});
9364 				}
9365 
9366 				x += column.calculatedWidth;
9367 			}
9368 			row++;
9369 			y += lh;
9370 		}
9371 		return bounds;
9372 	}
9373 
9374 	static class Style : Widget.Style {
9375 		override WidgetBackground background() {
9376 			return WidgetBackground(WidgetPainter.visualTheme.widgetBackgroundColor);
9377 		}
9378 	}
9379 	mixin OverrideStyle!Style;
9380 
9381 	private static class HeaderWidget : Widget {
9382 		this(TableViewWidgetInner tvw, Widget parent) {
9383 			super(parent);
9384 			this.tvw = tvw;
9385 
9386 			this.remainder = new Button("", this);
9387 
9388 			this.addEventListener((scope ClickEvent ev) {
9389 				int header = -1;
9390 				foreach(idx, child; this.children[1 .. $]) {
9391 					if(child is ev.target) {
9392 						header = cast(int) idx;
9393 						break;
9394 					}
9395 				}
9396 
9397 				if(header != -1) {
9398 					auto hce = new HeaderClickedEvent(tvw.tvw, header);
9399 					hce.dispatch();
9400 				}
9401 
9402 			});
9403 		}
9404 
9405 		void updateHeaders() {
9406 			foreach(child; children[1 .. $])
9407 				child.removeWidget();
9408 
9409 			foreach(column; tvw.tvw.columns) {
9410 				// the cast is ok because I dup it above, just the type is never changed.
9411 				// all this is private so it should never get messed up.
9412 				new Button(ImageLabel(cast(string) column.name, column.alignment), this);
9413 			}
9414 		}
9415 
9416 		Button remainder;
9417 		TableViewWidgetInner tvw;
9418 
9419 		override void recomputeChildLayout() {
9420 			registerMovement();
9421 			int pos;
9422 			foreach(idx, child; children[1 .. $]) {
9423 				if(idx >= tvw.tvw.columns.length)
9424 					continue;
9425 				child.x = pos;
9426 				child.y = 0;
9427 				child.width = tvw.tvw.columns[idx].calculatedWidth;
9428 				child.height = scaleWithDpi(16);// this.height;
9429 				pos += child.width;
9430 
9431 				child.recomputeChildLayout();
9432 			}
9433 
9434 			if(remainder is null)
9435 				return;
9436 
9437 			remainder.x = pos;
9438 			remainder.y = 0;
9439 			if(pos < this.width)
9440 				remainder.width = this.width - pos;// + 4;
9441 			else
9442 				remainder.width = 0;
9443 			remainder.height = scaleWithDpi(16);
9444 
9445 			remainder.recomputeChildLayout();
9446 		}
9447 
9448 		// for the scrollable children mixin
9449 		Point scrollOrigin() {
9450 			return Point(tvw.smw.position.x, 0);
9451 		}
9452 		void paintFrameAndBackground(WidgetPainter painter) { }
9453 
9454 		mixin ScrollableChildren;
9455 	}
9456 }
9457 
9458 /+
9459 
9460 // given struct / array / number / string / etc, make it viewable and editable
9461 class DataViewerWidget : Widget {
9462 
9463 }
9464 +/
9465 
9466 /++
9467 	A line edit box with an associated label.
9468 
9469 	History:
9470 		On May 17, 2021, the default internal layout was changed from horizontal to vertical.
9471 
9472 		```
9473 		Old: ________
9474 
9475 		New:
9476 		____________
9477 		```
9478 
9479 		To restore the old behavior, use `new LabeledLineEdit("label", TextAlignment.Right, parent);`
9480 
9481 		You can also use `new LabeledLineEdit("label", TextAlignment.Left, parent);` if you want a
9482 		horizontal label but left aligned. You may also consider a [GridLayout].
9483 +/
9484 alias LabeledLineEdit = Labeled!LineEdit;
9485 
9486 /++
9487 	History:
9488 		Added May 19, 2021
9489 +/
9490 class Labeled(T) : Widget {
9491 	///
9492 	this(string label, Widget parent) {
9493 		super(parent);
9494 		initialize!VerticalLayout(label, TextAlignment.Left, parent);
9495 	}
9496 
9497 	/++
9498 		History:
9499 			The alignment parameter was added May 17, 2021
9500 	+/
9501 	this(string label, TextAlignment alignment, Widget parent) {
9502 		super(parent);
9503 		initialize!HorizontalLayout(label, alignment, parent);
9504 	}
9505 
9506 	private void initialize(L)(string label, TextAlignment alignment, Widget parent) {
9507 		tabStop = false;
9508 		horizontal = is(L == HorizontalLayout);
9509 		auto hl = new L(this);
9510 		this.label = new TextLabel(label, alignment, hl);
9511 		this.lineEdit = new T(hl);
9512 
9513 		this.label.labelFor = this.lineEdit;
9514 	}
9515 
9516 	private bool horizontal;
9517 
9518 	TextLabel label; ///
9519 	T lineEdit; ///
9520 
9521 	override int flexBasisWidth() { return 250; }
9522 
9523 	override int minHeight() {
9524 		return this.children[0].minHeight;
9525 	}
9526 	override int maxHeight() { return minHeight(); }
9527 	override int marginTop() { return 4; }
9528 	override int marginBottom() { return 4; }
9529 
9530 	// FIXME: i should prolly call it value as well as content tbh
9531 
9532 	///
9533 	@property string content() {
9534 		return lineEdit.content;
9535 	}
9536 	///
9537 	@property void content(string c) {
9538 		return lineEdit.content(c);
9539 	}
9540 
9541 	///
9542 	void selectAll() {
9543 		lineEdit.selectAll();
9544 	}
9545 
9546 	override void focus() {
9547 		lineEdit.focus();
9548 	}
9549 }
9550 
9551 /++
9552 	A labeled password edit.
9553 
9554 	History:
9555 		Added as a class on January 25, 2021, changed into an alias of the new [Labeled] template on May 19, 2021
9556 
9557 		The default parameters for the constructors were also removed on May 19, 2021
9558 +/
9559 alias LabeledPasswordEdit = Labeled!PasswordEdit;
9560 
9561 private string toMenuLabel(string s) {
9562 	string n;
9563 	n.reserve(s.length);
9564 	foreach(c; s)
9565 		if(c == '_')
9566 			n ~= ' ';
9567 		else
9568 			n ~= c;
9569 	return n;
9570 }
9571 
9572 private void autoExceptionHandler(Exception e) {
9573 	messageBox(e.msg);
9574 }
9575 
9576 private void delegate() makeAutomaticHandler(alias fn, T)(T t) {
9577 	static if(is(T : void delegate())) {
9578 		return () {
9579 			try
9580 				t();
9581 			catch(Exception e)
9582 				autoExceptionHandler(e);
9583 		};
9584 	} else static if(is(typeof(fn) Params == __parameters)) {
9585 		static if(Params.length == 1 && is(Params[0] == FileName!(member, filters, type), alias member, string[] filters, FileDialogType type)) {
9586 			return () {
9587 				void onOK(string s) {
9588 					member = s;
9589 					try
9590 						t(Params[0](s));
9591 					catch(Exception e)
9592 						autoExceptionHandler(e);
9593 				}
9594 
9595 				if(
9596 					(type == FileDialogType.Automatic && (__traits(identifier, fn).startsWith("Save") || __traits(identifier, fn).startsWith("Export")))
9597 					|| type == FileDialogType.Save)
9598 				{
9599 					getSaveFileName(&onOK, member, filters, null);
9600 				} else
9601 					getOpenFileName(&onOK, member, filters, null);
9602 			};
9603 		} else {
9604 			struct S {
9605 				static if(!__traits(compiles, mixin(`{ static foreach(i; 1..4) {} }`))) {
9606 					pragma(msg, "warning: automatic handler of params not yet implemented on your compiler");
9607 				} else mixin(q{
9608 				static foreach(idx, ignore; Params) {
9609 					mixin("Params[idx] " ~ __traits(identifier, Params[idx .. idx + 1]) ~ ";");
9610 				}
9611 				});
9612 			}
9613 			return () {
9614 				dialog((S s) {
9615 					try {
9616 						static if(is(typeof(t) Ret == return)) {
9617 							static if(is(Ret == void)) {
9618 								t(s.tupleof);
9619 							} else {
9620 								auto ret = t(s.tupleof);
9621 								import std.conv;
9622 								messageBox(to!string(ret), "Returned Value");
9623 							}
9624 						}
9625 					} catch(Exception e)
9626 						autoExceptionHandler(e);
9627 				}, null, __traits(identifier, fn));
9628 			};
9629 		}
9630 	}
9631 }
9632 
9633 private template hasAnyRelevantAnnotations(a...) {
9634 	bool helper() {
9635 		bool any;
9636 		foreach(attr; a) {
9637 			static if(is(typeof(attr) == .menu))
9638 				any = true;
9639 			else static if(is(typeof(attr) == .toolbar))
9640 				any = true;
9641 			else static if(is(attr == .separator))
9642 				any = true;
9643 			else static if(is(typeof(attr) == .accelerator))
9644 				any = true;
9645 			else static if(is(typeof(attr) == .hotkey))
9646 				any = true;
9647 			else static if(is(typeof(attr) == .icon))
9648 				any = true;
9649 			else static if(is(typeof(attr) == .label))
9650 				any = true;
9651 			else static if(is(typeof(attr) == .tip))
9652 				any = true;
9653 		}
9654 		return any;
9655 	}
9656 
9657 	enum bool hasAnyRelevantAnnotations = helper();
9658 }
9659 
9660 /++
9661 	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.
9662 +/
9663 class MainWindow : Window {
9664 	///
9665 	this(string title = null, int initialWidth = 500, int initialHeight = 500) {
9666 		super(initialWidth, initialHeight, title);
9667 
9668 		_clientArea = new ClientAreaWidget();
9669 		_clientArea.x = 0;
9670 		_clientArea.y = 0;
9671 		_clientArea.width = this.width;
9672 		_clientArea.height = this.height;
9673 		_clientArea.tabStop = false;
9674 
9675 		super.addChild(_clientArea);
9676 
9677 		statusBar = new StatusBar(this);
9678 	}
9679 
9680 	/++
9681 		Adds a menu and toolbar from annotated functions.
9682 
9683 	---
9684         struct Commands {
9685                 @menu("File") {
9686                         void New() {}
9687                         void Open() {}
9688                         void Save() {}
9689                         @separator
9690                         void Exit() @accelerator("Alt+F4") @hotkey('x') {
9691                                 window.close();
9692                         }
9693                 }
9694 
9695                 @menu("Edit") {
9696                         void Undo() {
9697                                 undo();
9698                         }
9699                         @separator
9700                         void Cut() {}
9701                         void Copy() {}
9702                         void Paste() {}
9703                 }
9704 
9705                 @menu("Help") {
9706                         void About() {}
9707                 }
9708         }
9709 
9710         Commands commands;
9711 
9712         window.setMenuAndToolbarFromAnnotatedCode(commands);
9713 	---
9714 
9715 	Note that you can call this function multiple times and it will add the items in order to the given items.
9716 
9717 	+/
9718 	void setMenuAndToolbarFromAnnotatedCode(T)(ref T t) if(!is(T == class) && !is(T == interface)) {
9719 		setMenuAndToolbarFromAnnotatedCode_internal(t);
9720 	}
9721 	void setMenuAndToolbarFromAnnotatedCode(T)(T t) if(is(T == class) || is(T == interface)) {
9722 		setMenuAndToolbarFromAnnotatedCode_internal(t);
9723 	}
9724 	void setMenuAndToolbarFromAnnotatedCode_internal(T)(ref T t) {
9725 		Action[] toolbarActions;
9726 		auto menuBar = this.menuBar is null ? new MenuBar() : this.menuBar;
9727 		Menu[string] mcs;
9728 
9729 		foreach(menu; menuBar.subMenus) {
9730 			mcs[menu.label] = menu;
9731 		}
9732 
9733 		foreach(memberName; __traits(derivedMembers, T)) {
9734 			static if(memberName != "this")
9735 			static if(hasAnyRelevantAnnotations!(__traits(getAttributes, __traits(getMember, T, memberName)))) {
9736 				.menu menu;
9737 				.toolbar toolbar;
9738 				bool separator;
9739 				.accelerator accelerator;
9740 				.hotkey hotkey;
9741 				.icon icon;
9742 				string label;
9743 				string tip;
9744 				foreach(attr; __traits(getAttributes, __traits(getMember, T, memberName))) {
9745 					static if(is(typeof(attr) == .menu))
9746 						menu = attr;
9747 					else static if(is(typeof(attr) == .toolbar))
9748 						toolbar = attr;
9749 					else static if(is(attr == .separator))
9750 						separator = true;
9751 					else static if(is(typeof(attr) == .accelerator))
9752 						accelerator = attr;
9753 					else static if(is(typeof(attr) == .hotkey))
9754 						hotkey = attr;
9755 					else static if(is(typeof(attr) == .icon))
9756 						icon = attr;
9757 					else static if(is(typeof(attr) == .label))
9758 						label = attr.label;
9759 					else static if(is(typeof(attr) == .tip))
9760 						tip = attr.tip;
9761 				}
9762 
9763 				if(menu !is .menu.init || toolbar !is .toolbar.init) {
9764 					ushort correctIcon = icon.id; // FIXME
9765 					if(label.length == 0)
9766 						label = memberName.toMenuLabel;
9767 
9768 					auto handler = makeAutomaticHandler!(__traits(getMember, T, memberName))(&__traits(getMember, t, memberName));
9769 
9770 					auto action = new Action(label, correctIcon, handler);
9771 
9772 					if(accelerator.keyString.length) {
9773 						auto ke = KeyEvent.parse(accelerator.keyString);
9774 						action.accelerator = ke;
9775 						accelerators[ke.toStr] = handler;
9776 					}
9777 
9778 					if(toolbar !is .toolbar.init)
9779 						toolbarActions ~= action;
9780 					if(menu !is .menu.init) {
9781 						Menu mc;
9782 						if(menu.name in mcs) {
9783 							mc = mcs[menu.name];
9784 						} else {
9785 							mc = new Menu(menu.name, this);
9786 							menuBar.addItem(mc);
9787 							mcs[menu.name] = mc;
9788 						}
9789 
9790 						if(separator)
9791 							mc.addSeparator();
9792 						mc.addItem(new MenuItem(action));
9793 					}
9794 				}
9795 			}
9796 		}
9797 
9798 		this.menuBar = menuBar;
9799 
9800 		if(toolbarActions.length) {
9801 			auto tb = new ToolBar(toolbarActions, this);
9802 		}
9803 	}
9804 
9805 	void delegate()[string] accelerators;
9806 
9807 	override void defaultEventHandler_keydown(KeyDownEvent event) {
9808 		auto str = event.originalKeyEvent.toStr;
9809 		if(auto acl = str in accelerators)
9810 			(*acl)();
9811 		super.defaultEventHandler_keydown(event);
9812 	}
9813 
9814 	override void defaultEventHandler_mouseover(MouseOverEvent event) {
9815 		super.defaultEventHandler_mouseover(event);
9816 		if(this.statusBar !is null && event.target.statusTip.length)
9817 			this.statusBar.parts[0].content = event.target.statusTip;
9818 		else if(this.statusBar !is null && this.statusTip.length)
9819 			this.statusBar.parts[0].content = this.statusTip; // ~ " " ~ event.target.toString();
9820 	}
9821 
9822 	override void addChild(Widget c, int position = int.max) {
9823 		if(auto tb = cast(ToolBar) c)
9824 			version(win32_widgets)
9825 				super.addChild(c, 0);
9826 			else version(custom_widgets)
9827 				super.addChild(c, menuBar ? 1 : 0);
9828 			else static assert(0);
9829 		else
9830 			clientArea.addChild(c, position);
9831 	}
9832 
9833 	ToolBar _toolBar;
9834 	///
9835 	ToolBar toolBar() { return _toolBar; }
9836 	///
9837 	ToolBar toolBar(ToolBar t) {
9838 		_toolBar = t;
9839 		foreach(child; this.children)
9840 			if(child is t)
9841 				return t;
9842 		version(win32_widgets)
9843 			super.addChild(t, 0);
9844 		else version(custom_widgets)
9845 			super.addChild(t, menuBar ? 1 : 0);
9846 		else static assert(0);
9847 		return t;
9848 	}
9849 
9850 	MenuBar _menu;
9851 	///
9852 	MenuBar menuBar() { return _menu; }
9853 	///
9854 	MenuBar menuBar(MenuBar m) {
9855 		if(m is _menu) {
9856 			version(custom_widgets)
9857 				recomputeChildLayout();
9858 			return m;
9859 		}
9860 
9861 		if(_menu !is null) {
9862 			// make sure it is sanely removed
9863 			// FIXME
9864 		}
9865 
9866 		_menu = m;
9867 
9868 		version(win32_widgets) {
9869 			SetMenu(parentWindow.win.impl.hwnd, m.handle);
9870 		} else version(custom_widgets) {
9871 			super.addChild(m, 0);
9872 
9873 		//	clientArea.y = menu.height;
9874 		//	clientArea.height = this.height - menu.height;
9875 
9876 			recomputeChildLayout();
9877 		} else static assert(false);
9878 
9879 		return _menu;
9880 	}
9881 	private Widget _clientArea;
9882 	///
9883 	@property Widget clientArea() { return _clientArea; }
9884 	protected @property void clientArea(Widget wid) {
9885 		_clientArea = wid;
9886 	}
9887 
9888 	private StatusBar _statusBar;
9889 	/++
9890 		Returns the window's [StatusBar]. Be warned it may be `null`.
9891 	+/
9892 	@property StatusBar statusBar() { return _statusBar; }
9893 	/// ditto
9894 	@property void statusBar(StatusBar bar) {
9895 		if(_statusBar !is null)
9896 			_statusBar.removeWidget();
9897 		_statusBar = bar;
9898 		if(bar !is null)
9899 			super.addChild(_statusBar);
9900 	}
9901 }
9902 
9903 /+
9904 	This is really an implementation detail of [MainWindow]
9905 +/
9906 private class ClientAreaWidget : Widget {
9907 	this() {
9908 		this.tabStop = false;
9909 		super(null);
9910 		//sa = new ScrollableWidget(this);
9911 	}
9912 	/*
9913 	ScrollableWidget sa;
9914 	override void addChild(Widget w, int position) {
9915 		if(sa is null)
9916 			super.addChild(w, position);
9917 		else {
9918 			sa.addChild(w, position);
9919 			sa.setContentSize(this.minWidth + 1, this.minHeight);
9920 			writeln(sa.contentWidth, "x", sa.contentHeight);
9921 		}
9922 	}
9923 	*/
9924 }
9925 
9926 /**
9927 	Toolbars are lists of buttons (typically icons) that appear under the menu.
9928 	Each button ought to correspond to a menu item, represented by [Action] objects.
9929 */
9930 class ToolBar : Widget {
9931 	version(win32_widgets) {
9932 		private int idealHeight;
9933 		override int minHeight() { return idealHeight; }
9934 		override int maxHeight() { return idealHeight; }
9935 	} else version(custom_widgets) {
9936 		override int minHeight() { return toolbarIconSize; }// defaultLineHeight * 3/2; }
9937 		override int maxHeight() { return toolbarIconSize; } //defaultLineHeight * 3/2; }
9938 	} else static assert(false);
9939 	override int heightStretchiness() { return 0; }
9940 
9941 	version(win32_widgets) {
9942 		HIMAGELIST imageListSmall;
9943 		HIMAGELIST imageListLarge;
9944 	}
9945 
9946 	this(Widget parent) {
9947 		this(null, parent);
9948 	}
9949 
9950 	version(win32_widgets)
9951 	void changeIconSize(bool useLarge) {
9952 		SendMessageW(hwnd, TB_SETIMAGELIST, cast(WPARAM) 0, cast(LPARAM) (useLarge ? imageListLarge : imageListSmall));
9953 
9954 		/+
9955 		SIZE size;
9956 		import core.sys.windows.commctrl;
9957 		SendMessageW(hwnd, TB_GETMAXSIZE, 0, cast(LPARAM) &size);
9958 		idealHeight = size.cy + 4; // the plus 4 is a hack
9959 		+/
9960 
9961 		idealHeight = useLarge ? 34 : 26;
9962 
9963 		if(parent) {
9964 			parent.recomputeChildLayout();
9965 			parent.redraw();
9966 		}
9967 
9968 		SendMessageW(hwnd, TB_SETBUTTONSIZE, 0, (idealHeight-4) << 16 | (idealHeight-4));
9969 		SendMessageW(hwnd, TB_AUTOSIZE, 0, 0);
9970 	}
9971 
9972 	///
9973 	this(Action[] actions, Widget parent) {
9974 		super(parent);
9975 
9976 		tabStop = false;
9977 
9978 		version(win32_widgets) {
9979 			// so i like how the flat thing looks on windows, but not on wine
9980 			// and eh, with windows visual styles enabled it looks cool anyway soooo gonna
9981 			// leave it commented
9982 			createWin32Window(this, "ToolbarWindow32"w, "", TBSTYLE_LIST|/*TBSTYLE_FLAT|*/TBSTYLE_TOOLTIPS);
9983 
9984 			SendMessageW(hwnd, TB_SETEXTENDEDSTYLE, 0, 8/*TBSTYLE_EX_MIXEDBUTTONS*/);
9985 
9986 			imageListSmall = ImageList_Create(
9987 				// width, height
9988 				16, 16,
9989 				ILC_COLOR16 | ILC_MASK,
9990 				16 /*numberOfButtons*/, 0);
9991 
9992 			imageListLarge = ImageList_Create(
9993 				// width, height
9994 				24, 24,
9995 				ILC_COLOR16 | ILC_MASK,
9996 				16 /*numberOfButtons*/, 0);
9997 
9998 			SendMessageW(hwnd, TB_SETIMAGELIST, cast(WPARAM) 0, cast(LPARAM) imageListSmall);
9999 			SendMessageW(hwnd, TB_LOADIMAGES, cast(WPARAM) IDB_STD_SMALL_COLOR, cast(LPARAM) HINST_COMMCTRL);
10000 
10001 			SendMessageW(hwnd, TB_SETIMAGELIST, cast(WPARAM) 0, cast(LPARAM) imageListLarge);
10002 			SendMessageW(hwnd, TB_LOADIMAGES, cast(WPARAM) IDB_STD_LARGE_COLOR, cast(LPARAM) HINST_COMMCTRL);
10003 
10004 			SendMessageW(hwnd, TB_SETMAXTEXTROWS, 0, 0);
10005 
10006 			TBBUTTON[] buttons;
10007 
10008 			// FIXME: I_IMAGENONE is if here is no icon
10009 			foreach(action; actions)
10010 				buttons ~= TBBUTTON(
10011 					MAKELONG(cast(ushort)(action.iconId ? (action.iconId - 1) : -2 /* I_IMAGENONE */), 0),
10012 					action.id,
10013 					TBSTATE_ENABLED, // state
10014 					0, // style
10015 					0, // reserved array, just zero it out
10016 					0, // dwData
10017 					cast(size_t) toWstringzInternal(action.label) // INT_PTR
10018 				);
10019 
10020 			SendMessageW(hwnd, TB_BUTTONSTRUCTSIZE, cast(WPARAM)TBBUTTON.sizeof, 0);
10021 			SendMessageW(hwnd, TB_ADDBUTTONSW, cast(WPARAM) buttons.length, cast(LPARAM)buttons.ptr);
10022 
10023 			/*
10024 			RECT rect;
10025 			GetWindowRect(hwnd, &rect);
10026 			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
10027 			*/
10028 
10029 			dpiChanged(); // to load the things calling changeIconSize the first time
10030 
10031 			assert(idealHeight);
10032 		} else version(custom_widgets) {
10033 			foreach(action; actions)
10034 				new ToolButton(action, this);
10035 		} else static assert(false);
10036 	}
10037 
10038 	override void recomputeChildLayout() {
10039 		.recomputeChildLayout!"width"(this);
10040 	}
10041 
10042 
10043 	version(win32_widgets)
10044 	override protected void dpiChanged() {
10045 		auto sz = scaleWithDpi(16);
10046 		if(sz >= 20)
10047 			changeIconSize(true);
10048 		else
10049 			changeIconSize(false);
10050 	}
10051 }
10052 
10053 enum toolbarIconSize = 24;
10054 
10055 /// 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.
10056 class ToolButton : Button {
10057 	///
10058 	this(string label, Widget parent) {
10059 		super(label, parent);
10060 		tabStop = false;
10061 	}
10062 	///
10063 	this(Action action, Widget parent) {
10064 		super(action.label, parent);
10065 		tabStop = false;
10066 		this.action = action;
10067 	}
10068 
10069 	version(custom_widgets)
10070 	override void defaultEventHandler_click(ClickEvent event) {
10071 		foreach(handler; action.triggered)
10072 			handler();
10073 	}
10074 
10075 	Action action;
10076 
10077 	override int maxWidth() { return toolbarIconSize; }
10078 	override int minWidth() { return toolbarIconSize; }
10079 	override int maxHeight() { return toolbarIconSize; }
10080 	override int minHeight() { return toolbarIconSize; }
10081 
10082 	version(custom_widgets)
10083 	override void paint(WidgetPainter painter) {
10084 	painter.drawThemed(delegate Rectangle (const Rectangle bounds) {
10085 		painter.outlineColor = Color.black;
10086 
10087 		// I want to get from 16 to 24. that's * 3 / 2
10088 		static assert(toolbarIconSize >= 16);
10089 		enum multiplier = toolbarIconSize / 8;
10090 		enum divisor = 2 + ((toolbarIconSize % 8) ? 1 : 0);
10091 		switch(action.iconId) {
10092 			case GenericIcons.New:
10093 				painter.fillColor = Color.white;
10094 				painter.drawPolygon(
10095 					Point(3, 2) * multiplier / divisor, Point(3, 13) * multiplier / divisor, Point(12, 13) * multiplier / divisor, Point(12, 6) * multiplier / divisor,
10096 					Point(8, 2) * multiplier / divisor, Point(8, 6) * multiplier / divisor, Point(12, 6) * multiplier / divisor, Point(8, 2) * multiplier / divisor,
10097 					Point(3, 2) * multiplier / divisor, Point(3, 13) * multiplier / divisor
10098 				);
10099 			break;
10100 			case GenericIcons.Save:
10101 				painter.fillColor = Color.white;
10102 				painter.outlineColor = Color.black;
10103 				painter.drawRectangle(Point(2, 2) * multiplier / divisor, Point(13, 13) * multiplier / divisor);
10104 
10105 				// the label
10106 				painter.drawRectangle(Point(4, 8) * multiplier / divisor, Point(11, 13) * multiplier / divisor);
10107 
10108 				// the slider
10109 				painter.fillColor = Color.black;
10110 				painter.outlineColor = Color.black;
10111 				painter.drawRectangle(Point(4, 3) * multiplier / divisor, Point(10, 6) * multiplier / divisor);
10112 
10113 				painter.fillColor = Color.white;
10114 				painter.outlineColor = Color.white;
10115 				// the disc window
10116 				painter.drawRectangle(Point(5, 3) * multiplier / divisor, Point(6, 5) * multiplier / divisor);
10117 			break;
10118 			case GenericIcons.Open:
10119 				painter.fillColor = Color.white;
10120 				painter.drawPolygon(
10121 					Point(4, 4) * multiplier / divisor, Point(4, 12) * multiplier / divisor, Point(13, 12) * multiplier / divisor, Point(13, 3) * multiplier / divisor,
10122 					Point(9, 3) * multiplier / divisor, Point(9, 4) * multiplier / divisor, Point(4, 4) * multiplier / divisor);
10123 				painter.drawPolygon(
10124 					Point(2, 6) * multiplier / divisor, Point(11, 6) * multiplier / divisor,
10125 					Point(12, 12) * multiplier / divisor, Point(4, 12) * multiplier / divisor,
10126 					Point(2, 6) * multiplier / divisor);
10127 				//painter.drawLine(Point(9, 6) * multiplier / divisor, Point(13, 7) * multiplier / divisor);
10128 			break;
10129 			case GenericIcons.Copy:
10130 				painter.fillColor = Color.white;
10131 				painter.drawRectangle(Point(3, 2) * multiplier / divisor, Point(9, 10) * multiplier / divisor);
10132 				painter.drawRectangle(Point(6, 5) * multiplier / divisor, Point(12, 13) * multiplier / divisor);
10133 			break;
10134 			case GenericIcons.Cut:
10135 				painter.fillColor = Color.transparent;
10136 				painter.outlineColor = getComputedStyle.foregroundColor();
10137 				painter.drawLine(Point(3, 2) * multiplier / divisor, Point(10, 9) * multiplier / divisor);
10138 				painter.drawLine(Point(4, 9) * multiplier / divisor, Point(11, 2) * multiplier / divisor);
10139 				painter.drawRectangle(Point(3, 9) * multiplier / divisor, Point(5, 13) * multiplier / divisor);
10140 				painter.drawRectangle(Point(9, 9) * multiplier / divisor, Point(11, 12) * multiplier / divisor);
10141 			break;
10142 			case GenericIcons.Paste:
10143 				painter.fillColor = Color.white;
10144 				painter.drawRectangle(Point(2, 3) * multiplier / divisor, Point(11, 11) * multiplier / divisor);
10145 				painter.drawRectangle(Point(6, 8) * multiplier / divisor, Point(13, 13) * multiplier / divisor);
10146 				painter.drawLine(Point(6, 2) * multiplier / divisor, Point(4, 5) * multiplier / divisor);
10147 				painter.drawLine(Point(6, 2) * multiplier / divisor, Point(9, 5) * multiplier / divisor);
10148 				painter.fillColor = Color.black;
10149 				painter.drawRectangle(Point(4, 5) * multiplier / divisor, Point(9, 6) * multiplier / divisor);
10150 			break;
10151 			case GenericIcons.Help:
10152 				painter.outlineColor = getComputedStyle.foregroundColor();
10153 				painter.drawText(Point(0, 0), "?", Point(width, height), TextAlignment.Center | TextAlignment.VerticalCenter);
10154 			break;
10155 			case GenericIcons.Undo:
10156 				painter.fillColor = Color.transparent;
10157 				painter.drawArc(Point(3, 4) * multiplier / divisor, 9 * multiplier / divisor, 9 * multiplier / divisor, 0, 360 * 64);
10158 				painter.outlineColor = Color.black;
10159 				painter.fillColor = Color.black;
10160 				painter.drawPolygon(
10161 					Point(4, 4) * multiplier / divisor,
10162 					Point(8, 2) * multiplier / divisor,
10163 					Point(8, 6) * multiplier / divisor,
10164 					Point(4, 4) * multiplier / divisor,
10165 				);
10166 			break;
10167 			case GenericIcons.Redo:
10168 				painter.fillColor = Color.transparent;
10169 				painter.drawArc(Point(3, 4) * multiplier / divisor, 9 * multiplier / divisor, 9 * multiplier / divisor, 0, 360 * 64);
10170 				painter.outlineColor = Color.black;
10171 				painter.fillColor = Color.black;
10172 				painter.drawPolygon(
10173 					Point(10, 4) * multiplier / divisor,
10174 					Point(6, 2) * multiplier / divisor,
10175 					Point(6, 6) * multiplier / divisor,
10176 					Point(10, 4) * multiplier / divisor,
10177 				);
10178 			break;
10179 			default:
10180 				painter.drawText(Point(0, 0), action.label, Point(width, height), TextAlignment.Center | TextAlignment.VerticalCenter);
10181 		}
10182 		return bounds;
10183 		});
10184 	}
10185 
10186 }
10187 
10188 
10189 ///
10190 class MenuBar : Widget {
10191 	MenuItem[] items;
10192 	Menu[] subMenus;
10193 
10194 	version(win32_widgets) {
10195 		HMENU handle;
10196 		///
10197 		this(Widget parent = null) {
10198 			super(parent);
10199 
10200 			handle = CreateMenu();
10201 			tabStop = false;
10202 		}
10203 	} else version(custom_widgets) {
10204 		///
10205 		this(Widget parent = null) {
10206 			tabStop = false; // these are selected some other way
10207 			super(parent);
10208 		}
10209 
10210 		mixin Padding!q{2};
10211 	} else static assert(false);
10212 
10213 	version(custom_widgets)
10214 	override void paint(WidgetPainter painter) {
10215 		draw3dFrame(this, painter, FrameStyle.risen, getComputedStyle().background.color);
10216 	}
10217 
10218 	///
10219 	MenuItem addItem(MenuItem item) {
10220 		this.addChild(item);
10221 		items ~= item;
10222 		version(win32_widgets) {
10223 			AppendMenuW(handle, MF_STRING, item.action is null ? 9000 : item.action.id, toWstringzInternal(item.label));
10224 		}
10225 		return item;
10226 	}
10227 
10228 
10229 	///
10230 	Menu addItem(Menu item) {
10231 
10232 		subMenus ~= item;
10233 
10234 		auto mbItem = new MenuItem(item.label, null);// this.parentWindow); // I'ma add the child down below so hopefully this isn't too insane
10235 
10236 		addChild(mbItem);
10237 		items ~= mbItem;
10238 
10239 		version(win32_widgets) {
10240 			AppendMenuW(handle, MF_STRING | MF_POPUP, cast(UINT) item.handle, toWstringzInternal(item.label));
10241 		} else version(custom_widgets) {
10242 			mbItem.defaultEventHandlers["mousedown"] = (Widget e, Event ev) {
10243 				item.popup(mbItem);
10244 			};
10245 		} else static assert(false);
10246 
10247 		return item;
10248 	}
10249 
10250 	override void recomputeChildLayout() {
10251 		.recomputeChildLayout!"width"(this);
10252 	}
10253 
10254 	override int maxHeight() { return defaultLineHeight + 4; }
10255 	override int minHeight() { return defaultLineHeight + 4; }
10256 }
10257 
10258 
10259 /**
10260 	Status bars appear at the bottom of a MainWindow.
10261 	They are made out of Parts, with a width and content.
10262 
10263 	They can have multiple parts or be in simple mode. FIXME: implement simple mode.
10264 
10265 
10266 	sb.parts[0].content = "Status bar text!";
10267 */
10268 class StatusBar : Widget {
10269 	private Part[] partsArray;
10270 	///
10271 	struct Parts {
10272 		@disable this();
10273 		this(StatusBar owner) { this.owner = owner; }
10274 		//@disable this(this);
10275 		///
10276 		@property int length() { return cast(int) owner.partsArray.length; }
10277 		private StatusBar owner;
10278 		private this(StatusBar owner, Part[] parts) {
10279 			this.owner.partsArray = parts;
10280 			this.owner = owner;
10281 		}
10282 		///
10283 		Part opIndex(int p) {
10284 			if(owner.partsArray.length == 0)
10285 				this ~= new StatusBar.Part(300);
10286 			return owner.partsArray[p];
10287 		}
10288 
10289 		///
10290 		Part opOpAssign(string op : "~" )(Part p) {
10291 			assert(owner.partsArray.length < 255);
10292 			p.owner = this.owner;
10293 			p.idx = cast(int) owner.partsArray.length;
10294 			owner.partsArray ~= p;
10295 			version(win32_widgets) {
10296 				int[256] pos;
10297 				int cpos = 0;
10298 				foreach(idx, part; owner.partsArray) {
10299 					if(part.width)
10300 						cpos += part.width;
10301 					else
10302 						cpos += 100;
10303 
10304 					if(idx + 1 == owner.partsArray.length)
10305 						pos[idx] = -1;
10306 					else
10307 						pos[idx] = cpos;
10308 				}
10309 				SendMessageW(owner.hwnd, WM_USER + 4 /*SB_SETPARTS*/, owner.partsArray.length, cast(size_t) pos.ptr);
10310 			} else version(custom_widgets) {
10311 				owner.redraw();
10312 			} else static assert(false);
10313 
10314 			return p;
10315 		}
10316 	}
10317 
10318 	private Parts _parts;
10319 	///
10320 	final @property Parts parts() {
10321 		return _parts;
10322 	}
10323 
10324 	///
10325 	static class Part {
10326 		int width;
10327 		StatusBar owner;
10328 
10329 		///
10330 		this(int w = 100) { width = w; }
10331 
10332 		private int idx;
10333 		private string _content;
10334 		///
10335 		@property string content() { return _content; }
10336 		///
10337 		@property void content(string s) {
10338 			version(win32_widgets) {
10339 				_content = s;
10340 				WCharzBuffer bfr = WCharzBuffer(s);
10341 				SendMessageW(owner.hwnd, SB_SETTEXT, idx, cast(LPARAM) bfr.ptr);
10342 			} else version(custom_widgets) {
10343 				if(_content != s) {
10344 					_content = s;
10345 					owner.redraw();
10346 				}
10347 			} else static assert(false);
10348 		}
10349 	}
10350 	string simpleModeContent;
10351 	bool inSimpleMode;
10352 
10353 
10354 	///
10355 	this(Widget parent) {
10356 		super(null); // FIXME
10357 		_parts = Parts(this);
10358 		tabStop = false;
10359 		version(win32_widgets) {
10360 			parentWindow = parent.parentWindow;
10361 			createWin32Window(this, "msctls_statusbar32"w, "", 0);
10362 
10363 			RECT rect;
10364 			GetWindowRect(hwnd, &rect);
10365 			idealHeight = rect.bottom - rect.top;
10366 			assert(idealHeight);
10367 		} else version(custom_widgets) {
10368 		} else static assert(false);
10369 	}
10370 
10371 	version(win32_widgets)
10372 	override protected void dpiChanged() {
10373 		RECT rect;
10374 		GetWindowRect(hwnd, &rect);
10375 		idealHeight = rect.bottom - rect.top;
10376 		assert(idealHeight);
10377 	}
10378 
10379 	version(custom_widgets)
10380 	override void paint(WidgetPainter painter) {
10381 		auto cs = getComputedStyle();
10382 		this.draw3dFrame(painter, FrameStyle.sunk, cs.background.color);
10383 		int cpos = 0;
10384 		int remainingLength = this.width;
10385 		foreach(idx, part; this.partsArray) {
10386 			auto partWidth = part.width ? part.width : ((idx + 1 == this.partsArray.length) ? remainingLength : 100);
10387 			painter.setClipRectangle(Point(cpos, 0), partWidth, height);
10388 			draw3dFrame(cpos, 0, partWidth, height, painter, FrameStyle.sunk, cs.background.color);
10389 			painter.setClipRectangle(Point(cpos + 2, 2), partWidth - 4, height - 4);
10390 
10391 			painter.outlineColor = cs.foregroundColor();
10392 			painter.fillColor = cs.foregroundColor();
10393 
10394 			painter.drawText(Point(cpos + 4, 0), part.content, Point(width, height), TextAlignment.VerticalCenter);
10395 			cpos += partWidth;
10396 			remainingLength -= partWidth;
10397 		}
10398 	}
10399 
10400 
10401 	version(win32_widgets) {
10402 		private int idealHeight;
10403 		override int maxHeight() { return idealHeight; }
10404 		override int minHeight() { return idealHeight; }
10405 	} else version(custom_widgets) {
10406 		override int maxHeight() { return defaultLineHeight + 4; }
10407 		override int minHeight() { return defaultLineHeight + 4; }
10408 	} else static assert(false);
10409 }
10410 
10411 /// Displays an in-progress indicator without known values
10412 version(none)
10413 class IndefiniteProgressBar : Widget {
10414 	version(win32_widgets)
10415 	this(Widget parent) {
10416 		super(parent);
10417 		createWin32Window(this, "msctls_progress32"w, "", 8 /* PBS_MARQUEE */);
10418 		tabStop = false;
10419 	}
10420 	override int minHeight() { return 10; }
10421 }
10422 
10423 /// A progress bar with a known endpoint and completion amount
10424 class ProgressBar : Widget {
10425 	/++
10426 		History:
10427 			Added March 16, 2022 (dub v10.7)
10428 	+/
10429 	this(int min, int max, Widget parent) {
10430 		this(parent);
10431 		setRange(cast(ushort) min, cast(ushort) max); // FIXME
10432 	}
10433 	this(Widget parent) {
10434 		version(win32_widgets) {
10435 			super(parent);
10436 			createWin32Window(this, "msctls_progress32"w, "", 0);
10437 			tabStop = false;
10438 		} else version(custom_widgets) {
10439 			super(parent);
10440 			max = 100;
10441 			step = 10;
10442 			tabStop = false;
10443 		} else static assert(0);
10444 	}
10445 
10446 	version(custom_widgets)
10447 	override void paint(WidgetPainter painter) {
10448 		auto cs = getComputedStyle();
10449 		this.draw3dFrame(painter, FrameStyle.sunk, cs.background.color);
10450 		painter.fillColor = cs.progressBarColor;
10451 		painter.drawRectangle(Point(0, 0), width * current / max, height);
10452 	}
10453 
10454 
10455 	version(custom_widgets) {
10456 		int current;
10457 		int max;
10458 		int step;
10459 	}
10460 
10461 	///
10462 	void advanceOneStep() {
10463 		version(win32_widgets)
10464 			SendMessageW(hwnd, PBM_STEPIT, 0, 0);
10465 		else version(custom_widgets)
10466 			addToPosition(step);
10467 		else static assert(false);
10468 	}
10469 
10470 	///
10471 	void setStepIncrement(int increment) {
10472 		version(win32_widgets)
10473 			SendMessageW(hwnd, PBM_SETSTEP, increment, 0);
10474 		else version(custom_widgets)
10475 			step = increment;
10476 		else static assert(false);
10477 	}
10478 
10479 	///
10480 	void addToPosition(int amount) {
10481 		version(win32_widgets)
10482 			SendMessageW(hwnd, PBM_DELTAPOS, amount, 0);
10483 		else version(custom_widgets)
10484 			setPosition(current + amount);
10485 		else static assert(false);
10486 	}
10487 
10488 	///
10489 	void setPosition(int pos) {
10490 		version(win32_widgets)
10491 			SendMessageW(hwnd, PBM_SETPOS, pos, 0);
10492 		else version(custom_widgets) {
10493 			current = pos;
10494 			if(current > max)
10495 				current = max;
10496 			redraw();
10497 		}
10498 		else static assert(false);
10499 	}
10500 
10501 	///
10502 	void setRange(ushort min, ushort max) {
10503 		version(win32_widgets)
10504 			SendMessageW(hwnd, PBM_SETRANGE, 0, MAKELONG(min, max));
10505 		else version(custom_widgets) {
10506 			this.max = max;
10507 		}
10508 		else static assert(false);
10509 	}
10510 
10511 	override int minHeight() { return 10; }
10512 }
10513 
10514 version(custom_widgets)
10515 private void extractWindowsStyleLabel(scope const char[] label, out string thisLabel, out dchar thisAccelerator) {
10516 	thisLabel.reserve(label.length);
10517 	bool justSawAmpersand;
10518 	foreach(ch; label) {
10519 		if(justSawAmpersand) {
10520 			justSawAmpersand = false;
10521 			if(ch == '&') {
10522 				goto plain;
10523 			}
10524 			thisAccelerator = ch;
10525 		} else {
10526 			if(ch == '&') {
10527 				justSawAmpersand = true;
10528 				continue;
10529 			}
10530 			plain:
10531 			thisLabel ~= ch;
10532 		}
10533 	}
10534 }
10535 
10536 /++
10537 	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.
10538 
10539 
10540 	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
10541 
10542 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
10543 
10544 	History:
10545 		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.
10546 +/
10547 class Fieldset : Widget {
10548 	// FIXME: on Windows,it doesn't draw the background on the label
10549 	// on X, it doesn't fix the clipping rectangle for it
10550 	version(win32_widgets)
10551 		override int paddingTop() { return defaultLineHeight; }
10552 	else version(custom_widgets)
10553 		override int paddingTop() { return defaultLineHeight + 2; }
10554 	else static assert(false);
10555 	override int paddingBottom() { return 6; }
10556 	override int paddingLeft() { return 6; }
10557 	override int paddingRight() { return 6; }
10558 
10559 	override int marginLeft() { return 6; }
10560 	override int marginRight() { return 6; }
10561 	override int marginTop() { return 2; }
10562 	override int marginBottom() { return 2; }
10563 
10564 	string legend;
10565 
10566 	version(custom_widgets) private dchar accelerator;
10567 
10568 	this(string legend, Widget parent) {
10569 		version(win32_widgets) {
10570 			super(parent);
10571 			this.legend = legend;
10572 			createWin32Window(this, "button"w, legend, BS_GROUPBOX);
10573 			tabStop = false;
10574 		} else version(custom_widgets) {
10575 			super(parent);
10576 			tabStop = false;
10577 
10578 			legend.extractWindowsStyleLabel(this.legend, this.accelerator);
10579 		} else static assert(0);
10580 	}
10581 
10582 	version(custom_widgets)
10583 	override void paint(WidgetPainter painter) {
10584 		painter.fillColor = Color.transparent;
10585 		auto cs = getComputedStyle();
10586 		painter.pen = Pen(cs.foregroundColor, 1);
10587 		painter.drawRectangle(Point(0, defaultLineHeight / 2), width, height - defaultLineHeight / 2);
10588 
10589 		auto tx = painter.textSize(legend);
10590 		painter.outlineColor = Color.transparent;
10591 
10592 		static if(UsingSimpledisplayX11) {
10593 			painter.fillColor = getComputedStyle().windowBackgroundColor;
10594 			painter.drawRectangle(Point(8, 0), tx.width, tx.height);
10595 		} else version(Windows) {
10596 			auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
10597 			painter.drawRectangle(Point(8, -tx.height/2), tx.width, tx.height);
10598 			SelectObject(painter.impl.hdc, b);
10599 		} else static assert(0);
10600 		painter.outlineColor = cs.foregroundColor;
10601 		painter.drawText(Point(8, 0), legend);
10602 	}
10603 
10604 	override int maxHeight() {
10605 		auto m = paddingTop() + paddingBottom();
10606 		foreach(child; children) {
10607 			auto mh = child.maxHeight();
10608 			if(mh == int.max)
10609 				return int.max;
10610 			m += mh;
10611 			m += child.marginBottom();
10612 			m += child.marginTop();
10613 		}
10614 		m += 6;
10615 		if(m < minHeight)
10616 			return minHeight;
10617 		return m;
10618 	}
10619 
10620 	override int minHeight() {
10621 		auto m = paddingTop() + paddingBottom();
10622 		foreach(child; children) {
10623 			m += child.minHeight();
10624 			m += child.marginBottom();
10625 			m += child.marginTop();
10626 		}
10627 		return m + 6;
10628 	}
10629 }
10630 
10631 /++
10632 	$(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")
10633 	$(IMG //arsdnet.net/minigui-screenshots/linux/Fieldset.png, Same thing, but in the default Linux theme.)
10634 +/
10635 version(minigui_screenshots)
10636 @Screenshot("Fieldset")
10637 unittest {
10638 	auto window = new Window(200, 100);
10639 	auto set = new Fieldset("Baby will", window);
10640 	auto option1 = new Radiobox("Eat", set);
10641 	auto option2 = new Radiobox("Cry", set);
10642 	auto option3 = new Radiobox("Sleep", set);
10643 	window.loop();
10644 }
10645 
10646 /// Draws a line
10647 class HorizontalRule : Widget {
10648 	mixin Margin!q{ 2 };
10649 	override int minHeight() { return 2; }
10650 	override int maxHeight() { return 2; }
10651 
10652 	///
10653 	this(Widget parent) {
10654 		super(parent);
10655 	}
10656 
10657 	override void paint(WidgetPainter painter) {
10658 		auto cs = getComputedStyle();
10659 		painter.outlineColor = cs.darkAccentColor;
10660 		painter.drawLine(Point(0, 0), Point(width, 0));
10661 		painter.outlineColor = cs.lightAccentColor;
10662 		painter.drawLine(Point(0, 1), Point(width, 1));
10663 	}
10664 }
10665 
10666 version(minigui_screenshots)
10667 @Screenshot("HorizontalRule")
10668 /++
10669 	$(IMG //arsdnet.net/minigui-screenshots/linux/HorizontalRule.png, Same thing, but in the default Linux theme.)
10670 
10671 +/
10672 unittest {
10673 	auto window = new Window(200, 100);
10674 	auto above = new TextLabel("Above the line", TextAlignment.Left, window);
10675 	new HorizontalRule(window);
10676 	auto below = new TextLabel("Below the line", TextAlignment.Left, window);
10677 	window.loop();
10678 }
10679 
10680 /// ditto
10681 class VerticalRule : Widget {
10682 	mixin Margin!q{ 2 };
10683 	override int minWidth() { return 2; }
10684 	override int maxWidth() { return 2; }
10685 
10686 	///
10687 	this(Widget parent) {
10688 		super(parent);
10689 	}
10690 
10691 	override void paint(WidgetPainter painter) {
10692 		auto cs = getComputedStyle();
10693 		painter.outlineColor = cs.darkAccentColor;
10694 		painter.drawLine(Point(0, 0), Point(0, height));
10695 		painter.outlineColor = cs.lightAccentColor;
10696 		painter.drawLine(Point(1, 0), Point(1, height));
10697 	}
10698 }
10699 
10700 
10701 ///
10702 class Menu : Window {
10703 	void remove() {
10704 		foreach(i, child; parentWindow.children)
10705 			if(child is this) {
10706 				parentWindow._children = parentWindow._children[0 .. i] ~ parentWindow._children[i + 1 .. $];
10707 				break;
10708 			}
10709 		parentWindow.redraw();
10710 
10711 		parentWindow.releaseMouseCapture();
10712 	}
10713 
10714 	///
10715 	void addSeparator() {
10716 		version(win32_widgets)
10717 			AppendMenu(handle, MF_SEPARATOR, 0, null);
10718 		else version(custom_widgets)
10719 			auto hr = new HorizontalRule(this);
10720 		else static assert(0);
10721 	}
10722 
10723 	override int paddingTop() { return 4; }
10724 	override int paddingBottom() { return 4; }
10725 	override int paddingLeft() { return 2; }
10726 	override int paddingRight() { return 2; }
10727 
10728 	version(win32_widgets) {}
10729 	else version(custom_widgets) {
10730 		SimpleWindow dropDown;
10731 		Widget menuParent;
10732 		void popup(Widget parent, int offsetX = 0, int offsetY = int.min) {
10733 			this.menuParent = parent;
10734 
10735 			int w = 150;
10736 			int h = paddingTop + paddingBottom;
10737 			if(this.children.length) {
10738 				// hacking it to get the ideal height out of recomputeChildLayout
10739 				this.width = w;
10740 				this.height = h;
10741 				this.recomputeChildLayout();
10742 				h = this.children[$-1].y + this.children[$-1].height + this.children[$-1].marginBottom;
10743 				h += paddingBottom;
10744 
10745 				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
10746 			}
10747 
10748 			if(offsetY == int.min)
10749 				offsetY = parent.defaultLineHeight;
10750 
10751 			auto coord = parent.globalCoordinates();
10752 			dropDown.moveResize(coord.x + offsetX, coord.y + offsetY, w, h);
10753 			this.x = 0;
10754 			this.y = 0;
10755 			this.width = dropDown.width;
10756 			this.height = dropDown.height;
10757 			this.drawableWindow = dropDown;
10758 			this.recomputeChildLayout();
10759 
10760 			static if(UsingSimpledisplayX11)
10761 				XSync(XDisplayConnection.get, 0);
10762 
10763 			dropDown.visibilityChanged = (bool visible) {
10764 				if(visible) {
10765 					this.redraw();
10766 					dropDown.grabInput();
10767 				} else {
10768 					dropDown.releaseInputGrab();
10769 				}
10770 			};
10771 
10772 			dropDown.show();
10773 
10774 			clickListener = this.addEventListener((scope ClickEvent ev) {
10775 				unpopup();
10776 				// need to unlock asap just in case other user handlers block...
10777 				static if(UsingSimpledisplayX11)
10778 					flushGui();
10779 			}, true /* again for asap action */);
10780 		}
10781 
10782 		EventListener clickListener;
10783 	}
10784 	else static assert(false);
10785 
10786 	version(custom_widgets)
10787 	void unpopup() {
10788 		mouseLastOver = mouseLastDownOn = null;
10789 		dropDown.hide();
10790 		if(!menuParent.parentWindow.win.closed) {
10791 			if(auto maw = cast(MouseActivatedWidget) menuParent) {
10792 				maw.setDynamicState(DynamicState.depressed, false);
10793 				maw.setDynamicState(DynamicState.hover, false);
10794 				maw.redraw();
10795 			}
10796 			// menuParent.parentWindow.win.focus();
10797 		}
10798 		clickListener.disconnect();
10799 	}
10800 
10801 	MenuItem[] items;
10802 
10803 	///
10804 	MenuItem addItem(MenuItem item) {
10805 		addChild(item);
10806 		items ~= item;
10807 		version(win32_widgets) {
10808 			AppendMenuW(handle, MF_STRING, item.action is null ? 9000 : item.action.id, toWstringzInternal(item.label));
10809 		}
10810 		return item;
10811 	}
10812 
10813 	string label;
10814 
10815 	version(win32_widgets) {
10816 		HMENU handle;
10817 		///
10818 		this(string label, Widget parent) {
10819 			// not actually passing the parent since it effs up the drawing
10820 			super(cast(Widget) null);// parent);
10821 			this.label = label;
10822 			handle = CreatePopupMenu();
10823 		}
10824 	} else version(custom_widgets) {
10825 		///
10826 		this(string label, Widget parent) {
10827 
10828 			if(dropDown) {
10829 				dropDown.close();
10830 			}
10831 			dropDown = new SimpleWindow(
10832 				150, 4,
10833 				null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.dropdownMenu, WindowFlags.dontAutoShow, parent ? parent.parentWindow.win : null);
10834 
10835 			this.label = label;
10836 
10837 			super(dropDown);
10838 		}
10839 	} else static assert(false);
10840 
10841 	override int maxHeight() { return defaultLineHeight; }
10842 	override int minHeight() { return defaultLineHeight; }
10843 
10844 	version(custom_widgets)
10845 	override void paint(WidgetPainter painter) {
10846 		this.draw3dFrame(painter, FrameStyle.risen, getComputedStyle.background.color);
10847 	}
10848 }
10849 
10850 /++
10851 	A MenuItem belongs to a [Menu] - use [Menu.addItem] to add one - and calls an [Action] when it is clicked.
10852 +/
10853 class MenuItem : MouseActivatedWidget {
10854 	Menu submenu;
10855 
10856 	Action action;
10857 	string label;
10858 
10859 	override int paddingLeft() { return 4; }
10860 
10861 	override int maxHeight() { return defaultLineHeight + 4; }
10862 	override int minHeight() { return defaultLineHeight + 4; }
10863 	override int minWidth() { return defaultTextWidth(label) + 8 + scaleWithDpi(12); }
10864 	override int maxWidth() {
10865 		if(cast(MenuBar) parent) {
10866 			return minWidth();
10867 		}
10868 		return int.max;
10869 	}
10870 	/// This should ONLY be used if there is no associated action, for example, if the menu item is just a submenu.
10871 	this(string lbl, Widget parent = null) {
10872 		super(parent);
10873 		//label = lbl; // FIXME
10874 		foreach(char ch; lbl) // FIXME
10875 			if(ch != '&') // FIXME
10876 				label ~= ch; // FIXME
10877 		tabStop = false; // these are selected some other way
10878 	}
10879 
10880 	///
10881 	this(Action action, Widget parent = null) {
10882 		assert(action !is null);
10883 		this(action.label, parent);
10884 		this.action = action;
10885 		tabStop = false; // these are selected some other way
10886 	}
10887 
10888 	version(custom_widgets)
10889 	override void paint(WidgetPainter painter) {
10890 		auto cs = getComputedStyle();
10891 		if(dynamicState & DynamicState.depressed)
10892 			this.draw3dFrame(painter, FrameStyle.sunk, cs.background.color);
10893 		if(dynamicState & DynamicState.hover)
10894 			painter.outlineColor = cs.activeMenuItemColor;
10895 		else
10896 			painter.outlineColor = cs.foregroundColor;
10897 		painter.fillColor = Color.transparent;
10898 		painter.drawText(scaleWithDpi(Point(cast(MenuBar) this.parent ? 4 : 20, 0)), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
10899 		if(action && action.accelerator !is KeyEvent.init) {
10900 			painter.drawText(scaleWithDpi(Point(cast(MenuBar) this.parent ? 4 : 20, 0)), action.accelerator.toStr(), Point(width - 4, height), TextAlignment.Right | TextAlignment.VerticalCenter);
10901 
10902 		}
10903 	}
10904 
10905 	static class Style : Widget.Style {
10906 		override bool variesWithState(ulong dynamicStateFlags) {
10907 			return super.variesWithState(dynamicStateFlags) || (dynamicStateFlags & (DynamicState.depressed | DynamicState.hover));
10908 		}
10909 	}
10910 	mixin OverrideStyle!Style;
10911 
10912 	override void defaultEventHandler_triggered(Event event) {
10913 		if(action)
10914 		foreach(handler; action.triggered)
10915 			handler();
10916 
10917 		if(auto pmenu = cast(Menu) this.parent)
10918 			pmenu.remove();
10919 
10920 		super.defaultEventHandler_triggered(event);
10921 	}
10922 }
10923 
10924 version(win32_widgets)
10925 /// A "mouse activiated widget" is really just an abstract variant of button.
10926 class MouseActivatedWidget : Widget {
10927 	@property bool isChecked() {
10928 		assert(hwnd);
10929 		return SendMessageW(hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED;
10930 
10931 	}
10932 	@property void isChecked(bool state) {
10933 		assert(hwnd);
10934 		SendMessageW(hwnd, BM_SETCHECK, state ? BST_CHECKED : BST_UNCHECKED, 0);
10935 
10936 	}
10937 
10938 	override void handleWmCommand(ushort cmd, ushort id) {
10939 		if(cmd == 0) {
10940 			auto event = new Event(EventType.triggered, this);
10941 			event.dispatch();
10942 		}
10943 	}
10944 
10945 	this(Widget parent) {
10946 		super(parent);
10947 	}
10948 }
10949 else version(custom_widgets)
10950 /// ditto
10951 class MouseActivatedWidget : Widget {
10952 	@property bool isChecked() { return isChecked_; }
10953 	@property bool isChecked(bool b) { return isChecked_ = b; }
10954 
10955 	private bool isChecked_;
10956 
10957 	this(Widget parent) {
10958 		super(parent);
10959 
10960 		addEventListener((MouseDownEvent ev) {
10961 			if(ev.button == MouseButton.left) {
10962 				setDynamicState(DynamicState.depressed, true);
10963 				setDynamicState(DynamicState.hover, true);
10964 				redraw();
10965 			}
10966 		});
10967 
10968 		addEventListener((MouseUpEvent ev) {
10969 			if(ev.button == MouseButton.left) {
10970 				setDynamicState(DynamicState.depressed, false);
10971 				setDynamicState(DynamicState.hover, false);
10972 				redraw();
10973 			}
10974 		});
10975 
10976 		addEventListener((MouseMoveEvent mme) {
10977 			if(!(mme.state & ModifierState.leftButtonDown)) {
10978 				if(dynamicState_ & DynamicState.depressed) {
10979 					setDynamicState(DynamicState.depressed, false);
10980 					redraw();
10981 				}
10982 			}
10983 		});
10984 	}
10985 
10986 	override void defaultEventHandler_focus(Event ev) {
10987 		super.defaultEventHandler_focus(ev);
10988 		this.redraw();
10989 	}
10990 	override void defaultEventHandler_blur(Event ev) {
10991 		super.defaultEventHandler_blur(ev);
10992 		setDynamicState(DynamicState.depressed, false);
10993 		this.redraw();
10994 	}
10995 	override void defaultEventHandler_keydown(KeyDownEvent ev) {
10996 		super.defaultEventHandler_keydown(ev);
10997 		if(ev.key == Key.Space || ev.key == Key.Enter || ev.key == Key.PadEnter) {
10998 			setDynamicState(DynamicState.depressed, true);
10999 			setDynamicState(DynamicState.hover, true);
11000 			this.redraw();
11001 		}
11002 	}
11003 	override void defaultEventHandler_keyup(KeyUpEvent ev) {
11004 		super.defaultEventHandler_keyup(ev);
11005 		if(!(dynamicState & DynamicState.depressed))
11006 			return;
11007 		setDynamicState(DynamicState.depressed, false);
11008 		setDynamicState(DynamicState.hover, false);
11009 		this.redraw();
11010 
11011 		auto event = new Event(EventType.triggered, this);
11012 		event.sendDirectly();
11013 	}
11014 	override void defaultEventHandler_click(ClickEvent ev) {
11015 		super.defaultEventHandler_click(ev);
11016 		if(ev.button == MouseButton.left) {
11017 			auto event = new Event(EventType.triggered, this);
11018 			event.sendDirectly();
11019 		}
11020 	}
11021 
11022 }
11023 else static assert(false);
11024 
11025 /*
11026 /++
11027 	Like the tablet thing, it would have a label, a description, and a switch slider thingy.
11028 
11029 	Basically the same as a checkbox.
11030 +/
11031 class OnOffSwitch : MouseActivatedWidget {
11032 
11033 }
11034 */
11035 
11036 /++
11037 	History:
11038 		Added June 15, 2021 (dub v10.1)
11039 +/
11040 struct ImageLabel {
11041 	/++
11042 		Defines a label+image combo used by some widgets.
11043 
11044 		If you provide just a text label, that is all the widget will try to
11045 		display. Or just an image will display just that. If you provide both,
11046 		it may display both text and image side by side or display the image
11047 		and offer text on an input event depending on the widget.
11048 
11049 		History:
11050 			The `alignment` parameter was added on September 27, 2021
11051 	+/
11052 	this(string label, TextAlignment alignment = TextAlignment.Center) {
11053 		this.label = label;
11054 		this.displayFlags = DisplayFlags.displayText;
11055 		this.alignment = alignment;
11056 	}
11057 
11058 	/// ditto
11059 	this(string label, MemoryImage image, TextAlignment alignment = TextAlignment.Center) {
11060 		this.label = label;
11061 		this.image = image;
11062 		this.displayFlags = DisplayFlags.displayText | DisplayFlags.displayImage;
11063 		this.alignment = alignment;
11064 	}
11065 
11066 	/// ditto
11067 	this(MemoryImage image, TextAlignment alignment = TextAlignment.Center) {
11068 		this.image = image;
11069 		this.displayFlags = DisplayFlags.displayImage;
11070 		this.alignment = alignment;
11071 	}
11072 
11073 	/// ditto
11074 	this(string label, MemoryImage image, int displayFlags, TextAlignment alignment = TextAlignment.Center) {
11075 		this.label = label;
11076 		this.image = image;
11077 		this.alignment = alignment;
11078 		this.displayFlags = displayFlags;
11079 	}
11080 
11081 	string label;
11082 	MemoryImage image;
11083 
11084 	enum DisplayFlags {
11085 		displayText = 1 << 0,
11086 		displayImage = 1 << 1,
11087 	}
11088 
11089 	int displayFlags = DisplayFlags.displayText | DisplayFlags.displayImage;
11090 
11091 	TextAlignment alignment;
11092 }
11093 
11094 /++
11095 	A basic checked or not checked box with an attached label.
11096 
11097 
11098 	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
11099 
11100 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
11101 
11102 	History:
11103 		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.
11104 +/
11105 class Checkbox : MouseActivatedWidget {
11106 	version(win32_widgets) {
11107 		override int maxHeight() { return scaleWithDpi(16); }
11108 		override int minHeight() { return scaleWithDpi(16); }
11109 	} else version(custom_widgets) {
11110 		override int maxHeight() { return defaultLineHeight; }
11111 		override int minHeight() { return defaultLineHeight; }
11112 	} else static assert(0);
11113 
11114 	override int marginLeft() { return 4; }
11115 
11116 	override int flexBasisWidth() { return 24 + cast(int) label.length * 7; }
11117 
11118 	/++
11119 		Just an alias because I keep typing checked out of web habit.
11120 
11121 		History:
11122 			Added May 31, 2021
11123 	+/
11124 	alias checked = isChecked;
11125 
11126 	private string label;
11127 	private dchar accelerator;
11128 
11129 	/++
11130 	+/
11131 	this(string label, Widget parent) {
11132 		this(ImageLabel(label), Appearance.checkbox, parent);
11133 	}
11134 
11135 	/// ditto
11136 	this(string label, Appearance appearance, Widget parent) {
11137 		this(ImageLabel(label), appearance, parent);
11138 	}
11139 
11140 	/++
11141 		Changes the look and may change the ideal size of the widget without changing its behavior. The precise look is platform-specific.
11142 
11143 		History:
11144 			Added June 29, 2021 (dub v10.2)
11145 	+/
11146 	enum Appearance {
11147 		checkbox, /// a normal checkbox
11148 		pushbutton, /// a button that is showed as pushed when checked and up when unchecked. Similar to the bold button in a toolbar in Wordpad.
11149 		//sliderswitch,
11150 	}
11151 	private Appearance appearance;
11152 
11153 	/// ditto
11154 	private this(ImageLabel label, Appearance appearance, Widget parent) {
11155 		super(parent);
11156 		version(win32_widgets) {
11157 			this.label = label.label;
11158 
11159 			uint extraStyle;
11160 			final switch(appearance) {
11161 				case Appearance.checkbox:
11162 				break;
11163 				case Appearance.pushbutton:
11164 					extraStyle |= BS_PUSHLIKE;
11165 				break;
11166 			}
11167 
11168 			createWin32Window(this, "button"w, label.label, BS_CHECKBOX | extraStyle);
11169 		} else version(custom_widgets) {
11170 			label.label.extractWindowsStyleLabel(this.label, this.accelerator);
11171 		} else static assert(0);
11172 	}
11173 
11174 	version(custom_widgets)
11175 	override void paint(WidgetPainter painter) {
11176 		auto cs = getComputedStyle();
11177 		if(isFocused()) {
11178 			painter.pen = Pen(Color.black, 1, Pen.Style.Dotted);
11179 			painter.fillColor = cs.windowBackgroundColor;
11180 			painter.drawRectangle(Point(0, 0), width, height);
11181 			painter.pen = Pen(Color.black, 1, Pen.Style.Solid);
11182 		} else {
11183 			painter.pen = Pen(cs.windowBackgroundColor, 1, Pen.Style.Solid);
11184 			painter.fillColor = cs.windowBackgroundColor;
11185 			painter.drawRectangle(Point(0, 0), width, height);
11186 		}
11187 
11188 
11189 		enum buttonSize = 16;
11190 
11191 		painter.outlineColor = Color.black;
11192 		painter.fillColor = Color.white;
11193 		painter.drawRectangle(scaleWithDpi(Point(2, 2)), scaleWithDpi(buttonSize - 2), scaleWithDpi(buttonSize - 2));
11194 
11195 		if(isChecked) {
11196 			painter.pen = Pen(Color.black, 2);
11197 			// I'm using height so the checkbox is square
11198 			enum padding = 5;
11199 			painter.drawLine(scaleWithDpi(Point(padding, padding)), scaleWithDpi(Point(buttonSize - (padding-2), buttonSize - (padding-2))));
11200 			painter.drawLine(scaleWithDpi(Point(buttonSize-(padding-2), padding)), scaleWithDpi(Point(padding, buttonSize - (padding-2))));
11201 
11202 			painter.pen = Pen(Color.black, 1);
11203 		}
11204 
11205 		if(label !is null) {
11206 			painter.outlineColor = cs.foregroundColor();
11207 			painter.fillColor = cs.foregroundColor();
11208 
11209 			// FIXME: should prolly just align the baseline or something
11210 			painter.drawText(scaleWithDpi(Point(buttonSize + 4, 2)), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
11211 		}
11212 	}
11213 
11214 	override void defaultEventHandler_triggered(Event ev) {
11215 		isChecked = !isChecked;
11216 
11217 		this.emit!(ChangeEvent!bool)(&isChecked);
11218 
11219 		redraw();
11220 	}
11221 
11222 	/// Emits a change event with the checked state
11223 	mixin Emits!(ChangeEvent!bool);
11224 }
11225 
11226 /// Adds empty space to a layout.
11227 class VerticalSpacer : Widget {
11228 	///
11229 	this(Widget parent) {
11230 		super(parent);
11231 	}
11232 }
11233 
11234 /// ditto
11235 class HorizontalSpacer : Widget {
11236 	///
11237 	this(Widget parent) {
11238 		super(parent);
11239 		this.tabStop = false;
11240 	}
11241 }
11242 
11243 
11244 /++
11245 	Creates a radio button with an associated label. These are usually put inside a [Fieldset].
11246 
11247 
11248 	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
11249 
11250 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
11251 
11252 	History:
11253 		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.
11254 +/
11255 class Radiobox : MouseActivatedWidget {
11256 
11257 	version(win32_widgets) {
11258 		override int maxHeight() { return scaleWithDpi(16); }
11259 		override int minHeight() { return scaleWithDpi(16); }
11260 	} else version(custom_widgets) {
11261 		override int maxHeight() { return defaultLineHeight; }
11262 		override int minHeight() { return defaultLineHeight; }
11263 	} else static assert(0);
11264 
11265 	override int marginLeft() { return 4; }
11266 
11267 	// FIXME: make a label getter
11268 	private string label;
11269 	private dchar accelerator;
11270 
11271 	version(win32_widgets)
11272 	this(string label, Widget parent) {
11273 		super(parent);
11274 		this.label = label;
11275 		createWin32Window(this, "button"w, label, BS_AUTORADIOBUTTON);
11276 	}
11277 	else version(custom_widgets)
11278 	this(string label, Widget parent) {
11279 		super(parent);
11280 		label.extractWindowsStyleLabel(this.label, this.accelerator);
11281 		height = 16;
11282 		width = height + 4 + cast(int) label.length * 16;
11283 	}
11284 	else static assert(false);
11285 
11286 	version(custom_widgets)
11287 	override void paint(WidgetPainter painter) {
11288 		auto cs = getComputedStyle();
11289 		if(isFocused) {
11290 			painter.fillColor = cs.windowBackgroundColor;
11291 			painter.pen = Pen(Color.black, 1, Pen.Style.Dotted);
11292 		} else {
11293 			painter.fillColor = cs.windowBackgroundColor;
11294 			painter.outlineColor = cs.windowBackgroundColor;
11295 		}
11296 		painter.drawRectangle(Point(0, 0), width, height);
11297 
11298 		painter.pen = Pen(Color.black, 1, Pen.Style.Solid);
11299 
11300 		enum buttonSize = 16;
11301 
11302 		painter.outlineColor = Color.black;
11303 		painter.fillColor = Color.white;
11304 		painter.drawEllipse(scaleWithDpi(Point(2, 2)), scaleWithDpi(Point(buttonSize - 2, buttonSize - 2)));
11305 		if(isChecked) {
11306 			painter.outlineColor = Color.black;
11307 			painter.fillColor = Color.black;
11308 			// I'm using height so the checkbox is square
11309 			painter.drawEllipse(scaleWithDpi(Point(5, 5)), scaleWithDpi(Point(buttonSize - 5, buttonSize - 5)));
11310 		}
11311 
11312 		painter.outlineColor = cs.foregroundColor();
11313 		painter.fillColor = cs.foregroundColor();
11314 
11315 		painter.drawText(scaleWithDpi(Point(buttonSize + 4, 0)), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
11316 	}
11317 
11318 
11319 	override void defaultEventHandler_triggered(Event ev) {
11320 		isChecked = true;
11321 
11322 		if(this.parent) {
11323 			foreach(child; this.parent.children) {
11324 				if(child is this) continue;
11325 				if(auto rb = cast(Radiobox) child) {
11326 					rb.isChecked = false;
11327 					rb.emit!(ChangeEvent!bool)(&rb.isChecked);
11328 					rb.redraw();
11329 				}
11330 			}
11331 		}
11332 
11333 		this.emit!(ChangeEvent!bool)(&this.isChecked);
11334 
11335 		redraw();
11336 	}
11337 
11338 	/// 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.
11339 	mixin Emits!(ChangeEvent!bool);
11340 }
11341 
11342 
11343 /++
11344 	Creates a push button with unbounded size. When it is clicked, it emits a `triggered` event.
11345 
11346 
11347 	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
11348 
11349 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
11350 
11351 	History:
11352 		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.
11353 +/
11354 class Button : MouseActivatedWidget {
11355 	override int heightStretchiness() { return 3; }
11356 	override int widthStretchiness() { return 3; }
11357 
11358 	/++
11359 		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.
11360 
11361 		History:
11362 			Added July 2, 2021
11363 	+/
11364 	public bool triggersOnMultiClick;
11365 
11366 	private string label_;
11367 	private TextAlignment alignment;
11368 	private dchar accelerator;
11369 
11370 	///
11371 	string label() { return label_; }
11372 	///
11373 	void label(string l) {
11374 		label_ = l;
11375 		version(win32_widgets) {
11376 			WCharzBuffer bfr = WCharzBuffer(l);
11377 			SetWindowTextW(hwnd, bfr.ptr);
11378 		} else version(custom_widgets) {
11379 			redraw();
11380 		}
11381 	}
11382 
11383 	override void defaultEventHandler_dblclick(DoubleClickEvent ev) {
11384 		super.defaultEventHandler_dblclick(ev);
11385 		if(triggersOnMultiClick) {
11386 			if(ev.button == MouseButton.left) {
11387 				auto event = new Event(EventType.triggered, this);
11388 				event.sendDirectly();
11389 			}
11390 		}
11391 	}
11392 
11393 	private Sprite sprite;
11394 	private int displayFlags;
11395 
11396 	/++
11397 		Creates a push button with the given label, which may be an image or some text.
11398 
11399 		Bugs:
11400 			If the image is bigger than the button, it may not be displayed in the right position on Linux.
11401 
11402 		History:
11403 			The [ImageLabel] overload was added on June 21, 2021 (dub v10.1).
11404 
11405 			The button with label and image will respect requests to show both on Windows as
11406 			of March 28, 2022 iff you provide a manifest file to opt into common controls v6.
11407 	+/
11408 	this(ImageLabel label, Widget parent) {
11409 		version(win32_widgets) {
11410 			// FIXME: use ideal button size instead
11411 			width = 50;
11412 			height = 30;
11413 			super(parent);
11414 
11415 			// BS_BITMAP is set when we want image only, so checking for exactly that combination
11416 			enum imgFlags = ImageLabel.DisplayFlags.displayImage | ImageLabel.DisplayFlags.displayText;
11417 			auto extraStyle = ((label.displayFlags & imgFlags) == ImageLabel.DisplayFlags.displayImage) ? BS_BITMAP : 0;
11418 
11419 			// the transparent thing can mess up borders in other cases, so only going to keep it for bitmap things where it might matter
11420 			createWin32Window(this, "button"w, label.label, BS_PUSHBUTTON | extraStyle, extraStyle == BS_BITMAP ? WS_EX_TRANSPARENT : 0 );
11421 
11422 			if(label.image) {
11423 				sprite = Sprite.fromMemoryImage(parentWindow.win, label.image, true);
11424 
11425 				SendMessageW(hwnd, BM_SETIMAGE, IMAGE_BITMAP, cast(LPARAM) sprite.nativeHandle);
11426 			}
11427 
11428 			this.label = label.label;
11429 		} else version(custom_widgets) {
11430 			width = 50;
11431 			height = 30;
11432 			super(parent);
11433 
11434 			label.label.extractWindowsStyleLabel(this.label_, this.accelerator);
11435 
11436 			if(label.image) {
11437 				this.sprite = Sprite.fromMemoryImage(parentWindow.win, label.image);
11438 				this.displayFlags = label.displayFlags;
11439 			}
11440 
11441 			this.alignment = label.alignment;
11442 		}
11443 	}
11444 
11445 	///
11446 	this(string label, Widget parent) {
11447 		this(ImageLabel(label), parent);
11448 	}
11449 
11450 	override int minHeight() { return defaultLineHeight + 4; }
11451 
11452 	static class Style : Widget.Style {
11453 		override WidgetBackground background() {
11454 			auto cs = widget.getComputedStyle(); // FIXME: this is potentially recursive
11455 
11456 			auto pressed = DynamicState.depressed | DynamicState.hover;
11457 			if((widget.dynamicState & pressed) == pressed) {
11458 				return WidgetBackground(cs.depressedButtonColor());
11459 			} else if(widget.dynamicState & DynamicState.hover) {
11460 				return WidgetBackground(cs.hoveringColor());
11461 			} else {
11462 				return WidgetBackground(cs.buttonColor());
11463 			}
11464 		}
11465 
11466 		override FrameStyle borderStyle() {
11467 			auto pressed = DynamicState.depressed | DynamicState.hover;
11468 			if((widget.dynamicState & pressed) == pressed) {
11469 				return FrameStyle.sunk;
11470 			} else {
11471 				return FrameStyle.risen;
11472 			}
11473 
11474 		}
11475 
11476 		override bool variesWithState(ulong dynamicStateFlags) {
11477 			return super.variesWithState(dynamicStateFlags) || (dynamicStateFlags & (DynamicState.depressed | DynamicState.hover));
11478 		}
11479 	}
11480 	mixin OverrideStyle!Style;
11481 
11482 	version(custom_widgets)
11483 	override void paint(WidgetPainter painter) {
11484 		painter.drawThemed(delegate Rectangle(const Rectangle bounds) {
11485 			if(sprite) {
11486 				sprite.drawAt(
11487 					painter,
11488 					bounds.upperLeft + Point((bounds.width - sprite.width) / 2, (bounds.height - sprite.height) / 2),
11489 					Point(0, 0)
11490 				);
11491 			} else {
11492 				painter.drawText(bounds.upperLeft, label, bounds.lowerRight, alignment | TextAlignment.VerticalCenter);
11493 			}
11494 			return bounds;
11495 		});
11496 	}
11497 
11498 	override int flexBasisWidth() {
11499 		version(win32_widgets) {
11500 			SIZE size;
11501 			SendMessage(hwnd, BCM_GETIDEALSIZE, 0, cast(LPARAM) &size);
11502 			if(size.cx == 0)
11503 				goto fallback;
11504 			return size.cx + scaleWithDpi(16);
11505 		}
11506 		fallback:
11507 			return scaleWithDpi(cast(int) label.length * 8 + 16);
11508 	}
11509 
11510 	override int flexBasisHeight() {
11511 		version(win32_widgets) {
11512 			SIZE size;
11513 			SendMessage(hwnd, BCM_GETIDEALSIZE, 0, cast(LPARAM) &size);
11514 			if(size.cy == 0)
11515 				goto fallback;
11516 			return size.cy + scaleWithDpi(6);
11517 		}
11518 		fallback:
11519 			return defaultLineHeight + 4;
11520 	}
11521 }
11522 
11523 /++
11524 	A button with a consistent size, suitable for user commands like OK and CANCEL.
11525 +/
11526 class CommandButton : Button {
11527 	this(string label, Widget parent) {
11528 		super(label, parent);
11529 	}
11530 
11531 	// FIXME: I think I can simply make this 0 stretchiness instead of max now that the flex basis is there
11532 
11533 	override int maxHeight() {
11534 		return defaultLineHeight + 4;
11535 	}
11536 
11537 	override int maxWidth() {
11538 		return defaultLineHeight * 4;
11539 	}
11540 
11541 	override int marginLeft() { return 12; }
11542 	override int marginRight() { return 12; }
11543 	override int marginTop() { return 12; }
11544 	override int marginBottom() { return 12; }
11545 }
11546 
11547 ///
11548 enum ArrowDirection {
11549 	left, ///
11550 	right, ///
11551 	up, ///
11552 	down ///
11553 }
11554 
11555 ///
11556 version(custom_widgets)
11557 class ArrowButton : Button {
11558 	///
11559 	this(ArrowDirection direction, Widget parent) {
11560 		super("", parent);
11561 		this.direction = direction;
11562 		triggersOnMultiClick = true;
11563 	}
11564 
11565 	private ArrowDirection direction;
11566 
11567 	override int minHeight() { return scaleWithDpi(16); }
11568 	override int maxHeight() { return scaleWithDpi(16); }
11569 	override int minWidth() { return scaleWithDpi(16); }
11570 	override int maxWidth() { return scaleWithDpi(16); }
11571 
11572 	override void paint(WidgetPainter painter) {
11573 		super.paint(painter);
11574 
11575 		auto cs = getComputedStyle();
11576 
11577 		painter.outlineColor = cs.foregroundColor;
11578 		painter.fillColor = cs.foregroundColor;
11579 
11580 		auto offset = Point((this.width - scaleWithDpi(16)) / 2, (this.height - scaleWithDpi(16)) / 2);
11581 
11582 		final switch(direction) {
11583 			case ArrowDirection.up:
11584 				painter.drawPolygon(
11585 					scaleWithDpi(Point(2, 10) + offset),
11586 					scaleWithDpi(Point(7, 5) + offset),
11587 					scaleWithDpi(Point(12, 10) + offset),
11588 					scaleWithDpi(Point(2, 10) + offset)
11589 				);
11590 			break;
11591 			case ArrowDirection.down:
11592 				painter.drawPolygon(
11593 					scaleWithDpi(Point(2, 6) + offset),
11594 					scaleWithDpi(Point(7, 11) + offset),
11595 					scaleWithDpi(Point(12, 6) + offset),
11596 					scaleWithDpi(Point(2, 6) + offset)
11597 				);
11598 			break;
11599 			case ArrowDirection.left:
11600 				painter.drawPolygon(
11601 					scaleWithDpi(Point(10, 2) + offset),
11602 					scaleWithDpi(Point(5, 7) + offset),
11603 					scaleWithDpi(Point(10, 12) + offset),
11604 					scaleWithDpi(Point(10, 2) + offset)
11605 				);
11606 			break;
11607 			case ArrowDirection.right:
11608 				painter.drawPolygon(
11609 					scaleWithDpi(Point(6, 2) + offset),
11610 					scaleWithDpi(Point(11, 7) + offset),
11611 					scaleWithDpi(Point(6, 12) + offset),
11612 					scaleWithDpi(Point(6, 2) + offset)
11613 				);
11614 			break;
11615 		}
11616 	}
11617 }
11618 
11619 private
11620 int[2] getChildPositionRelativeToParentOrigin(Widget c) nothrow {
11621 	int x, y;
11622 	Widget par = c;
11623 	while(par) {
11624 		x += par.x;
11625 		y += par.y;
11626 		par = par.parent;
11627 	}
11628 	return [x, y];
11629 }
11630 
11631 version(win32_widgets)
11632 private
11633 int[2] getChildPositionRelativeToParentHwnd(Widget c) nothrow {
11634 // MapWindowPoints?
11635 	int x, y;
11636 	Widget par = c;
11637 	while(par) {
11638 		x += par.x;
11639 		y += par.y;
11640 		par = par.parent;
11641 		if(par !is null && par.useNativeDrawing())
11642 			break;
11643 	}
11644 	return [x, y];
11645 }
11646 
11647 ///
11648 class ImageBox : Widget {
11649 	private MemoryImage image_;
11650 
11651 	override int widthStretchiness() { return 1; }
11652 	override int heightStretchiness() { return 1; }
11653 	override int widthShrinkiness() { return 1; }
11654 	override int heightShrinkiness() { return 1; }
11655 
11656 	override int flexBasisHeight() {
11657 		return image_.height;
11658 	}
11659 
11660 	override int flexBasisWidth() {
11661 		return image_.width;
11662 	}
11663 
11664 	///
11665 	public void setImage(MemoryImage image){
11666 		this.image_ = image;
11667 		if(this.parentWindow && this.parentWindow.win) {
11668 			if(sprite)
11669 				sprite.dispose();
11670 			sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_, true));
11671 		}
11672 		redraw();
11673 	}
11674 
11675 	/// How to fit the image in the box if they aren't an exact match in size?
11676 	enum HowToFit {
11677 		center, /// centers the image, cropping around all the edges as needed
11678 		crop, /// always draws the image in the upper left, cropping the lower right if needed
11679 		// stretch, /// not implemented
11680 	}
11681 
11682 	private Sprite sprite;
11683 	private HowToFit howToFit_;
11684 
11685 	private Color backgroundColor_;
11686 
11687 	///
11688 	this(MemoryImage image, HowToFit howToFit, Color backgroundColor, Widget parent) {
11689 		this.image_ = image;
11690 		this.tabStop = false;
11691 		this.howToFit_ = howToFit;
11692 		this.backgroundColor_ = backgroundColor;
11693 		super(parent);
11694 		updateSprite();
11695 	}
11696 
11697 	/// ditto
11698 	this(MemoryImage image, HowToFit howToFit, Widget parent) {
11699 		this(image, howToFit, Color.transparent, parent);
11700 	}
11701 
11702 	private void updateSprite() {
11703 		if(sprite is null && this.parentWindow && this.parentWindow.win) {
11704 			sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_, true));
11705 		}
11706 	}
11707 
11708 	override void paint(WidgetPainter painter) {
11709 		updateSprite();
11710 		if(backgroundColor_.a) {
11711 			painter.fillColor = backgroundColor_;
11712 			painter.drawRectangle(Point(0, 0), width, height);
11713 		}
11714 		if(howToFit_ == HowToFit.crop)
11715 			sprite.drawAt(painter, Point(0, 0));
11716 		else if(howToFit_ == HowToFit.center) {
11717 			sprite.drawAt(painter, Point((width - image_.width) / 2, (height - image_.height) / 2));
11718 		}
11719 	}
11720 }
11721 
11722 ///
11723 class TextLabel : Widget {
11724 	override int maxHeight() { return defaultLineHeight; }
11725 	override int minHeight() { return defaultLineHeight; }
11726 	override int minWidth() { return 32; }
11727 
11728 	override int flexBasisHeight() { return minHeight(); }
11729 	override int flexBasisWidth() { return defaultTextWidth(label); }
11730 
11731 	string label_;
11732 
11733 	/++
11734 		Indicates which other control this label is here for. Similar to HTML `for` attribute.
11735 
11736 		In practice this means a click on the label will focus the `labelFor`. In future versions
11737 		it will also set screen reader hints but that is not yet implemented.
11738 
11739 		History:
11740 			Added October 3, 2021 (dub v10.4)
11741 	+/
11742 	Widget labelFor;
11743 
11744 	///
11745 	@scriptable
11746 	string label() { return label_; }
11747 
11748 	///
11749 	@scriptable
11750 	void label(string l) {
11751 		label_ = l;
11752 		version(win32_widgets) {
11753 			WCharzBuffer bfr = WCharzBuffer(l);
11754 			SetWindowTextW(hwnd, bfr.ptr);
11755 		} else version(custom_widgets)
11756 			redraw();
11757 	}
11758 
11759 	///
11760 	this(string label, TextAlignment alignment, Widget parent) {
11761 		this.label_ = label;
11762 		this.alignment = alignment;
11763 		this.tabStop = false;
11764 		super(parent);
11765 
11766 		version(win32_widgets)
11767 		createWin32Window(this, "static"w, label, (alignment & TextAlignment.Center) ? SS_CENTER : 0, (alignment & TextAlignment.Right) ? WS_EX_RIGHT : WS_EX_LEFT);
11768 	}
11769 
11770 	override void defaultEventHandler_click(scope ClickEvent ce) {
11771 		if(this.labelFor !is null)
11772 			this.labelFor.focus();
11773 	}
11774 
11775 	/++
11776 		WARNING: this currently sets TextAlignment.Right as the default. That will change in a future version.
11777 		For future-proofing of your code, if you rely on TextAlignment.Right, you MUST specify that explicitly.
11778 	+/
11779 	this(string label, Widget parent) {
11780 		this(label, TextAlignment.Right, parent);
11781 	}
11782 
11783 
11784 	TextAlignment alignment;
11785 
11786 	version(custom_widgets)
11787 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
11788 		painter.outlineColor = getComputedStyle().foregroundColor;
11789 		painter.drawText(Point(0, 0), this.label, Point(width, height), alignment);
11790 		return bounds;
11791 	}
11792 
11793 }
11794 
11795 version(custom_widgets)
11796 	private struct etc {
11797 		mixin ExperimentalTextComponent;
11798 	}
11799 
11800 version(win32_widgets)
11801 	alias EditableTextWidgetParent = Widget; ///
11802 else version(custom_widgets) {
11803 	version(trash_text) {
11804 		alias EditableTextWidgetParent = ScrollableWidget; ///
11805 	} else {
11806 		alias EditableTextWidgetParent = Widget;
11807 		version=use_new_text_system;
11808 		import arsd.textlayouter;
11809 	}
11810 } else static assert(0);
11811 
11812 version(use_new_text_system)
11813 class TextDisplayHelper : Widget {
11814 	protected TextLayouter l;
11815 	protected ScrollMessageWidget smw;
11816 
11817 	private const(TextLayouter.State)*[] undoStack;
11818 	private const(TextLayouter.State)*[] redoStack;
11819 
11820 	bool readonly;
11821 	bool caretNavigation; // scroll lock can flip this
11822 	bool singleLine;
11823 	bool acceptsTabInput;
11824 
11825 	private Menu ctx;
11826 	override Menu contextMenu(int x, int y) {
11827 		if(ctx is null) {
11828 			ctx = new Menu("Actions", this);
11829 			ctx.addItem(new MenuItem(new Action("&Undo", GenericIcons.Undo, &undo)));
11830 			ctx.addItem(new MenuItem(new Action("&Redo", GenericIcons.Redo, &redo)));
11831 			ctx.addSeparator();
11832 			ctx.addItem(new MenuItem(new Action("Cu&t", GenericIcons.Cut, &cut)));
11833 			ctx.addItem(new MenuItem(new Action("&Copy", GenericIcons.Copy, &copy)));
11834 			ctx.addItem(new MenuItem(new Action("&Paste", GenericIcons.Paste, &paste)));
11835 			ctx.addItem(new MenuItem(new Action("&Delete", 0, &deleteContentOfSelection)));
11836 			ctx.addSeparator();
11837 			ctx.addItem(new MenuItem(new Action("Select &All", 0, &selectAll)));
11838 		}
11839 		return ctx;
11840 	}
11841 
11842 	override void defaultEventHandler_blur(Event ev) {
11843 		super.defaultEventHandler_blur(ev);
11844 		if(l.wasMutated()) {
11845 			auto evt = new ChangeEvent!string(this, &this.content);
11846 			evt.dispatch();
11847 			l.clearWasMutatedFlag();
11848 		}
11849 	}
11850 
11851 	private string content() {
11852 		return l.getTextString();
11853 	}
11854 
11855 	void undo() {
11856 		if(undoStack.length) {
11857 			auto state = undoStack[$-1];
11858 			undoStack = undoStack[0 .. $-1];
11859 			undoStack.assumeSafeAppend();
11860 			redoStack ~= l.saveState();
11861 			l.restoreState(state);
11862 			adjustScrollbarSizes();
11863 			scrollForCaret();
11864 			redraw();
11865 			stateCheckpoint = true;
11866 		}
11867 	}
11868 
11869 	void redo() {
11870 		if(redoStack.length) {
11871 			doStateCheckpoint();
11872 			auto state = redoStack[$-1];
11873 			redoStack = redoStack[0 .. $-1];
11874 			redoStack.assumeSafeAppend();
11875 			l.restoreState(state);
11876 			adjustScrollbarSizes();
11877 			scrollForCaret();
11878 			redraw();
11879 			stateCheckpoint = true;
11880 		}
11881 	}
11882 
11883 	void cut() {
11884 		with(l.selection()) {
11885 			if(!isEmpty()) {
11886 				setClipboardText(parentWindow.win, getContentString());
11887 				doStateCheckpoint();
11888 				replaceContent("");
11889 				adjustScrollbarSizes();
11890 				scrollForCaret();
11891 				this.redraw();
11892 			}
11893 		}
11894 
11895 	}
11896 
11897 	void copy() {
11898 		with(l.selection()) {
11899 			if(!isEmpty()) {
11900 				setClipboardText(parentWindow.win, getContentString());
11901 				this.redraw();
11902 			}
11903 		}
11904 	}
11905 
11906 	void paste() {
11907 		getClipboardText(parentWindow.win, (txt) {
11908 			doStateCheckpoint();
11909 			l.selection.replaceContent(txt);
11910 			adjustScrollbarSizes();
11911 			scrollForCaret();
11912 			this.redraw();
11913 		});
11914 	}
11915 
11916 	void deleteContentOfSelection() {
11917 		doStateCheckpoint();
11918 		l.selection.replaceContent("");
11919 		l.selection.setUserXCoordinate();
11920 		adjustScrollbarSizes();
11921 		scrollForCaret();
11922 		redraw();
11923 	}
11924 
11925 	void selectAll() {
11926 		with(l.selection) {
11927 			moveToStartOfDocument();
11928 			setAnchor();
11929 			moveToEndOfDocument();
11930 			setFocus();
11931 		}
11932 		redraw();
11933 	}
11934 
11935 	protected bool stateCheckpoint = true;
11936 
11937 	protected void doStateCheckpoint() {
11938 		if(stateCheckpoint) {
11939 			undoStack ~= l.saveState();
11940 			stateCheckpoint = false;
11941 		}
11942 	}
11943 
11944 	protected void adjustScrollbarSizes() {
11945 		// FIXME: will want a content area helper function instead of doing all these subtractions myself
11946 		auto borderWidth = 2;
11947 		this.smw.setTotalArea(l.width, l.height);
11948 		this.smw.setViewableArea(
11949 			this.width - this.paddingLeft - this.paddingRight - borderWidth * 2,
11950 			this.height - this.paddingTop - this.paddingBottom - borderWidth * 2);
11951 	}
11952 
11953 	protected void scrollForCaret() {
11954 		// writeln(l.width, "x", l.height); writeln(this.width - this.paddingLeft - this.paddingRight, " ", this.height - this.paddingTop - this.paddingBottom);
11955 		smw.scrollIntoView(l.selection.focusBoundingBox());
11956 	}
11957 
11958 	// FIXME: this should be a theme changed event listener instead
11959 	private BaseVisualTheme currentTheme;
11960 	override void recomputeChildLayout() {
11961 		if(currentTheme is null)
11962 			currentTheme = WidgetPainter.visualTheme;
11963 		if(WidgetPainter.visualTheme !is currentTheme) {
11964 			currentTheme = WidgetPainter.visualTheme;
11965 			auto ds = this.l.defaultStyle;
11966 			if(auto ms = cast(MyTextStyle) ds) {
11967 				auto cs = getComputedStyle();
11968 				auto font = cs.font();
11969 				if(font !is null)
11970 					ms.font_ = font;
11971 				else {
11972 					auto osc = new OperatingSystemFont();
11973 					osc.loadDefault;
11974 					ms.font_ = osc;
11975 				}
11976 			}
11977 		}
11978 		super.recomputeChildLayout();
11979 	}
11980 
11981 	private Point adjustForSingleLine(Point p) {
11982 		if(singleLine)
11983 			return Point(p.x, this.height / 2);
11984 		else
11985 			return p;
11986 	}
11987 
11988 	private bool wordWrapEnabled_;
11989 
11990 	this(TextLayouter l, ScrollMessageWidget parent) {
11991 		this.smw = parent;
11992 
11993 		smw.addDefaultWheelListeners(16, 16, 8);
11994 		smw.movementPerButtonClick(16, 16);
11995 
11996 		this.defaultPadding = Rectangle(2, 2, 2, 2);
11997 
11998 		this.l = l;
11999 		super(parent);
12000 
12001 		smw.addEventListener((scope ScrollEvent se) {
12002 			this.redraw();
12003 		});
12004 
12005 		bool mouseDown;
12006 
12007 		this.addEventListener((scope ResizeEvent re) {
12008 			// FIXME: I should add a method to give this client area width thing
12009 			if(wordWrapEnabled_)
12010 				this.l.wordWrapWidth = this.width - this.paddingLeft - this.paddingRight;
12011 
12012 			adjustScrollbarSizes();
12013 			scrollForCaret();
12014 
12015 			this.redraw();
12016 		});
12017 
12018 		this.addEventListener((scope KeyDownEvent kde) {
12019 			switch(kde.key) {
12020 				case Key.Up, Key.Down, Key.Left, Key.Right:
12021 				case Key.Home, Key.End:
12022 					stateCheckpoint = true;
12023 					bool setPosition = false;
12024 					switch(kde.key) {
12025 						case Key.Up: l.selection.moveUp(); break;
12026 						case Key.Down: l.selection.moveDown(); break;
12027 						case Key.Left: l.selection.moveLeft(); setPosition = true; break;
12028 						case Key.Right: l.selection.moveRight(); setPosition = true; break;
12029 						case Key.Home: l.selection.moveToStartOfLine(); setPosition = true; break;
12030 						case Key.End: l.selection.moveToEndOfLine(); setPosition = true; break;
12031 						default: assert(0);
12032 					}
12033 
12034 					if(kde.shiftKey)
12035 						l.selection.setFocus();
12036 					else
12037 						l.selection.setAnchor();
12038 					if(setPosition)
12039 						l.selection.setUserXCoordinate();
12040 					scrollForCaret();
12041 					redraw();
12042 				break;
12043 				case Key.PageUp, Key.PageDown:
12044 					// FIXME
12045 					scrollForCaret();
12046 				break;
12047 				case Key.Delete:
12048 					if(l.selection.isEmpty()) {
12049 						l.selection.setAnchor();
12050 						l.selection.moveRight();
12051 						l.selection.setFocus();
12052 					}
12053 					deleteContentOfSelection();
12054 					adjustScrollbarSizes();
12055 					scrollForCaret();
12056 				break;
12057 				case Key.Insert:
12058 				break;
12059 				case Key.A:
12060 					if(kde.ctrlKey)
12061 						selectAll();
12062 				break;
12063 				case Key.F:
12064 					// find
12065 				break;
12066 				case Key.Z:
12067 					if(kde.ctrlKey)
12068 						undo();
12069 				break;
12070 				case Key.R:
12071 					if(kde.ctrlKey)
12072 						redo();
12073 				break;
12074 				case Key.X:
12075 					if(kde.ctrlKey)
12076 						cut();
12077 				break;
12078 				case Key.C:
12079 					if(kde.ctrlKey)
12080 						copy();
12081 				break;
12082 				case Key.V:
12083 					if(kde.ctrlKey)
12084 						paste();
12085 				break;
12086 				case Key.F1:
12087 					with(l.selection()) {
12088 						moveToStartOfLine();
12089 						setAnchor();
12090 						moveToEndOfLine();
12091 						moveToIncludeAdjacentEndOfLineMarker();
12092 						setFocus();
12093 						replaceContent("");
12094 					}
12095 
12096 					redraw();
12097 				break;
12098 				/*
12099 				case Key.F2:
12100 					l.selection().changeStyle((old) => l.registerStyle(new MyTextStyle(
12101 						//(cast(MyTextStyle) old).font,
12102 						font2,
12103 						Color.red)));
12104 					redraw();
12105 				break;
12106 				*/
12107 				case Key.Tab:
12108 					// we process the char event, so don't want to change focus on it
12109 					if(acceptsTabInput)
12110 						kde.preventDefault();
12111 				break;
12112 				default:
12113 			}
12114 		});
12115 
12116 		Point downAt;
12117 
12118 		static if(UsingSimpledisplayX11)
12119 		this.addEventListener((scope ClickEvent ce) {
12120 			if(ce.button == MouseButton.middle) {
12121 				parentWindow.win.getPrimarySelection((txt) {
12122 					l.selection.replaceContent(txt);
12123 					redraw();
12124 				});
12125 			}
12126 		});
12127 
12128 		this.addEventListener((scope MouseDownEvent ce) {
12129 			if(ce.button == MouseButton.left) {
12130 				downAt = Point(ce.clientX - this.paddingLeft, ce.clientY - this.paddingTop);
12131 				l.selection.moveTo(adjustForSingleLine(smw.position + downAt));
12132 				l.selection.setAnchor();
12133 				mouseDown = true;
12134 				parentWindow.captureMouse(this);
12135 				this.redraw();
12136 			} else if(ce.button == MouseButton.right) {
12137 				this.showContextMenu(ce.clientX, ce.clientY);
12138 			}
12139 			//writeln(ce.clientX, ", ", ce.clientY, " = ", l.offsetOfClick(Point(ce.clientX, ce.clientY)));
12140 		});
12141 
12142 		Timer autoscrollTimer;
12143 		int autoscrollDirection;
12144 		int autoscrollAmount;
12145 
12146 		void autoscroll() {
12147 			switch(autoscrollDirection) {
12148 				case 0: smw.scrollUp(autoscrollAmount); break;
12149 				case 1: smw.scrollDown(autoscrollAmount); break;
12150 				case 2: smw.scrollLeft(autoscrollAmount); break;
12151 				case 3: smw.scrollRight(autoscrollAmount); break;
12152 				default: assert(0);
12153 			}
12154 
12155 			this.redraw();
12156 		}
12157 
12158 		void setAutoscrollTimer(int direction, int amount) {
12159 			if(autoscrollTimer is null) {
12160 				autoscrollTimer = new Timer(1000 / 60, &autoscroll);
12161 			}
12162 
12163 			autoscrollDirection = direction;
12164 			autoscrollAmount = amount;
12165 		}
12166 
12167 		void stopAutoscrollTimer() {
12168 			if(autoscrollTimer !is null) {
12169 				autoscrollTimer.dispose();
12170 				autoscrollTimer = null;
12171 			}
12172 			autoscrollAmount = 0;
12173 			autoscrollDirection = 0;
12174 		}
12175 
12176 		this.addEventListener((scope MouseMoveEvent ce) {
12177 			if(mouseDown) {
12178 				auto movedTo = Point(ce.clientX - this.paddingLeft, ce.clientY - this.paddingTop);
12179 
12180 				// FIXME: when scrolling i actually do want a timer.
12181 				// i also want a zone near the sides of the window where i can auto scroll
12182 
12183 				auto scrollMultiplier = scaleWithDpi(16);
12184 				auto scrollDivisor = scaleWithDpi(16); // if you go more than 64px up it will scroll faster
12185 
12186 				if(!singleLine && movedTo.y < 4) {
12187 					setAutoscrollTimer(0, scrollMultiplier * -(movedTo.y-4) / scrollDivisor);
12188 				} else
12189 				if(!singleLine && (movedTo.y + 6) > this.height) {
12190 					setAutoscrollTimer(1, scrollMultiplier * (movedTo.y + 6 - this.height) / scrollDivisor);
12191 				} else
12192 				if(movedTo.x < 4) {
12193 					setAutoscrollTimer(2, scrollMultiplier * -(movedTo.x-4) / scrollDivisor);
12194 				} else
12195 				if((movedTo.x + 6) > this.width) {
12196 					setAutoscrollTimer(3, scrollMultiplier * (movedTo.x + 6 - this.width) / scrollDivisor);
12197 				} else
12198 					stopAutoscrollTimer();
12199 
12200 				l.selection.moveTo(adjustForSingleLine(smw.position + movedTo));
12201 				l.selection.setFocus();
12202 				this.redraw();
12203 			}
12204 		});
12205 
12206 		this.addEventListener((scope MouseUpEvent ce) {
12207 			// FIXME: assert primary selection
12208 			if(mouseDown && ce.button == MouseButton.left) {
12209 				stateCheckpoint = true;
12210 				//l.selection.moveTo(adjustForSingleLine(smw.position + Point(ce.clientX - this.paddingLeft, ce.clientY - this.paddingTop)));
12211 				//l.selection.setFocus();
12212 				mouseDown = false;
12213 				parentWindow.releaseMouseCapture();
12214 				stopAutoscrollTimer();
12215 				this.redraw();
12216 			}
12217 			//writeln(ce.clientX, ", ", ce.clientY, " = ", l.offsetOfClick(Point(ce.clientX, ce.clientY)));
12218 		});
12219 
12220 		this.addEventListener((scope CharEvent ce) {
12221 			if(readonly)
12222 				return;
12223 			if(ce.character < 32 && ce.character != '\t' && ce.character != '\n' && ce.character != '\b')
12224 				return; // skip the ctrl+x characters we don't care about as plain text
12225 
12226 			if(singleLine && ce.character == '\n')
12227 				return;
12228 			if(!acceptsTabInput && ce.character == '\t')
12229 				return;
12230 
12231 			doStateCheckpoint();
12232 
12233 			char[4] buffer;
12234 			import std.utf; // FIXME: i should remove this. compile time not significant but the logs get spammed with phobos' import web
12235 			auto stride = encode(buffer, ce.character);
12236 			l.selection.replaceContent(buffer[0 .. stride]);
12237 			l.selection.setUserXCoordinate();
12238 			adjustScrollbarSizes();
12239 			scrollForCaret();
12240 			redraw();
12241 		});
12242 	}
12243 
12244 	static class Style : Widget.Style {
12245 		override WidgetBackground background() {
12246 			return WidgetBackground(WidgetPainter.visualTheme.widgetBackgroundColor);
12247 		}
12248 
12249 		override Color foregroundColor() {
12250 			return WidgetPainter.visualTheme.foregroundColor;
12251 		}
12252 
12253 		override FrameStyle borderStyle() {
12254 			return FrameStyle.sunk;
12255 		}
12256 
12257 		override MouseCursor cursor() {
12258 			return GenericCursor.Text;
12259 		}
12260 	}
12261 	mixin OverrideStyle!Style;
12262 
12263 	override int minHeight() { return borderBoxForContentBox(Rectangle(Point(0, 0), Size(0, Window.lineHeight))).height; }
12264 	override int maxHeight() {
12265 		if(singleLine)
12266 			return minHeight;
12267 		else
12268 			return super.maxHeight();
12269 	}
12270 
12271 	void drawTextSegment(WidgetPainter painter, Point upperLeft, scope const(char)[] text) {
12272 		painter.drawText(upperLeft, text);
12273 	}
12274 
12275 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
12276 		//painter.setFont(font);
12277 
12278 		auto cs = getComputedStyle();
12279 		auto defaultColor = cs.foregroundColor;
12280 
12281 		auto old = painter.setClipRectangle(bounds);
12282 		scope(exit) painter.setClipRectangle(old);
12283 
12284 		l.getDrawableText(delegate bool(txt, style, info, carets...) {
12285 			//writeln("Segment: ", txt);
12286 			assert(style !is null);
12287 
12288 			auto myStyle = cast(MyTextStyle) style;
12289 			assert(myStyle !is null);
12290 
12291 			painter.setFont(myStyle.font);
12292 			// defaultColor = myStyle.color; // FIXME: so wrong
12293 
12294 			if(info.selections && info.boundingBox.width > 0) {
12295 				auto color = this.isFocused ? cs.selectionBackgroundColor : Color(128, 128, 128); // FIXME don't hardcode
12296 				painter.fillColor = color;
12297 				painter.outlineColor = color;
12298 				painter.drawRectangle(Rectangle(info.boundingBox.upperLeft - smw.position() + bounds.upperLeft, info.boundingBox.size));
12299 				painter.outlineColor = cs.selectionForegroundColor;
12300 				//painter.fillColor = Color.white;
12301 			} else {
12302 				painter.outlineColor = defaultColor;
12303 			}
12304 
12305 			if(this.isFocused)
12306 			foreach(idx, caret; carets) {
12307 				if(idx == 0)
12308 					painter.notifyCursorPosition(caret.boundingBox.left - smw.position.x + bounds.left, caret.boundingBox.top - smw.position.y + bounds.top, caret.boundingBox.width, caret.boundingBox.height);
12309 				painter.drawLine(
12310 					caret.boundingBox.upperLeft + bounds.upperLeft - smw.position(),
12311 					bounds.upperLeft + Point(caret.boundingBox.left, caret.boundingBox.bottom) - smw.position()
12312 				);
12313 			}
12314 
12315 			if(txt.stripInternal.length)
12316 				drawTextSegment(painter, info.boundingBox.upperLeft - smw.position() + bounds.upperLeft, txt.stripRightInternal);
12317 
12318 			if(info.boundingBox.upperLeft.y - smw.position().y > this.height)
12319 				return false;
12320 			else {
12321 				return true;
12322 			}
12323 		}, Rectangle(smw.position(), bounds.size));
12324 
12325 		/+
12326 		int place = 0;
12327 		int y = 75;
12328 		foreach(width; widths) {
12329 			painter.fillColor = Color.red;
12330 			painter.drawRectangle(Point(place, y), Size(width, 75));
12331 			//y += 15;
12332 			place += width;
12333 		}
12334 		+/
12335 
12336 		return bounds;
12337 	}
12338 
12339 	static class MyTextStyle : TextStyle {
12340 		OperatingSystemFont font_;
12341 		this(OperatingSystemFont font, bool passwordMode = false) {
12342 			this.font_ = font;
12343 		}
12344 
12345 		override OperatingSystemFont font() {
12346 			return font_;
12347 		}
12348 	}
12349 }
12350 
12351 /+
12352 version(use_new_text_system)
12353 class TextWidget : Widget {
12354 	TextLayouter l;
12355 	ScrollMessageWidget smw;
12356 	TextDisplayHelper helper;
12357 	this(TextLayouter l, Widget parent) {
12358 		this.l = l;
12359 		super(parent);
12360 
12361 		smw = new ScrollMessageWidget(this);
12362 		//smw.horizontalScrollBar.hide;
12363 		//smw.verticalScrollBar.hide;
12364 		smw.addDefaultWheelListeners(16, 16, 8);
12365 		smw.movementPerButtonClick(16, 16);
12366 		helper = new TextDisplayHelper(l, smw);
12367 
12368 		// no need to do this here since there's gonna be a resize
12369 		// event immediately before any drawing
12370 		// smw.setTotalArea(l.width, l.height);
12371 		smw.setViewableArea(
12372 			this.width - this.paddingLeft - this.paddingRight,
12373 			this.height - this.paddingTop - this.paddingBottom);
12374 
12375 		/+
12376 		writeln(l.width, "x", l.height);
12377 		+/
12378 	}
12379 }
12380 +/
12381 
12382 
12383 
12384 
12385 /+
12386 	This awful thing has to be rewritten. And it needs to takecare of parentWindow.inputProxy.setIMEPopupLocation too
12387 +/
12388 
12389 /// Contains the implementation of text editing
12390 abstract class EditableTextWidget : EditableTextWidgetParent {
12391 	this(Widget parent) {
12392 		super(parent);
12393 
12394 		version(custom_widgets)
12395 			setupCustomTextEditing();
12396 	}
12397 
12398 	private bool wordWrapEnabled_;
12399 	void wordWrapEnabled(bool enabled) {
12400 		version(win32_widgets) {
12401 			SendMessageW(hwnd, EM_FMTLINES, enabled ? 1 : 0, 0);
12402 		} else version(custom_widgets) {
12403 			wordWrapEnabled_ = enabled;
12404 			version(use_new_text_system)
12405 			textLayout.wordWrapWidth = enabled ? this.width : 0; // FIXME
12406 		} else static assert(false);
12407 	}
12408 
12409 	override int minWidth() { return scaleWithDpi(16); }
12410 	override int widthStretchiness() { return 7; }
12411 
12412 	version(use_new_text_system)
12413 	override int maxHeight() { return tdh.maxHeight; }
12414 
12415 	version(use_new_text_system)
12416 	override void focus() { if(tdh) tdh.focus(); else super.focus(); }
12417 
12418 	void selectAll() {
12419 		version(win32_widgets)
12420 			SendMessage(hwnd, EM_SETSEL, 0, -1);
12421 		else version(custom_widgets) {
12422 			version(use_new_text_system)
12423 				tdh.selectAll();
12424 			else
12425 				textLayout.selectAll();
12426 			redraw();
12427 		}
12428 	}
12429 
12430 	version(use_new_text_system)
12431 		TextDisplayHelper tdh;
12432 
12433 	@property string content() {
12434 		version(win32_widgets) {
12435 			wchar[4096] bufferstack;
12436 			wchar[] buffer;
12437 			auto len = GetWindowTextLength(hwnd);
12438 			if(len < bufferstack.length)
12439 				buffer = bufferstack[0 .. len + 1];
12440 			else
12441 				buffer = new wchar[](len + 1);
12442 
12443 			auto l = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
12444 			if(l >= 0)
12445 				return makeUtf8StringFromWindowsString(buffer[0 .. l]);
12446 			else
12447 				return null;
12448 		} else version(custom_widgets) {
12449 			version(use_new_text_system) {
12450 				return textLayout.getTextString();
12451 			} else
12452 				return textLayout.getPlainText();
12453 		} else static assert(false);
12454 	}
12455 	@property void content(string s) {
12456 		version(win32_widgets) {
12457 			WCharzBuffer bfr = WCharzBuffer(s, WindowsStringConversionFlags.convertNewLines);
12458 			SetWindowTextW(hwnd, bfr.ptr);
12459 		} else version(custom_widgets) {
12460 			version(use_new_text_system) {
12461 				selectAll();
12462 				textLayout.selection.replaceContent(s);
12463 
12464 				tdh.adjustScrollbarSizes();
12465 				// these don't seem to help
12466 				// tdh.smw.setPosition(0, 0);
12467 				// tdh.scrollForCaret();
12468 
12469 				redraw();
12470 			} else {
12471 				textLayout.clear();
12472 				textLayout.addText(s);
12473 
12474 				{
12475 				// FIXME: it should be able to get this info easier
12476 				auto painter = draw();
12477 				textLayout.redoLayout(painter);
12478 				}
12479 				auto cbb = textLayout.contentBoundingBox();
12480 				setContentSize(cbb.width, cbb.height);
12481 				/*
12482 				textLayout.addText(ForegroundColor.red, s);
12483 				textLayout.addText(ForegroundColor.blue, TextFormat.underline, "http://dpldocs.info/");
12484 				textLayout.addText(" is the best!");
12485 				*/
12486 				redraw();
12487 			}
12488 		}
12489 		else static assert(false);
12490 	}
12491 
12492 	void addText(string txt) {
12493 		version(custom_widgets) {
12494 			version(use_new_text_system) {
12495 				textLayout.appendText(txt);
12496 				tdh.adjustScrollbarSizes();
12497 				redraw();
12498 			} else {
12499 				textLayout.addText(txt);
12500 
12501 				{
12502 				// FIXME: it should be able to get this info easier
12503 				auto painter = draw();
12504 				textLayout.redoLayout(painter);
12505 				}
12506 				auto cbb = textLayout.contentBoundingBox();
12507 				setContentSize(cbb.width, cbb.height);
12508 			}
12509 		} else version(win32_widgets) {
12510 			// get the current selection
12511 			DWORD StartPos, EndPos;
12512 			SendMessageW( hwnd, EM_GETSEL, cast(WPARAM)(&StartPos), cast(LPARAM)(&EndPos) );
12513 
12514 			// move the caret to the end of the text
12515 			int outLength = GetWindowTextLengthW(hwnd);
12516 			SendMessageW( hwnd, EM_SETSEL, outLength, outLength );
12517 
12518 			// insert the text at the new caret position
12519 			WCharzBuffer bfr = WCharzBuffer(txt, WindowsStringConversionFlags.convertNewLines);
12520 			SendMessageW( hwnd, EM_REPLACESEL, TRUE, cast(LPARAM) bfr.ptr );
12521 
12522 			// restore the previous selection
12523 			SendMessageW( hwnd, EM_SETSEL, StartPos, EndPos );
12524 		} else static assert(0);
12525 	}
12526 
12527 	version(custom_widgets)
12528 	version(trash_text)
12529 	override void paintFrameAndBackground(WidgetPainter painter) {
12530 		this.draw3dFrame(painter, FrameStyle.sunk, Color.white);
12531 	}
12532 
12533 	version(use_new_text_system)
12534 	TextDisplayHelper textDisplayHelperFactory(TextLayouter textLayout, ScrollMessageWidget smw) {
12535 		return new TextDisplayHelper(textLayout, smw);
12536 	}
12537 
12538 	version(use_new_text_system)
12539 	TextStyle defaultTextStyle() {
12540 		auto cs = getComputedStyle();
12541 		auto font = cs.font;
12542 		if(font is null) {
12543 			font = new OperatingSystemFont;
12544 			font.loadDefault();
12545 		}
12546 		return new TextDisplayHelper.MyTextStyle(font);
12547 	}
12548 
12549 	version(win32_widgets) { /* will do it with Windows calls in the classes */ }
12550 	else version(custom_widgets) {
12551 		// FIXME
12552 		version(use_new_text_system) {
12553 			TextLayouter textLayout;
12554 
12555 			void setupCustomTextEditing() {
12556 				textLayout = new TextLayouter(defaultTextStyle());
12557 				auto smw = new ScrollMessageWidget(this);
12558 				if(!showingHorizontalScroll)
12559 					smw.horizontalScrollBar.hide();
12560 				if(!showingVerticalScroll)
12561 					smw.verticalScrollBar.hide();
12562 				this.tabStop = false;
12563 				smw.tabStop = false;
12564 				tdh = textDisplayHelperFactory(textLayout, smw);
12565 			}
12566 
12567 		} else {
12568 
12569 			static if(SimpledisplayTimerAvailable)
12570 				Timer caretTimer;
12571 			etc.TextLayout textLayout;
12572 
12573 			void setupCustomTextEditing() {
12574 				textLayout = new etc.TextLayout(Rectangle(4, 2, width - 8, height - 4));
12575 				textLayout.selectionXorColor = getComputedStyle().activeListXorColor;
12576 			}
12577 
12578 			override void paint(WidgetPainter painter) {
12579 				if(parentWindow.win.closed) return;
12580 
12581 				textLayout.boundingBox = Rectangle(4, 2, width - 8, height - 4);
12582 
12583 				/*
12584 				painter.outlineColor = Color.white;
12585 				painter.fillColor = Color.white;
12586 				painter.drawRectangle(Point(4, 4), contentWidth, contentHeight);
12587 				*/
12588 
12589 				painter.outlineColor = Color.black;
12590 				// painter.drawText(Point(4, 4), content, Point(width - 4, height - 4));
12591 
12592 				textLayout.caretShowingOnScreen = false;
12593 
12594 				textLayout.drawInto(painter, !parentWindow.win.closed && isFocused());
12595 			}
12596 		}
12597 
12598 		static class Style : Widget.Style {
12599 			override FrameStyle borderStyle() {
12600 				return FrameStyle.sunk;
12601 			}
12602 			override MouseCursor cursor() {
12603 				return GenericCursor.Text;
12604 			}
12605 		}
12606 		mixin OverrideStyle!Style;
12607 	}
12608 	else static assert(false);
12609 
12610 	version(trash_text)
12611 	version(custom_widgets)
12612 	override void defaultEventHandler_mousedown(MouseDownEvent ev) {
12613 		super.defaultEventHandler_mousedown(ev);
12614 		if(parentWindow.win.closed) return;
12615 		if(ev.button == MouseButton.left) {
12616 			if(textLayout.selectNone())
12617 				redraw();
12618 			textLayout.moveCaretToPixelCoordinates(ev.clientX, ev.clientY);
12619 			this.focus();
12620 			//this.parentWindow.win.grabInput();
12621 		} else if(ev.button == MouseButton.middle) {
12622 			static if(UsingSimpledisplayX11) {
12623 				getPrimarySelection(parentWindow.win, (in char[] txt) {
12624 					textLayout.insert(txt);
12625 					redraw();
12626 
12627 					auto cbb = textLayout.contentBoundingBox();
12628 					setContentSize(cbb.width, cbb.height);
12629 				});
12630 			}
12631 		}
12632 	}
12633 
12634 	version(trash_text)
12635 	version(custom_widgets)
12636 	override void defaultEventHandler_mouseup(MouseUpEvent ev) {
12637 		//this.parentWindow.win.releaseInputGrab();
12638 		super.defaultEventHandler_mouseup(ev);
12639 	}
12640 
12641 	version(trash_text)
12642 	version(custom_widgets)
12643 	override void defaultEventHandler_mousemove(MouseMoveEvent ev) {
12644 		super.defaultEventHandler_mousemove(ev);
12645 		if(ev.state & ModifierState.leftButtonDown) {
12646 			textLayout.selectToPixelCoordinates(ev.clientX, ev.clientY);
12647 			redraw();
12648 		}
12649 	}
12650 
12651 	version(trash_text)
12652 	version(custom_widgets)
12653 	override void defaultEventHandler_focus(Event ev) {
12654 		super.defaultEventHandler_focus(ev);
12655 		if(parentWindow.win.closed) return;
12656 		auto painter = this.draw();
12657 		textLayout.drawCaret(painter);
12658 
12659 		static if(SimpledisplayTimerAvailable)
12660 		if(caretTimer) {
12661 			caretTimer.destroy();
12662 			caretTimer = null;
12663 		}
12664 
12665 		bool blinkingCaret = true;
12666 		static if(UsingSimpledisplayX11)
12667 			if(!Image.impl.xshmAvailable)
12668 				blinkingCaret = false; // if on a remote connection, don't waste bandwidth on an expendable blink
12669 
12670 		if(blinkingCaret)
12671 		static if(SimpledisplayTimerAvailable)
12672 		caretTimer = new Timer(500, {
12673 			if(parentWindow.win.closed) {
12674 				caretTimer.destroy();
12675 				return;
12676 			}
12677 			if(isFocused()) {
12678 				auto painter = this.draw();
12679 				textLayout.drawCaret(painter);
12680 			} else if(textLayout.caretShowingOnScreen) {
12681 				auto painter = this.draw();
12682 				textLayout.eraseCaret(painter);
12683 			}
12684 		});
12685 	}
12686 
12687 	version(trash_text) {
12688 		private string lastContentBlur;
12689 
12690 		override void defaultEventHandler_blur(Event ev) {
12691 			super.defaultEventHandler_blur(ev);
12692 			if(parentWindow.win.closed) return;
12693 			version(custom_widgets) {
12694 				auto painter = this.draw();
12695 				textLayout.eraseCaret(painter);
12696 				static if(SimpledisplayTimerAvailable)
12697 				if(caretTimer) {
12698 					caretTimer.destroy();
12699 					caretTimer = null;
12700 				}
12701 			}
12702 
12703 			if(this.content != lastContentBlur) {
12704 				auto evt = new ChangeEvent!string(this, &this.content);
12705 				evt.dispatch();
12706 				lastContentBlur = this.content;
12707 			}
12708 		}
12709 	}
12710 
12711 	version(win32_widgets) {
12712 		private string lastContentBlur;
12713 
12714 		override void defaultEventHandler_blur(Event ev) {
12715 			super.defaultEventHandler_blur(ev);
12716 
12717 			if(this.content != lastContentBlur) {
12718 				auto evt = new ChangeEvent!string(this, &this.content);
12719 				evt.dispatch();
12720 				lastContentBlur = this.content;
12721 			}
12722 		}
12723 	}
12724 
12725 
12726 	version(trash_text)
12727 	version(custom_widgets)
12728 	override void defaultEventHandler_char(CharEvent ev) {
12729 		super.defaultEventHandler_char(ev);
12730 		textLayout.insert(ev.character);
12731 		redraw();
12732 
12733 		// FIXME: too inefficient
12734 		auto cbb = textLayout.contentBoundingBox();
12735 		setContentSize(cbb.width, cbb.height);
12736 	}
12737 	version(trash_text)
12738 	version(custom_widgets)
12739 	override void defaultEventHandler_keydown(KeyDownEvent ev) {
12740 		//super.defaultEventHandler_keydown(ev);
12741 		switch(ev.key) {
12742 			case Key.Delete:
12743 				textLayout.delete_();
12744 				redraw();
12745 			break;
12746 			case Key.Left:
12747 				textLayout.moveLeft();
12748 				redraw();
12749 			break;
12750 			case Key.Right:
12751 				textLayout.moveRight();
12752 				redraw();
12753 			break;
12754 			case Key.Up:
12755 				textLayout.moveUp();
12756 				redraw();
12757 			break;
12758 			case Key.Down:
12759 				textLayout.moveDown();
12760 				redraw();
12761 			break;
12762 			case Key.Home:
12763 				textLayout.moveHome();
12764 				redraw();
12765 			break;
12766 			case Key.End:
12767 				textLayout.moveEnd();
12768 				redraw();
12769 			break;
12770 			case Key.PageUp:
12771 				foreach(i; 0 .. 32)
12772 				textLayout.moveUp();
12773 				redraw();
12774 			break;
12775 			case Key.PageDown:
12776 				foreach(i; 0 .. 32)
12777 				textLayout.moveDown();
12778 				redraw();
12779 			break;
12780 
12781 			default:
12782 				 {} // intentionally blank, let "char" handle it
12783 		}
12784 		/*
12785 		if(ev.key == Key.Backspace) {
12786 			textLayout.backspace();
12787 			redraw();
12788 		}
12789 		*/
12790 		ensureVisibleInScroll(textLayout.caretBoundingBox());
12791 	}
12792 
12793 	version(use_new_text_system) {
12794 		bool showingVerticalScroll() { return true; }
12795 		bool showingHorizontalScroll() { return true; }
12796 	}
12797 }
12798 
12799 ///
12800 class LineEdit : EditableTextWidget {
12801 	// FIXME: hack
12802 	version(custom_widgets) {
12803 	override bool showingVerticalScroll() { return false; }
12804 	override bool showingHorizontalScroll() { return false; }
12805 	}
12806 
12807 	override int flexBasisWidth() { return 250; }
12808 
12809 	///
12810 	this(Widget parent) {
12811 		super(parent);
12812 		version(win32_widgets) {
12813 			createWin32Window(this, "edit"w, "",
12814 				0, WS_EX_CLIENTEDGE);//|WS_HSCROLL|ES_AUTOHSCROLL);
12815 		} else version(custom_widgets) {
12816 			version(trash_text) {
12817 				setupCustomTextEditing();
12818 				addEventListener(delegate(CharEvent ev) {
12819 					if(ev.character == '\n')
12820 						ev.preventDefault();
12821 				});
12822 			}
12823 		} else static assert(false);
12824 	}
12825 
12826 	version(use_new_text_system)
12827 	override TextDisplayHelper textDisplayHelperFactory(TextLayouter textLayout, ScrollMessageWidget smw) {
12828 		auto tdh = new TextDisplayHelper(textLayout, smw);
12829 		tdh.singleLine = true;
12830 		return tdh;
12831 	}
12832 
12833 	version(win32_widgets) {
12834 		mixin Padding!q{2};
12835 		override int minHeight() { return borderBoxForContentBox(Rectangle(Point(0, 0), Size(0, defaultLineHeight))).height; }
12836 		override int maxHeight() { return minHeight; }
12837 	}
12838 
12839 	/+
12840 	@property void passwordMode(bool p) {
12841 		SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) | ES_PASSWORD);
12842 	}
12843 	+/
12844 }
12845 
12846 /++
12847 	A [LineEdit] that displays `*` in place of the actual characters.
12848 
12849 	Alas, Windows requires the window to be created differently to use this style,
12850 	so it had to be a new class instead of a toggle on and off on an existing object.
12851 
12852 	FIXME: this is not yet implemented on Linux, it will work the same as a TextEdit there for now.
12853 
12854 	History:
12855 		Added January 24, 2021
12856 +/
12857 class PasswordEdit : EditableTextWidget {
12858 	version(custom_widgets) {
12859 	override bool showingVerticalScroll() { return false; }
12860 	override bool showingHorizontalScroll() { return false; }
12861 	}
12862 
12863 	override int flexBasisWidth() { return 250; }
12864 
12865 	version(use_new_text_system)
12866 	override TextStyle defaultTextStyle() {
12867 		auto cs = getComputedStyle();
12868 
12869 		auto osf = new class OperatingSystemFont {
12870 			this() {
12871 				super(cs.font);
12872 			}
12873 			override int stringWidth(scope const(char)[] text, SimpleWindow window = null) {
12874 				int count = 0;
12875 				foreach(dchar ch; text)
12876 					count++;
12877 				return count * super.stringWidth("*", window);
12878 			}
12879 		};
12880 
12881 		return new TextDisplayHelper.MyTextStyle(osf);
12882 	}
12883 
12884 	version(use_new_text_system)
12885 	override TextDisplayHelper textDisplayHelperFactory(TextLayouter textLayout, ScrollMessageWidget smw) {
12886 		static class TDH : TextDisplayHelper {
12887 			this(TextLayouter textLayout, ScrollMessageWidget smw) {
12888 				singleLine = true;
12889 				super(textLayout, smw);
12890 			}
12891 
12892 			override void drawTextSegment(WidgetPainter painter, Point upperLeft, scope const(char)[] text) {
12893 				char[256] buffer = void;
12894 				int bufferLength = 0;
12895 				foreach(dchar ch; text)
12896 					buffer[bufferLength++] = '*';
12897 				painter.drawText(upperLeft, buffer[0..bufferLength]);
12898 			}
12899 		}
12900 
12901 		return new TDH(textLayout, smw);
12902 	}
12903 
12904 	///
12905 	this(Widget parent) {
12906 		super(parent);
12907 		version(win32_widgets) {
12908 			createWin32Window(this, "edit"w, "",
12909 				ES_PASSWORD, WS_EX_CLIENTEDGE);//|WS_HSCROLL|ES_AUTOHSCROLL);
12910 		} else version(custom_widgets) {
12911 			version(trash_text)
12912 			setupCustomTextEditing();
12913 			addEventListener(delegate(CharEvent ev) {
12914 				if(ev.character == '\n')
12915 					ev.preventDefault();
12916 			});
12917 		} else static assert(false);
12918 	}
12919 	version(win32_widgets) {
12920 		mixin Padding!q{2};
12921 		override int minHeight() { return borderBoxForContentBox(Rectangle(Point(0, 0), Size(0, defaultLineHeight))).height; }
12922 		override int maxHeight() { return minHeight; }
12923 	}
12924 }
12925 
12926 
12927 ///
12928 class TextEdit : EditableTextWidget {
12929 	///
12930 	this(Widget parent) {
12931 		super(parent);
12932 		version(win32_widgets) {
12933 			createWin32Window(this, "edit"w, "",
12934 				0|WS_VSCROLL|WS_HSCROLL|ES_MULTILINE|ES_WANTRETURN|ES_AUTOHSCROLL|ES_AUTOVSCROLL, WS_EX_CLIENTEDGE);
12935 		} else version(custom_widgets) {
12936 			version(trash_text)
12937 			setupCustomTextEditing();
12938 		} else static assert(false);
12939 	}
12940 	override int maxHeight() { return int.max; }
12941 	override int heightStretchiness() { return 7; }
12942 
12943 	override int flexBasisWidth() { return 250; }
12944 	override int flexBasisHeight() { return 25; }
12945 }
12946 
12947 
12948 /++
12949 
12950 +/
12951 version(none)
12952 class RichTextDisplay : Widget {
12953 	@property void content(string c) {}
12954 	void appendContent(string c) {}
12955 }
12956 
12957 ///
12958 class MessageBox : Window {
12959 	private string message;
12960 	MessageBoxButton buttonPressed = MessageBoxButton.None;
12961 	///
12962 	this(string message, string[] buttons = ["OK"], MessageBoxButton[] buttonIds = [MessageBoxButton.OK]) {
12963 		super(300, 100);
12964 
12965 		assert(buttons.length);
12966 		assert(buttons.length ==  buttonIds.length);
12967 
12968 		this.message = message;
12969 
12970 		int buttonsWidth = cast(int) buttons.length * 50 + (cast(int) buttons.length - 1) * 16;
12971 		buttonsWidth = scaleWithDpi(buttonsWidth);
12972 
12973 		int x = this.width / 2 - buttonsWidth / 2;
12974 
12975 		foreach(idx, buttonText; buttons) {
12976 			auto button = new Button(buttonText, this);
12977 			button.x = x;
12978 			button.y = height - (button.height + 10);
12979 			button.addEventListener(EventType.triggered, ((size_t idx) { return () {
12980 				this.buttonPressed = buttonIds[idx];
12981 				win.close();
12982 			}; })(idx));
12983 
12984 			button.registerMovement();
12985 			x += button.width;
12986 			x += scaleWithDpi(16);
12987 			if(idx == 0)
12988 				button.focus();
12989 		}
12990 
12991 		win.show();
12992 		redraw();
12993 	}
12994 
12995 	override void paint(WidgetPainter painter) {
12996 		super.paint(painter);
12997 
12998 		auto cs = getComputedStyle();
12999 
13000 		painter.outlineColor = cs.foregroundColor();
13001 		painter.fillColor = cs.foregroundColor();
13002 
13003 		painter.drawText(Point(0, 0), message, Point(width, height / 2), TextAlignment.Center | TextAlignment.VerticalCenter);
13004 	}
13005 
13006 	// this one is all fixed position
13007 	override void recomputeChildLayout() {}
13008 }
13009 
13010 ///
13011 enum MessageBoxStyle {
13012 	OK, ///
13013 	OKCancel, ///
13014 	RetryCancel, ///
13015 	YesNo, ///
13016 	YesNoCancel, ///
13017 	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.
13018 }
13019 
13020 ///
13021 enum MessageBoxIcon {
13022 	None, ///
13023 	Info, ///
13024 	Warning, ///
13025 	Error ///
13026 }
13027 
13028 /// Identifies the button the user pressed on a message box.
13029 enum MessageBoxButton {
13030 	None, /// The user closed the message box without clicking any of the buttons.
13031 	OK, ///
13032 	Cancel, ///
13033 	Retry, ///
13034 	Yes, ///
13035 	No, ///
13036 	Continue ///
13037 }
13038 
13039 
13040 /++
13041 	Displays a modal message box, blocking until the user dismisses it.
13042 
13043 	Returns: the button pressed.
13044 +/
13045 MessageBoxButton messageBox(string title, string message, MessageBoxStyle style = MessageBoxStyle.OK, MessageBoxIcon icon = MessageBoxIcon.None) {
13046 	version(win32_widgets) {
13047 		WCharzBuffer t = WCharzBuffer(title);
13048 		WCharzBuffer m = WCharzBuffer(message);
13049 		UINT type;
13050 		with(MessageBoxStyle)
13051 		final switch(style) {
13052 			case OK: type |= MB_OK; break;
13053 			case OKCancel: type |= MB_OKCANCEL; break;
13054 			case RetryCancel: type |= MB_RETRYCANCEL; break;
13055 			case YesNo: type |= MB_YESNO; break;
13056 			case YesNoCancel: type |= MB_YESNOCANCEL; break;
13057 			case RetryCancelContinue: type |= MB_CANCELTRYCONTINUE; break;
13058 		}
13059 		with(MessageBoxIcon)
13060 		final switch(icon) {
13061 			case None: break;
13062 			case Info: type |= MB_ICONINFORMATION; break;
13063 			case Warning: type |= MB_ICONWARNING; break;
13064 			case Error: type |= MB_ICONERROR; break;
13065 		}
13066 		switch(MessageBoxW(null, m.ptr, t.ptr, type)) {
13067 			case IDOK: return MessageBoxButton.OK;
13068 			case IDCANCEL: return MessageBoxButton.Cancel;
13069 			case IDTRYAGAIN, IDRETRY: return MessageBoxButton.Retry;
13070 			case IDYES: return MessageBoxButton.Yes;
13071 			case IDNO: return MessageBoxButton.No;
13072 			case IDCONTINUE: return MessageBoxButton.Continue;
13073 			default: return MessageBoxButton.None;
13074 		}
13075 	} else {
13076 		string[] buttons;
13077 		MessageBoxButton[] buttonIds;
13078 		with(MessageBoxStyle)
13079 		final switch(style) {
13080 			case OK:
13081 				buttons = ["OK"];
13082 				buttonIds = [MessageBoxButton.OK];
13083 			break;
13084 			case OKCancel:
13085 				buttons = ["OK", "Cancel"];
13086 				buttonIds = [MessageBoxButton.OK, MessageBoxButton.Cancel];
13087 			break;
13088 			case RetryCancel:
13089 				buttons = ["Retry", "Cancel"];
13090 				buttonIds = [MessageBoxButton.Retry, MessageBoxButton.Cancel];
13091 			break;
13092 			case YesNo:
13093 				buttons = ["Yes", "No"];
13094 				buttonIds = [MessageBoxButton.Yes, MessageBoxButton.No];
13095 			break;
13096 			case YesNoCancel:
13097 				buttons = ["Yes", "No", "Cancel"];
13098 				buttonIds = [MessageBoxButton.Yes, MessageBoxButton.No, MessageBoxButton.Cancel];
13099 			break;
13100 			case RetryCancelContinue:
13101 				buttons = ["Try Again", "Cancel", "Continue"];
13102 				buttonIds = [MessageBoxButton.Retry, MessageBoxButton.Cancel, MessageBoxButton.Continue];
13103 			break;
13104 		}
13105 		auto mb = new MessageBox(message, buttons, buttonIds);
13106 		EventLoop el = EventLoop.get;
13107 		el.run(() { return !mb.win.closed; });
13108 		return mb.buttonPressed;
13109 	}
13110 }
13111 
13112 /// ditto
13113 int messageBox(string message, MessageBoxStyle style = MessageBoxStyle.OK, MessageBoxIcon icon = MessageBoxIcon.None) {
13114 	return messageBox(null, message, style, icon);
13115 }
13116 
13117 
13118 
13119 ///
13120 alias void delegate(Widget handlerAttachedTo, Event event) EventHandler;
13121 
13122 /++
13123 	This is an opaque type you can use to disconnect an event handler when you're no longer interested.
13124 
13125 	History:
13126 		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.
13127 +/
13128 struct EventListener {
13129 	private Widget widget;
13130 	private string event;
13131 	private EventHandler handler;
13132 	private bool useCapture;
13133 
13134 	///
13135 	void disconnect() {
13136 		widget.removeEventListener(this);
13137 	}
13138 }
13139 
13140 /++
13141 	The purpose of this enum was to give a compile-time checked version of various standard event strings.
13142 
13143 	Now, I recommend you use a statically typed event object instead.
13144 
13145 	See_Also: [Event]
13146 +/
13147 enum EventType : string {
13148 	click = "click", ///
13149 
13150 	mouseenter = "mouseenter", ///
13151 	mouseleave = "mouseleave", ///
13152 	mousein = "mousein", ///
13153 	mouseout = "mouseout", ///
13154 	mouseup = "mouseup", ///
13155 	mousedown = "mousedown", ///
13156 	mousemove = "mousemove", ///
13157 
13158 	keydown = "keydown", ///
13159 	keyup = "keyup", ///
13160 	char_ = "char", ///
13161 
13162 	focus = "focus", ///
13163 	blur = "blur", ///
13164 
13165 	triggered = "triggered", ///
13166 
13167 	change = "change", ///
13168 }
13169 
13170 /++
13171 	Represents an event that is currently being processed.
13172 
13173 
13174 	Minigui's event model is based on the web browser. An event has a name, a target,
13175 	and an associated data object. It starts from the window and works its way down through
13176 	the target through all intermediate [Widget]s, triggering capture phase handlers as it goes,
13177 	then goes back up again all the way back to the window, triggering bubble phase handlers. At
13178 	the end, if [Event.preventDefault] has not been called, it calls the target widget's default
13179 	handlers for the event (please note that default handlers will be called even if [Event.stopPropagation]
13180 	was called; that just stops it from calling other handlers in the widget tree, but the default happens
13181 	whenever propagation is done, not only if it gets to the end of the chain).
13182 
13183 	This model has several nice points:
13184 
13185 	$(LIST
13186 		* It is easy to delegate dynamic handlers to a parent. You can have a parent container
13187 		  with event handlers set, then add/remove children as much as you want without needing
13188 		  to manage the event handlers on them - the parent alone can manage everything.
13189 
13190 		* It is easy to create new custom events in your application.
13191 
13192 		* It is familiar to many web developers.
13193 	)
13194 
13195 	There's a few downsides though:
13196 
13197 	$(LIST
13198 		* There's not a lot of type safety.
13199 
13200 		* You don't get a static list of what events a widget can emit.
13201 
13202 		* Tracing where an event got cancelled along the chain can get difficult; the downside of
13203 		  the central delegation benefit is it can be lead to debugging of action at a distance.
13204 	)
13205 
13206 	In May 2021, I started to adjust this model to minigui takes better advantage of D over Javascript
13207 	while keeping the benefits - and most compatibility with - the existing model. The main idea is
13208 	to simply use a D object type which provides a static interface as well as a built-in event name.
13209 	Then, a new static interface allows you to see what an event can emit and attach handlers to it
13210 	similarly to C#, which just forwards to the JS style api. They're fully compatible so you can still
13211 	delegate to a parent and use custom events as well as using the runtime dynamic access, in addition
13212 	to having a little more help from the D compiler and documentation generator.
13213 
13214 	Your code would change like this:
13215 
13216 	---
13217 	// old
13218 	widget.addEventListener("keydown", (Event ev) { ... }, /* optional arg */ useCapture );
13219 
13220 	// new
13221 	widget.addEventListener((KeyDownEvent ev) { ... }, /* optional arg */ useCapture );
13222 	---
13223 
13224 	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.
13225 
13226 	All you have to do is replace the string with a specific Event subclass. It will figure out the event string from the class.
13227 
13228 	Alternatively, you can cast the Event yourself to the appropriate subclass, but it is easier to let the library do it for you!
13229 
13230 	Thus the family of functions are:
13231 
13232 	[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.
13233 
13234 	[Widget.addDirectEventListener] is addEventListener, but only calls the handler if target == this. Useful for something you can't afford to delegate.
13235 
13236 	[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.
13237 
13238 	Let's implement a custom widget that can emit a ChangeEvent describing its `checked` property:
13239 
13240 	---
13241 	class MyCheckbox : Widget {
13242 		/// This gives a chance to document it and generates a convenience function to send it and attach handlers.
13243 		/// It is NOT actually required but should be used whenever possible.
13244 		mixin Emits!(ChangeEvent!bool);
13245 
13246 		this(Widget parent) {
13247 			super(parent);
13248 			setDefaultEventHandler((ClickEvent) { checked = !checked; });
13249 		}
13250 
13251 		private bool _checked;
13252 		@property bool checked() { return _checked; }
13253 		@property void checked(bool set) {
13254 			_checked = set;
13255 			emit!(ChangeEvent!bool)(&checked);
13256 		}
13257 	}
13258 	---
13259 
13260 	## Creating Your Own Events
13261 
13262 	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.
13263 
13264 	---
13265 	class MyEvent : Event {
13266 		this(Widget target) { super(EventString, target); }
13267 		mixin Register; // adds EventString and other reflection information
13268 	}
13269 	---
13270 
13271 	Then declare that it is sent with the [Emits] mixin, so you can use [Widget.emit] to dispatch it.
13272 
13273 	History:
13274 		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.
13275 
13276 		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.
13277 +/
13278 /+
13279 
13280 	## General Conventions
13281 
13282 	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.
13283 
13284 
13285 	## Qt-style signals and slots
13286 
13287 	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.
13288 
13289 	The intention is for events to be used when
13290 
13291 	---
13292 	class Demo : Widget {
13293 		this() {
13294 			myPropertyChanged = Signal!int(this);
13295 		}
13296 		@property myProperty(int v) {
13297 			myPropertyChanged.emit(v);
13298 		}
13299 
13300 		Signal!int myPropertyChanged; // i need to get `this` off it and inspect the name...
13301 		// but it can just genuinely not care about `this` since that's not really passed.
13302 	}
13303 
13304 	class Foo : Widget {
13305 		// the slot uda is not necessary, but it helps the script and ui builder find it.
13306 		@slot void setValue(int v) { ... }
13307 	}
13308 
13309 	demo.myPropertyChanged.connect(&foo.setValue);
13310 	---
13311 
13312 	The Signal type has a disabled default constructor, meaning your widget constructor must pass `this` to it in its constructor.
13313 
13314 	Some events may also wish to implement the Signal interface. These use particular arguments to call a method automatically.
13315 
13316 	class StringChangeEvent : ChangeEvent, Signal!string {
13317 		mixin SignalImpl
13318 	}
13319 
13320 +/
13321 class Event : ReflectableProperties {
13322 	/// Creates an event without populating any members and without sending it. See [dispatch]
13323 	this(string eventName, Widget emittedBy) {
13324 		this.eventName = eventName;
13325 		this.srcElement = emittedBy;
13326 	}
13327 
13328 
13329 	/// Implementations for the [ReflectableProperties] interface/
13330 	void getPropertiesList(scope void delegate(string name) sink) const {}
13331 	/// ditto
13332 	void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink) { }
13333 	/// ditto
13334 	SetPropertyResult setPropertyFromString(string name, scope const(char)[] str, bool strIsJson) {
13335 		return SetPropertyResult.notPermitted;
13336 	}
13337 
13338 
13339 	/+
13340 	/++
13341 		This is an internal implementation detail of [Register] and is subject to be changed or removed at any time without notice.
13342 
13343 		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.
13344 	+/
13345 	protected final void sinkJsonString(string memberName, scope const(char)[] value, scope void delegate(string name, scope const(char)[] value) finalSink) {
13346 		if(value.length == 0) {
13347 			finalSink(memberName, `""`);
13348 			return;
13349 		}
13350 
13351 		char[1024] bufferBacking;
13352 		char[] buffer = bufferBacking;
13353 		int bufferPosition;
13354 
13355 		void sink(char ch) {
13356 			if(bufferPosition >= buffer.length)
13357 				buffer.length = buffer.length + 1024;
13358 			buffer[bufferPosition++] = ch;
13359 		}
13360 
13361 		sink('"');
13362 
13363 		foreach(ch; value) {
13364 			switch(ch) {
13365 				case '\\':
13366 					sink('\\'); sink('\\');
13367 				break;
13368 				case '"':
13369 					sink('\\'); sink('"');
13370 				break;
13371 				case '\n':
13372 					sink('\\'); sink('n');
13373 				break;
13374 				case '\r':
13375 					sink('\\'); sink('r');
13376 				break;
13377 				case '\t':
13378 					sink('\\'); sink('t');
13379 				break;
13380 				default:
13381 					sink(ch);
13382 			}
13383 		}
13384 
13385 		sink('"');
13386 
13387 		finalSink(memberName, buffer[0 .. bufferPosition]);
13388 	}
13389 	+/
13390 
13391 	/+
13392 	enum EventInitiator {
13393 		system,
13394 		minigui,
13395 		user
13396 	}
13397 
13398 	immutable EventInitiator; initiatedBy;
13399 	+/
13400 
13401 	/++
13402 		Events should generally follow the propagation model, but there's some exceptions
13403 		to that rule. If so, they should override this to return false. In that case, only
13404 		bubbling event handlers on the target itself and capturing event handlers on the containing
13405 		window will be called. (That is, [dispatch] will call [sendDirectly] instead of doing the normal
13406 		capture -> target -> bubble process.)
13407 
13408 		History:
13409 			Added May 12, 2021
13410 	+/
13411 	bool propagates() const pure nothrow @nogc @safe {
13412 		return true;
13413 	}
13414 
13415 	/++
13416 		hints as to whether preventDefault will actually do anything. not entirely reliable.
13417 
13418 		History:
13419 			Added May 14, 2021
13420 	+/
13421 	bool cancelable() const pure nothrow @nogc @safe {
13422 		return true;
13423 	}
13424 
13425 	/++
13426 		You can mix this into child class to register some boilerplate. It includes the `EventString`
13427 		member, a constructor, and implementations of the dynamic get data interfaces.
13428 
13429 		If you fail to do this, your event will probably not have full compatibility but it might still work for you.
13430 
13431 
13432 		You can override the default EventString by simply providing your own in the form of
13433 		`enum string EventString = "some.name";` The default is the name of your class and its parent entity
13434 		which provides some namespace protection against conflicts in other libraries while still being fairly
13435 		easy to use.
13436 
13437 		If you provide your own constructor, it will override the default constructor provided here. A constructor
13438 		must call `super(EventString, passed_widget_target)` at some point. The `passed_widget_target` must be the
13439 		first argument to your constructor.
13440 
13441 		History:
13442 			Added May 13, 2021.
13443 	+/
13444 	protected static mixin template Register() {
13445 		public enum string EventString = __traits(identifier, __traits(parent, typeof(this))) ~ "." ~  __traits(identifier, typeof(this));
13446 		this(Widget target) { super(EventString, target); }
13447 
13448 		mixin ReflectableProperties.RegisterGetters;
13449 	}
13450 
13451 	/++
13452 		This is the widget that emitted the event.
13453 
13454 
13455 		The aliased names come from Javascript for ease of web developers to transition in, but they're all synonyms.
13456 
13457 		History:
13458 			The `source` name was added on May 14, 2021. It is a little weird that `source` and `target` are synonyms,
13459 			but that's a side effect of it doing both capture and bubble handlers and people are used to it from the web
13460 			so I don't intend to remove these aliases.
13461 	+/
13462 	Widget source;
13463 	/// ditto
13464 	alias source target;
13465 	/// ditto
13466 	alias source srcElement;
13467 
13468 	Widget relatedTarget; /// Note: likely to be deprecated at some point.
13469 
13470 	/// Prevents the default event handler (if there is one) from being called
13471 	void preventDefault() {
13472 		lastDefaultPrevented = true;
13473 		defaultPrevented = true;
13474 	}
13475 
13476 	/// Stops the event propagation immediately.
13477 	void stopPropagation() {
13478 		propagationStopped = true;
13479 	}
13480 
13481 	private bool defaultPrevented;
13482 	private bool propagationStopped;
13483 	private string eventName;
13484 
13485 	private bool isBubbling;
13486 
13487 	/// 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.
13488 	protected void adjustScrolling() { }
13489 	/// ditto
13490 	protected void adjustClientCoordinates(int deltaX, int deltaY) { }
13491 
13492 	/++
13493 		this sends it only to the target. If you want propagation, use dispatch() instead.
13494 
13495 		This should be made private!!!
13496 
13497 	+/
13498 	void sendDirectly() {
13499 		if(srcElement is null)
13500 			return;
13501 
13502 		// 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.
13503 
13504 		//debug if(eventName != "mousemove" && target !is null && target.parentWindow && target.parentWindow.devTools)
13505 			//target.parentWindow.devTools.log("Event ", eventName, " dispatched directly to ", srcElement);
13506 
13507 		adjustScrolling();
13508 
13509 		if(auto e = target.parentWindow) {
13510 			if(auto handlers = "*" in e.capturingEventHandlers)
13511 			foreach(handler; *handlers)
13512 				if(handler) handler(e, this);
13513 			if(auto handlers = eventName in e.capturingEventHandlers)
13514 			foreach(handler; *handlers)
13515 				if(handler) handler(e, this);
13516 		}
13517 
13518 		auto e = srcElement;
13519 
13520 		if(auto handlers = eventName in e.bubblingEventHandlers)
13521 		foreach(handler; *handlers)
13522 			if(handler) handler(e, this);
13523 
13524 		if(auto handlers = "*" in e.bubblingEventHandlers)
13525 		foreach(handler; *handlers)
13526 			if(handler) handler(e, this);
13527 
13528 		// there's never a default for a catch-all event
13529 		if(!defaultPrevented)
13530 			if(eventName in e.defaultEventHandlers)
13531 				e.defaultEventHandlers[eventName](e, this);
13532 	}
13533 
13534 	/// this dispatches the element using the capture -> target -> bubble process
13535 	void dispatch() {
13536 		if(srcElement is null)
13537 			return;
13538 
13539 		if(!propagates) {
13540 			sendDirectly;
13541 			return;
13542 		}
13543 
13544 		//debug if(eventName != "mousemove" && target !is null && target.parentWindow && target.parentWindow.devTools)
13545 			//target.parentWindow.devTools.log("Event ", eventName, " dispatched to ", srcElement);
13546 
13547 		adjustScrolling();
13548 		// first capture, then bubble
13549 
13550 		Widget[] chain;
13551 		Widget curr = srcElement;
13552 		while(curr) {
13553 			auto l = curr;
13554 			chain ~= l;
13555 			curr = curr.parent;
13556 		}
13557 
13558 		isBubbling = false;
13559 
13560 		foreach_reverse(e; chain) {
13561 			if(auto handlers = "*" in e.capturingEventHandlers)
13562 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
13563 
13564 			if(propagationStopped)
13565 				break;
13566 
13567 			if(auto handlers = eventName in e.capturingEventHandlers)
13568 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
13569 
13570 			// the default on capture should really be to always do nothing
13571 
13572 			//if(!defaultPrevented)
13573 			//	if(eventName in e.defaultEventHandlers)
13574 			//		e.defaultEventHandlers[eventName](e.element, this);
13575 
13576 			if(propagationStopped)
13577 				break;
13578 		}
13579 
13580 		int adjustX;
13581 		int adjustY;
13582 
13583 		isBubbling = true;
13584 		if(!propagationStopped)
13585 		foreach(e; chain) {
13586 			if(auto handlers = eventName in e.bubblingEventHandlers)
13587 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
13588 
13589 			if(propagationStopped)
13590 				break;
13591 
13592 			if(auto handlers = "*" in e.bubblingEventHandlers)
13593 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
13594 
13595 			if(propagationStopped)
13596 				break;
13597 
13598 			if(e.encapsulatedChildren()) {
13599 				adjustClientCoordinates(adjustX, adjustY);
13600 				target = e;
13601 			} else {
13602 				adjustX += e.x;
13603 				adjustY += e.y;
13604 			}
13605 		}
13606 
13607 		if(!defaultPrevented)
13608 		foreach(e; chain) {
13609 			if(eventName in e.defaultEventHandlers)
13610 				e.defaultEventHandlers[eventName](e, this);
13611 		}
13612 	}
13613 
13614 
13615 	/* old compatibility things */
13616 	deprecated("Use some subclass of KeyEventBase instead of plain Event in your handler going forward. WARNING these may crash on non-key events!")
13617 	final @property {
13618 		Key key() { return (cast(KeyEventBase) this).key; }
13619 		KeyEvent originalKeyEvent() { return (cast(KeyEventBase) this).originalKeyEvent; }
13620 
13621 		bool ctrlKey() { return (cast(KeyEventBase) this).ctrlKey; }
13622 		bool altKey() { return (cast(KeyEventBase) this).altKey; }
13623 		bool shiftKey() { return (cast(KeyEventBase) this).shiftKey; }
13624 	}
13625 
13626 	deprecated("Use some subclass of MouseEventBase instead of Event in your handler going forward. WARNING these may crash on non-mouse events!")
13627 	final @property {
13628 		int clientX() { return (cast(MouseEventBase) this).clientX; }
13629 		int clientY() { return (cast(MouseEventBase) this).clientY; }
13630 
13631 		int viewportX() { return (cast(MouseEventBase) this).viewportX; }
13632 		int viewportY() { return (cast(MouseEventBase) this).viewportY; }
13633 
13634 		int button() { return (cast(MouseEventBase) this).button; }
13635 		int buttonLinear() { return (cast(MouseEventBase) this).buttonLinear; }
13636 	}
13637 
13638 	deprecated("Use either a KeyEventBase or a MouseEventBase instead of Event in your handler going forward")
13639 	final @property {
13640 		int state() {
13641 			if(auto meb = cast(MouseEventBase) this)
13642 				return meb.state;
13643 			if(auto keb = cast(KeyEventBase) this)
13644 				return keb.state;
13645 			assert(0);
13646 		}
13647 	}
13648 
13649 	deprecated("Use a CharEvent instead of Event in your handler going forward")
13650 	final @property {
13651 		dchar character() {
13652 			if(auto ce = cast(CharEvent) this)
13653 				return ce.character;
13654 			return dchar.init;
13655 		}
13656 	}
13657 
13658 	// for change events
13659 	@property {
13660 		///
13661 		int intValue() { return 0; }
13662 		///
13663 		string stringValue() { return null; }
13664 	}
13665 }
13666 
13667 /++
13668 	This lets you statically verify you send the events you claim you send and gives you a hook to document them.
13669 
13670 	Please note that a widget may send events not listed as Emits. You can always construct and dispatch
13671 	dynamic and custom events, but the static list helps ensure you get them right.
13672 
13673 	If this is declared, you can use [Widget.emit] to send the event.
13674 
13675 	All events work the same way though, following the capture->widget->bubble model described under [Event].
13676 
13677 	History:
13678 		Added May 4, 2021
13679 +/
13680 mixin template Emits(EventType) {
13681 	import arsd.minigui : EventString;
13682 	static if(is(EventType : Event) && !is(EventType == Event))
13683 		mixin("private EventType[0] emits_" ~ EventStringIdentifier!EventType ~";");
13684 	else
13685 		static assert(0, "You can only emit subclasses of Event");
13686 }
13687 
13688 /// ditto
13689 mixin template Emits(string eventString) {
13690 	mixin("private Event[0] emits_" ~ eventString ~";");
13691 }
13692 
13693 /*
13694 class SignalEvent(string name) : Event {
13695 
13696 }
13697 */
13698 
13699 /++
13700 	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".
13701 
13702 
13703 	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.
13704 
13705 	History:
13706 		Added on May 13, 2021. Prior to that, you'd most likely `addEventListener(EventType.triggered, ...)` to handle similar things.
13707 +/
13708 class CommandEvent : Event {
13709 	enum EventString = "command";
13710 	this(Widget source, string CommandString = EventString) {
13711 		super(CommandString, source);
13712 	}
13713 }
13714 
13715 /++
13716 	A [CommandEvent] is typically actually an instance of these to hold the strongly-typed arguments.
13717 +/
13718 class CommandEventWithArgs(Args...) : CommandEvent {
13719 	this(Widget source, string CommandString, Args args) { super(source, CommandString); this.args = args; }
13720 	Args args;
13721 }
13722 
13723 /++
13724 	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.
13725 
13726 	See [CommandEvent] for more information.
13727 
13728 	Returns:
13729 		The [EventListener] you can use to remove the handler.
13730 +/
13731 EventListener consumesCommand(string CommandString, WidgetType, Args...)(WidgetType w, void delegate(Args) handler) {
13732 	return w.addEventListener(CommandString, (Event ev) {
13733 		if(ev.target is w)
13734 			return; // it does not consume its own commands!
13735 		if(auto cev = cast(CommandEventWithArgs!Args) ev) {
13736 			handler(cev.args);
13737 			ev.stopPropagation();
13738 		}
13739 	});
13740 }
13741 
13742 /++
13743 	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.
13744 +/
13745 void emitCommand(string CommandString, WidgetType, Args...)(WidgetType w, Args args) {
13746 	auto event = new CommandEventWithArgs!Args(w, CommandString, args);
13747 	event.dispatch();
13748 }
13749 
13750 class ResizeEvent : Event {
13751 	enum EventString = "resize";
13752 
13753 	this(Widget target) { super(EventString, target); }
13754 
13755 	override bool propagates() const { return false; }
13756 }
13757 
13758 /++
13759 	ClosingEvent is fired when a user is attempting to close a window. You can `preventDefault` to cancel the close.
13760 
13761 	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.
13762 
13763 	History:
13764 		Added June 21, 2021 (dub v10.1)
13765 +/
13766 class ClosingEvent : Event {
13767 	enum EventString = "closing";
13768 
13769 	this(Widget target) { super(EventString, target); }
13770 
13771 	override bool propagates() const { return false; }
13772 	override bool cancelable() const { return true; }
13773 }
13774 
13775 /// ditto
13776 class ClosedEvent : Event {
13777 	enum EventString = "closed";
13778 
13779 	this(Widget target) { super(EventString, target); }
13780 
13781 	override bool propagates() const { return false; }
13782 	override bool cancelable() const { return false; }
13783 }
13784 
13785 ///
13786 class BlurEvent : Event {
13787 	enum EventString = "blur";
13788 
13789 	// FIXME: related target?
13790 	this(Widget target) { super(EventString, target); }
13791 
13792 	override bool propagates() const { return false; }
13793 }
13794 
13795 ///
13796 class FocusEvent : Event {
13797 	enum EventString = "focus";
13798 
13799 	// FIXME: related target?
13800 	this(Widget target) { super(EventString, target); }
13801 
13802 	override bool propagates() const { return false; }
13803 }
13804 
13805 /++
13806 	FocusInEvent is a FocusEvent that propagates, while FocusOutEvent is a BlurEvent that propagates.
13807 
13808 	History:
13809 		Added July 3, 2021
13810 +/
13811 class FocusInEvent : Event {
13812 	enum EventString = "focusin";
13813 
13814 	// FIXME: related target?
13815 	this(Widget target) { super(EventString, target); }
13816 
13817 	override bool cancelable() const { return false; }
13818 }
13819 
13820 /// ditto
13821 class FocusOutEvent : Event {
13822 	enum EventString = "focusout";
13823 
13824 	// FIXME: related target?
13825 	this(Widget target) { super(EventString, target); }
13826 
13827 	override bool cancelable() const { return false; }
13828 }
13829 
13830 ///
13831 class ScrollEvent : Event {
13832 	enum EventString = "scroll";
13833 	this(Widget target) { super(EventString, target); }
13834 
13835 	override bool cancelable() const { return false; }
13836 }
13837 
13838 /++
13839 	Indicates that a character has been typed by the user. Normally dispatched to the currently focused widget.
13840 
13841 	History:
13842 		Added May 2, 2021. Previously, this was simply a "char" event and `character` as a member of the [Event] base class.
13843 +/
13844 class CharEvent : Event {
13845 	enum EventString = "char";
13846 	this(Widget target, dchar ch) {
13847 		character = ch;
13848 		super(EventString, target);
13849 	}
13850 
13851 	immutable dchar character;
13852 }
13853 
13854 /++
13855 	You should generally use a `ChangeEvent!Type` instead of this directly. See [ChangeEvent] for more information.
13856 +/
13857 abstract class ChangeEventBase : Event {
13858 	enum EventString = "change";
13859 	this(Widget target) {
13860 		super(EventString, target);
13861 	}
13862 
13863 	/+
13864 		// idk where or how exactly i want to do this.
13865 		// i might come back to it later.
13866 
13867 	// If a widget itself broadcasts one of theses itself, it stops propagation going down
13868 	// this way the source doesn't get too confused (think of a nested scroll widget)
13869 	//
13870 	// the idea is like the scroll bar emits a command event saying like "scroll left one line"
13871 	// then you consume that command and change you scroll x position to whatever. then you do
13872 	// some kind of change event that is broadcast back to the children and any horizontal scroll
13873 	// listeners are now able to update, without having an explicit connection between them.
13874 	void broadcastToChildren(string fieldName) {
13875 
13876 	}
13877 	+/
13878 }
13879 
13880 /++
13881 	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.
13882 
13883 
13884 	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).
13885 
13886 	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);`
13887 
13888 	Since it is emitted after the value has already changed, [preventDefault] is unlikely to do anything.
13889 
13890 	History:
13891 		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.
13892 +/
13893 class ChangeEvent(T) : ChangeEventBase {
13894 	this(Widget target, T delegate() getNewValue) {
13895 		assert(getNewValue !is null);
13896 		this.getNewValue = getNewValue;
13897 		super(target);
13898 	}
13899 
13900 	private T delegate() getNewValue;
13901 
13902 	/++
13903 		Gets the new value that just changed.
13904 	+/
13905 	@property T value() {
13906 		return getNewValue();
13907 	}
13908 
13909 	/// compatibility method for old generic Events
13910 	static if(is(immutable T == immutable int))
13911 		override int intValue() { return value; }
13912 	/// ditto
13913 	static if(is(immutable T == immutable string))
13914 		override string stringValue() { return value; }
13915 }
13916 
13917 /++
13918 	Contains shared properties for [KeyDownEvent]s and [KeyUpEvent]s.
13919 
13920 
13921 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
13922 
13923 	History:
13924 		Added May 2, 2021. Previously, its properties were members of the [Event] base class.
13925 +/
13926 abstract class KeyEventBase : Event {
13927 	this(string name, Widget target) {
13928 		super(name, target);
13929 	}
13930 
13931 	// for key events
13932 	Key key; ///
13933 
13934 	KeyEvent originalKeyEvent;
13935 
13936 	/++
13937 		Indicates the current state of the given keyboard modifier keys.
13938 
13939 		History:
13940 			Added to events on April 15, 2020.
13941 	+/
13942 	bool ctrlKey;
13943 
13944 	/// ditto
13945 	bool altKey;
13946 
13947 	/// ditto
13948 	bool shiftKey;
13949 
13950 	/++
13951 		The raw bitflags that are parsed out into [ctrlKey], [altKey], and [shiftKey].
13952 
13953 		See [arsd.simpledisplay.ModifierState] for other possible flags.
13954 	+/
13955 	int state;
13956 
13957 	mixin Register;
13958 }
13959 
13960 /++
13961 	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].
13962 
13963 
13964 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
13965 
13966 	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.
13967 
13968 	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.
13969 
13970 	See_Also: [KeyUpEvent], [CharEvent]
13971 
13972 	History:
13973 		Added May 2, 2021. Previously, it was only seen as the base [Event] class on "keydown" event listeners.
13974 +/
13975 class KeyDownEvent : KeyEventBase {
13976 	enum EventString = "keydown";
13977 	this(Widget target) { super(EventString, target); }
13978 }
13979 
13980 /++
13981 	Indicates that the user has released a key on the keyboard. For available properties, see [KeyEventBase].
13982 
13983 
13984 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
13985 
13986 	See_Also: [KeyDownEvent], [CharEvent]
13987 
13988 	History:
13989 		Added May 2, 2021. Previously, it was only seen as the base [Event] class on "keyup" event listeners.
13990 +/
13991 class KeyUpEvent : KeyEventBase {
13992 	enum EventString = "keyup";
13993 	this(Widget target) { super(EventString, target); }
13994 }
13995 
13996 /++
13997 	Contains shared properties for various mouse events;
13998 
13999 
14000 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
14001 
14002 	History:
14003 		Added May 2, 2021. Previously, its properties were members of the [Event] base class.
14004 +/
14005 abstract class MouseEventBase : Event {
14006 	this(string name, Widget target) {
14007 		super(name, target);
14008 	}
14009 
14010 	// for mouse events
14011 	int clientX; /// The mouse event location relative to the target widget
14012 	int clientY; /// ditto
14013 
14014 	int viewportX; /// The mouse event location relative to the window origin
14015 	int viewportY; /// ditto
14016 
14017 	int button; /// See: [MouseEvent.button]
14018 	int buttonLinear; /// See: [MouseEvent.buttonLinear]
14019 
14020 	/++
14021 		Indicates the current state of the given keyboard modifier keys.
14022 
14023 		History:
14024 			Added to mouse events on September 28, 2010.
14025 	+/
14026 	bool ctrlKey;
14027 
14028 	/// ditto
14029 	bool altKey;
14030 
14031 	/// ditto
14032 	bool shiftKey;
14033 
14034 
14035 
14036 	int state; ///
14037 
14038 	/++
14039 		for consistent names with key event.
14040 
14041 		History:
14042 			Added September 28, 2021 (dub v10.3)
14043 	+/
14044 	alias modifierState = state;
14045 
14046 	/++
14047 		Mouse wheel movement sends down/up/click events just like other buttons clicking. This method is to help you filter that out.
14048 
14049 		History:
14050 			Added May 15, 2021
14051 	+/
14052 	bool isMouseWheel() {
14053 		return button == MouseButton.wheelUp || button == MouseButton.wheelDown;
14054 	}
14055 
14056 	// private
14057 	override void adjustClientCoordinates(int deltaX, int deltaY) {
14058 		clientX += deltaX;
14059 		clientY += deltaY;
14060 	}
14061 
14062 	override void adjustScrolling() {
14063 	version(custom_widgets) { // TEMP
14064 		viewportX = clientX;
14065 		viewportY = clientY;
14066 		if(auto se = cast(ScrollableWidget) srcElement) {
14067 			clientX += se.scrollOrigin.x;
14068 			clientY += se.scrollOrigin.y;
14069 		} else if(auto se = cast(ScrollableContainerWidget) srcElement) {
14070 			//clientX += se.scrollX_;
14071 			//clientY += se.scrollY_;
14072 		}
14073 	}
14074 	}
14075 
14076 	mixin Register;
14077 }
14078 
14079 /++
14080 	Indicates that the user has worked with the mouse over your widget. For available properties, see [MouseEventBase].
14081 
14082 
14083 	$(WARNING
14084 		Important: MouseDownEvent, MouseUpEvent, ClickEvent, and DoubleClickEvent are all sent for all mouse buttons and
14085 		for wheel movement! You should check the [MouseEventBase.button|button] property in most your handlers to get correct
14086 		behavior.
14087 	)
14088 
14089 	[MouseDownEvent] is sent when the user presses a mouse button. It is also sent on mouse wheel movement.
14090 
14091 	[MouseUpEvent] is sent when the user releases a mouse button.
14092 
14093 	[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.)
14094 
14095 	[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.
14096 
14097 	[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.
14098 
14099 	[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.
14100 
14101 	[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.
14102 
14103 	[MouseEnterEvent] is sent when the mouse enters the bounding box of a widget.
14104 
14105 	[MouseLeaveEvent] is sent when the mouse leaves the bounding box of a widget.
14106 
14107 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
14108 
14109 	Rationale:
14110 
14111 		If you only want to do drag, mousedown/up works just fine being consistently sent.
14112 
14113 		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).
14114 
14115 		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.
14116 
14117 	History:
14118 		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.
14119 +/
14120 class MouseUpEvent : MouseEventBase {
14121 	enum EventString = "mouseup"; ///
14122 	this(Widget target) { super(EventString, target); }
14123 }
14124 /// ditto
14125 class MouseDownEvent : MouseEventBase {
14126 	enum EventString = "mousedown"; ///
14127 	this(Widget target) { super(EventString, target); }
14128 }
14129 /// ditto
14130 class MouseMoveEvent : MouseEventBase {
14131 	enum EventString = "mousemove"; ///
14132 	this(Widget target) { super(EventString, target); }
14133 }
14134 /// ditto
14135 class ClickEvent : MouseEventBase {
14136 	enum EventString = "click"; ///
14137 	this(Widget target) { super(EventString, target); }
14138 }
14139 /// ditto
14140 class DoubleClickEvent : MouseEventBase {
14141 	enum EventString = "dblclick"; ///
14142 	this(Widget target) { super(EventString, target); }
14143 }
14144 /// ditto
14145 class MouseOverEvent : Event {
14146 	enum EventString = "mouseover"; ///
14147 	this(Widget target) { super(EventString, target); }
14148 }
14149 /// ditto
14150 class MouseOutEvent : Event {
14151 	enum EventString = "mouseout"; ///
14152 	this(Widget target) { super(EventString, target); }
14153 }
14154 /// ditto
14155 class MouseEnterEvent : Event {
14156 	enum EventString = "mouseenter"; ///
14157 	this(Widget target) { super(EventString, target); }
14158 
14159 	override bool propagates() const { return false; }
14160 }
14161 /// ditto
14162 class MouseLeaveEvent : Event {
14163 	enum EventString = "mouseleave"; ///
14164 	this(Widget target) { super(EventString, target); }
14165 
14166 	override bool propagates() const { return false; }
14167 }
14168 
14169 private bool isAParentOf(Widget a, Widget b) {
14170 	if(a is null || b is null)
14171 		return false;
14172 
14173 	while(b !is null) {
14174 		if(a is b)
14175 			return true;
14176 		b = b.parent;
14177 	}
14178 
14179 	return false;
14180 }
14181 
14182 private struct WidgetAtPointResponse {
14183 	Widget widget;
14184 
14185 	// x, y relative to the widget in the response.
14186 	int x;
14187 	int y;
14188 }
14189 
14190 private WidgetAtPointResponse widgetAtPoint(Widget starting, int x, int y) {
14191 	assert(starting !is null);
14192 
14193 	starting.addScrollPosition(x, y);
14194 
14195 	auto child = starting.getChildAtPosition(x, y);
14196 	while(child) {
14197 		if(child.hidden)
14198 			continue;
14199 		starting = child;
14200 		x -= child.x;
14201 		y -= child.y;
14202 		auto r = starting.widgetAtPoint(x, y);//starting.getChildAtPosition(x, y);
14203 		child = r.widget;
14204 		if(child is starting)
14205 			break;
14206 	}
14207 	return WidgetAtPointResponse(starting, x, y);
14208 }
14209 
14210 version(win32_widgets) {
14211 private:
14212 	import core.sys.windows.commctrl;
14213 
14214 	pragma(lib, "comctl32");
14215 	shared static this() {
14216 		// http://msdn.microsoft.com/en-us/library/windows/desktop/bb775507(v=vs.85).aspx
14217 		INITCOMMONCONTROLSEX ic;
14218 		ic.dwSize = cast(DWORD) ic.sizeof;
14219 		ic.dwICC = ICC_UPDOWN_CLASS | ICC_WIN95_CLASSES | ICC_BAR_CLASSES | ICC_PROGRESS_CLASS | ICC_COOL_CLASSES | ICC_STANDARD_CLASSES | ICC_USEREX_CLASSES;
14220 		if(!InitCommonControlsEx(&ic)) {
14221 			//writeln("ICC failed");
14222 		}
14223 	}
14224 
14225 
14226 	// everything from here is just win32 headers copy pasta
14227 private:
14228 extern(Windows):
14229 
14230 	alias HANDLE HMENU;
14231 	HMENU CreateMenu();
14232 	bool SetMenu(HWND, HMENU);
14233 	HMENU CreatePopupMenu();
14234 	enum MF_POPUP = 0x10;
14235 	enum MF_STRING = 0;
14236 
14237 
14238 	BOOL InitCommonControlsEx(const INITCOMMONCONTROLSEX*);
14239 	struct INITCOMMONCONTROLSEX {
14240 		DWORD dwSize;
14241 		DWORD dwICC;
14242 	}
14243 	enum HINST_COMMCTRL = cast(HINSTANCE) (-1);
14244 enum {
14245         IDB_STD_SMALL_COLOR,
14246         IDB_STD_LARGE_COLOR,
14247         IDB_VIEW_SMALL_COLOR = 4,
14248         IDB_VIEW_LARGE_COLOR = 5
14249 }
14250 enum {
14251         STD_CUT,
14252         STD_COPY,
14253         STD_PASTE,
14254         STD_UNDO,
14255         STD_REDOW,
14256         STD_DELETE,
14257         STD_FILENEW,
14258         STD_FILEOPEN,
14259         STD_FILESAVE,
14260         STD_PRINTPRE,
14261         STD_PROPERTIES,
14262         STD_HELP,
14263         STD_FIND,
14264         STD_REPLACE,
14265         STD_PRINT // = 14
14266 }
14267 
14268 alias HANDLE HIMAGELIST;
14269 	HIMAGELIST ImageList_Create(int, int, UINT, int, int);
14270 	int ImageList_Add(HIMAGELIST, HBITMAP, HBITMAP);
14271         BOOL ImageList_Destroy(HIMAGELIST);
14272 
14273 uint MAKELONG(ushort a, ushort b) {
14274         return cast(uint) ((b << 16) | a);
14275 }
14276 
14277 
14278 struct TBBUTTON {
14279 	int   iBitmap;
14280 	int   idCommand;
14281 	BYTE  fsState;
14282 	BYTE  fsStyle;
14283 	version(Win64)
14284 	BYTE[6] bReserved;
14285 	else
14286 	BYTE[2]  bReserved;
14287 	DWORD dwData;
14288 	INT_PTR   iString;
14289 }
14290 
14291 	enum {
14292 		TB_ADDBUTTONSA   = WM_USER + 20,
14293 		TB_INSERTBUTTONA = WM_USER + 21,
14294 		TB_GETIDEALSIZE = WM_USER + 99,
14295 	}
14296 
14297 struct SIZE {
14298 	LONG cx;
14299 	LONG cy;
14300 }
14301 
14302 
14303 enum {
14304 	TBSTATE_CHECKED       = 1,
14305 	TBSTATE_PRESSED       = 2,
14306 	TBSTATE_ENABLED       = 4,
14307 	TBSTATE_HIDDEN        = 8,
14308 	TBSTATE_INDETERMINATE = 16,
14309 	TBSTATE_WRAP          = 32
14310 }
14311 
14312 
14313 
14314 enum {
14315 	ILC_COLOR    = 0,
14316 	ILC_COLOR4   = 4,
14317 	ILC_COLOR8   = 8,
14318 	ILC_COLOR16  = 16,
14319 	ILC_COLOR24  = 24,
14320 	ILC_COLOR32  = 32,
14321 	ILC_COLORDDB = 254,
14322 	ILC_MASK     = 1,
14323 	ILC_PALETTE  = 2048
14324 }
14325 
14326 
14327 alias TBBUTTON*       PTBBUTTON, LPTBBUTTON;
14328 
14329 
14330 enum {
14331 	TB_ENABLEBUTTON          = WM_USER + 1,
14332 	TB_CHECKBUTTON,
14333 	TB_PRESSBUTTON,
14334 	TB_HIDEBUTTON,
14335 	TB_INDETERMINATE, //     = WM_USER + 5,
14336 	TB_ISBUTTONENABLED       = WM_USER + 9,
14337 	TB_ISBUTTONCHECKED,
14338 	TB_ISBUTTONPRESSED,
14339 	TB_ISBUTTONHIDDEN,
14340 	TB_ISBUTTONINDETERMINATE, // = WM_USER + 13,
14341 	TB_SETSTATE              = WM_USER + 17,
14342 	TB_GETSTATE              = WM_USER + 18,
14343 	TB_ADDBITMAP             = WM_USER + 19,
14344 	TB_DELETEBUTTON          = WM_USER + 22,
14345 	TB_GETBUTTON,
14346 	TB_BUTTONCOUNT,
14347 	TB_COMMANDTOINDEX,
14348 	TB_SAVERESTOREA,
14349 	TB_CUSTOMIZE,
14350 	TB_ADDSTRINGA,
14351 	TB_GETITEMRECT,
14352 	TB_BUTTONSTRUCTSIZE,
14353 	TB_SETBUTTONSIZE,
14354 	TB_SETBITMAPSIZE,
14355 	TB_AUTOSIZE, //          = WM_USER + 33,
14356 	TB_GETTOOLTIPS           = WM_USER + 35,
14357 	TB_SETTOOLTIPS           = WM_USER + 36,
14358 	TB_SETPARENT             = WM_USER + 37,
14359 	TB_SETROWS               = WM_USER + 39,
14360 	TB_GETROWS,
14361 	TB_GETBITMAPFLAGS,
14362 	TB_SETCMDID,
14363 	TB_CHANGEBITMAP,
14364 	TB_GETBITMAP,
14365 	TB_GETBUTTONTEXTA,
14366 	TB_REPLACEBITMAP, //     = WM_USER + 46,
14367 	TB_GETBUTTONSIZE         = WM_USER + 58,
14368 	TB_SETBUTTONWIDTH        = WM_USER + 59,
14369 	TB_GETBUTTONTEXTW        = WM_USER + 75,
14370 	TB_SAVERESTOREW          = WM_USER + 76,
14371 	TB_ADDSTRINGW            = WM_USER + 77,
14372 }
14373 
14374 extern(Windows)
14375 BOOL EnumChildWindows(HWND, WNDENUMPROC, LPARAM);
14376 
14377 alias extern(Windows) BOOL function (HWND, LPARAM) WNDENUMPROC;
14378 
14379 
14380 	enum {
14381 		TB_SETINDENT = WM_USER + 47,
14382 		TB_SETIMAGELIST,
14383 		TB_GETIMAGELIST,
14384 		TB_LOADIMAGES,
14385 		TB_GETRECT,
14386 		TB_SETHOTIMAGELIST,
14387 		TB_GETHOTIMAGELIST,
14388 		TB_SETDISABLEDIMAGELIST,
14389 		TB_GETDISABLEDIMAGELIST,
14390 		TB_SETSTYLE,
14391 		TB_GETSTYLE,
14392 		//TB_GETBUTTONSIZE,
14393 		//TB_SETBUTTONWIDTH,
14394 		TB_SETMAXTEXTROWS,
14395 		TB_GETTEXTROWS // = WM_USER + 61
14396 	}
14397 
14398 enum {
14399 	CCM_FIRST            = 0x2000,
14400 	CCM_LAST             = CCM_FIRST + 0x200,
14401 	CCM_SETBKCOLOR       = 8193,
14402 	CCM_SETCOLORSCHEME   = 8194,
14403 	CCM_GETCOLORSCHEME   = 8195,
14404 	CCM_GETDROPTARGET    = 8196,
14405 	CCM_SETUNICODEFORMAT = 8197,
14406 	CCM_GETUNICODEFORMAT = 8198,
14407 	CCM_SETVERSION       = 0x2007,
14408 	CCM_GETVERSION       = 0x2008,
14409 	CCM_SETNOTIFYWINDOW  = 0x2009
14410 }
14411 
14412 
14413 enum {
14414 	PBM_SETRANGE     = WM_USER + 1,
14415 	PBM_SETPOS,
14416 	PBM_DELTAPOS,
14417 	PBM_SETSTEP,
14418 	PBM_STEPIT,   // = WM_USER + 5
14419 	PBM_SETRANGE32   = 1030,
14420 	PBM_GETRANGE,
14421 	PBM_GETPOS,
14422 	PBM_SETBARCOLOR, // = 1033
14423 	PBM_SETBKCOLOR   = CCM_SETBKCOLOR
14424 }
14425 
14426 enum {
14427 	PBS_SMOOTH   = 1,
14428 	PBS_VERTICAL = 4
14429 }
14430 
14431 enum {
14432         ICC_LISTVIEW_CLASSES = 1,
14433         ICC_TREEVIEW_CLASSES = 2,
14434         ICC_BAR_CLASSES      = 4,
14435         ICC_TAB_CLASSES      = 8,
14436         ICC_UPDOWN_CLASS     = 16,
14437         ICC_PROGRESS_CLASS   = 32,
14438         ICC_HOTKEY_CLASS     = 64,
14439         ICC_ANIMATE_CLASS    = 128,
14440         ICC_WIN95_CLASSES    = 255,
14441         ICC_DATE_CLASSES     = 256,
14442         ICC_USEREX_CLASSES   = 512,
14443         ICC_COOL_CLASSES     = 1024,
14444 	ICC_STANDARD_CLASSES = 0x00004000,
14445 }
14446 
14447 	enum WM_USER = 1024;
14448 }
14449 
14450 version(win32_widgets)
14451 	pragma(lib, "comdlg32");
14452 
14453 
14454 ///
14455 enum GenericIcons : ushort {
14456 	None, ///
14457 	// these happen to match the win32 std icons numerically if you just subtract one from the value
14458 	Cut, ///
14459 	Copy, ///
14460 	Paste, ///
14461 	Undo, ///
14462 	Redo, ///
14463 	Delete, ///
14464 	New, ///
14465 	Open, ///
14466 	Save, ///
14467 	PrintPreview, ///
14468 	Properties, ///
14469 	Help, ///
14470 	Find, ///
14471 	Replace, ///
14472 	Print, ///
14473 }
14474 
14475 enum FileDialogType {
14476 	Automatic,
14477 	Open,
14478 	Save
14479 }
14480 string previousFileReferenced;
14481 
14482 /++
14483 	Used in automatic menu functions to indicate that the user should be able to browse for a file.
14484 
14485 	Params:
14486 		storage = an alias to a `static string` variable that stores the last file referenced. It will
14487 		use this to pre-fill the dialog with a suggestion.
14488 
14489 		Please note that it MUST be `static` or you will get compile errors.
14490 
14491 		filters = the filters param to [getFileName]
14492 
14493 		type = the type if dialog to show. If `FileDialogType.Automatic`, it the driver code will
14494 		guess based on the function name. If it has the word "Save" or "Export" in it, it will show
14495 		a save dialog box. Otherwise, it will show an open dialog box.
14496 +/
14497 struct FileName(alias storage = previousFileReferenced, string[] filters = null, FileDialogType type = FileDialogType.Automatic) {
14498 	string name;
14499 	alias name this;
14500 }
14501 
14502 /++
14503 	Gets a file name for an open or save operation, calling your `onOK` function when the user has selected one. This function may or may not block depending on the operating system, you MUST assume it will complete asynchronously.
14504 
14505 	History:
14506 		onCancel was added November 6, 2021.
14507 
14508 		The dialog itself on Linux was modified on December 2, 2021 to include
14509 		a directory picker in addition to the command line completion view.
14510 
14511 		The `initialDirectory` argument was added November 9, 2022 (dub v10.10)
14512 	Future_directions:
14513 		I want to add some kind of custom preview and maybe thumbnail thing in the future,
14514 		at least on Linux, maybe on Windows too.
14515 +/
14516 void getOpenFileName(
14517 	void delegate(string) onOK,
14518 	string prefilledName = null,
14519 	string[] filters = null,
14520 	void delegate() onCancel = null,
14521 	string initialDirectory = null,
14522 )
14523 {
14524 	return getFileName(true, onOK, prefilledName, filters, onCancel, initialDirectory);
14525 }
14526 
14527 /// ditto
14528 void getSaveFileName(
14529 	void delegate(string) onOK,
14530 	string prefilledName = null,
14531 	string[] filters = null,
14532 	void delegate() onCancel = null,
14533 	string initialDirectory = null,
14534 )
14535 {
14536 	return getFileName(false, onOK, prefilledName, filters, onCancel, initialDirectory);
14537 }
14538 
14539 void getFileName(
14540 	bool openOrSave,
14541 	void delegate(string) onOK,
14542 	string prefilledName = null,
14543 	string[] filters = null, // format here is like ["Text files\0*.txt;*.text", "Image files\0*.png;*.jpg"]
14544 	void delegate() onCancel = null,
14545 	string initialDirectory = null,
14546 )
14547 {
14548 
14549 	version(win32_widgets) {
14550 		import core.sys.windows.commdlg;
14551 	/*
14552 	Ofn.lStructSize = sizeof(OPENFILENAME);
14553 	Ofn.hwndOwner = hWnd;
14554 	Ofn.lpstrFilter = szFilter;
14555 	Ofn.lpstrFile= szFile;
14556 	Ofn.nMaxFile = sizeof(szFile)/ sizeof(*szFile);
14557 	Ofn.lpstrFileTitle = szFileTitle;
14558 	Ofn.nMaxFileTitle = sizeof(szFileTitle);
14559 	Ofn.lpstrInitialDir = (LPSTR)NULL;
14560 	Ofn.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT;
14561 	Ofn.lpstrTitle = szTitle;
14562 	 */
14563 
14564 
14565 		wchar[1024] file = 0;
14566 		wchar[1024] filterBuffer = 0;
14567 		makeWindowsString(prefilledName, file[]);
14568 		OPENFILENAME ofn;
14569 		ofn.lStructSize = ofn.sizeof;
14570 		if(filters.length) {
14571 			string filter;
14572 			foreach(i, f; filters) {
14573 				filter ~= f;
14574 				filter ~= "\0";
14575 			}
14576 			filter ~= "\0";
14577 			ofn.lpstrFilter = makeWindowsString(filter, filterBuffer[], 0 /* already terminated */).ptr;
14578 		}
14579 		ofn.lpstrFile = file.ptr;
14580 		ofn.nMaxFile = file.length;
14581 
14582 		wchar[1024] initialDir = 0;
14583 		if(initialDirectory !is null) {
14584 			makeWindowsString(initialDirectory, initialDir[]);
14585 			ofn.lpstrInitialDir = file.ptr;
14586 		}
14587 
14588 		if(openOrSave ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn))
14589 		{
14590 			string okString = makeUtf8StringFromWindowsString(ofn.lpstrFile);
14591 			if(okString.length && okString[$-1] == '\0')
14592 				okString = okString[0..$-1];
14593 			onOK(okString);
14594 		} else {
14595 			if(onCancel)
14596 				onCancel();
14597 		}
14598 	} else version(custom_widgets) {
14599 		if(filters.length == 0)
14600 			filters = ["All Files\0*.*"];
14601 		auto picker = new FilePicker(prefilledName, filters, initialDirectory);
14602 		picker.onOK = onOK;
14603 		picker.onCancel = onCancel;
14604 		picker.show();
14605 	}
14606 }
14607 
14608 version(custom_widgets)
14609 private
14610 class FilePicker : Dialog {
14611 	void delegate(string) onOK;
14612 	void delegate() onCancel;
14613 	LineEdit lineEdit;
14614 
14615 	// returns common prefix
14616 	string loadFiles(string cwd, string[] filters...) {
14617 		string[] files;
14618 		string[] dirs;
14619 
14620 		string commonPrefix;
14621 
14622 		getFiles(cwd, (string name, bool isDirectory) {
14623 			if(name == ".")
14624 				return; // skip this as unnecessary
14625 			if(isDirectory)
14626 				dirs ~= name;
14627 			else {
14628 				foreach(filter; filters)
14629 				if(
14630 					filter.length <= 1 ||
14631 					filter == "*.*" ||
14632 					(filter[0] == '*' && name.endsWith(filter[1 .. $])) ||
14633 					(filter[$-1] == '*' && name.startsWith(filter[0 .. $ - 1]))
14634 				)
14635 				{
14636 					files ~= name;
14637 
14638 					if(filter.length > 0 && filter[$-1] == '*') {
14639 						if(commonPrefix is null) {
14640 							commonPrefix = name;
14641 						} else {
14642 							foreach(idx, char i; name) {
14643 								if(idx >= commonPrefix.length || i != commonPrefix[idx]) {
14644 									commonPrefix = commonPrefix[0 .. idx];
14645 									break;
14646 								}
14647 							}
14648 						}
14649 					}
14650 
14651 					break;
14652 				}
14653 			}
14654 		});
14655 
14656 		extern(C) static int comparator(scope const void* a, scope const void* b) {
14657 			auto sa = *cast(string*) a;
14658 			auto sb = *cast(string*) b;
14659 
14660 			for(int i = 0; i < sa.length; i++) {
14661 				if(i == sb.length)
14662 					return 1;
14663 				return sa[i] - sb[i];
14664 			}
14665 
14666 			return 0;
14667 		}
14668 
14669 		nonPhobosSort(files, &comparator);
14670 		nonPhobosSort(dirs, &comparator);
14671 
14672 		listWidget.clear();
14673 		dirWidget.clear();
14674 		foreach(name; dirs)
14675 			dirWidget.addOption(name);
14676 		foreach(name; files)
14677 			listWidget.addOption(name);
14678 
14679 		return commonPrefix;
14680 	}
14681 
14682 	ListWidget listWidget;
14683 	ListWidget dirWidget;
14684 
14685 	string currentDirectory;
14686 	string[] processedFilters;
14687 
14688 	//string[] filters = null, // format here is like ["Text files\0*.txt;*.text", "Image files\n*.png;*.jpg"]
14689 	this(string prefilledName, string[] filters, string initialDirectory, Window owner = null) {
14690 		super(300, 200, "Choose File..."); // owner);
14691 
14692 		foreach(filter; filters) {
14693 			while(filter.length && filter[0] != 0) {
14694 				filter = filter[1 .. $];
14695 			}
14696 			if(filter.length)
14697 				filter = filter[1 .. $]; // trim off the 0
14698 
14699 			while(filter.length) {
14700 				int idx = 0;
14701 				while(idx < filter.length && filter[idx] != ';') {
14702 					idx++;
14703 				}
14704 
14705 				processedFilters ~= filter[0 .. idx];
14706 				if(idx < filter.length)
14707 					idx++; // skip the ;
14708 				filter = filter[idx .. $];
14709 			}
14710 		}
14711 
14712 		currentDirectory = initialDirectory is null ? "." : initialDirectory;
14713 
14714 		{
14715 			auto hl = new HorizontalLayout(this);
14716 			dirWidget = new ListWidget(hl);
14717 			listWidget = new ListWidget(hl);
14718 
14719 			// double click events normally trigger something else but
14720 			// here user might be clicking kinda fast and we'd rather just
14721 			// keep it
14722 			dirWidget.addEventListener((scope DoubleClickEvent dev) {
14723 				auto ce = new ChangeEvent!void(dirWidget, () {});
14724 				ce.dispatch();
14725 			});
14726 
14727 			dirWidget.addEventListener((scope ChangeEvent!void sce) {
14728 				string v;
14729 				foreach(o; dirWidget.options)
14730 					if(o.selected) {
14731 						v = o.label;
14732 						break;
14733 					}
14734 				if(v.length) {
14735 					currentDirectory ~= "/" ~ v;
14736 					loadFiles(currentDirectory, processedFilters);
14737 				}
14738 			});
14739 
14740 			// double click here, on the other hand, selects the file
14741 			// and moves on
14742 			listWidget.addEventListener((scope DoubleClickEvent dev) {
14743 				OK();
14744 			});
14745 		}
14746 
14747 		lineEdit = new LineEdit(this);
14748 		lineEdit.focus();
14749 		lineEdit.addEventListener(delegate(CharEvent event) {
14750 			if(event.character == '\t' || event.character == '\n')
14751 				event.preventDefault();
14752 		});
14753 
14754 		listWidget.addEventListener(EventType.change, () {
14755 			foreach(o; listWidget.options)
14756 				if(o.selected)
14757 					lineEdit.content = o.label;
14758 		});
14759 
14760 		loadFiles(currentDirectory, processedFilters);
14761 
14762 		lineEdit.addEventListener((KeyDownEvent event) {
14763 			if(event.key == Key.Tab) {
14764 
14765 				auto current = lineEdit.content;
14766 				if(current.length >= 2 && current[0 ..2] == "./")
14767 					current = current[2 .. $];
14768 
14769 				auto commonPrefix = loadFiles(".", current ~ "*");
14770 
14771 				if(commonPrefix.length)
14772 					lineEdit.content = commonPrefix;
14773 
14774 				// FIXME: if that is a directory, add the slash? or even go inside?
14775 
14776 				event.preventDefault();
14777 			}
14778 		});
14779 
14780 		lineEdit.content = prefilledName;
14781 
14782 		auto hl = new HorizontalLayout(60, this);
14783 		auto cancelButton = new Button("Cancel", hl);
14784 		auto okButton = new Button("OK", hl);
14785 
14786 		cancelButton.addEventListener(EventType.triggered, &Cancel);
14787 		okButton.addEventListener(EventType.triggered, &OK);
14788 
14789 		this.addEventListener((KeyDownEvent event) {
14790 			if(event.key == Key.Enter || event.key == Key.PadEnter) {
14791 				event.preventDefault();
14792 				OK();
14793 			}
14794 			if(event.key == Key.Escape)
14795 				Cancel();
14796 		});
14797 
14798 	}
14799 
14800 	override void OK() {
14801 		if(lineEdit.content.length) {
14802 			string accepted;
14803 			auto c = lineEdit.content;
14804 			if(c.length && c[0] == '/')
14805 				accepted = c;
14806 			else
14807 				accepted = currentDirectory ~ "/" ~ lineEdit.content;
14808 
14809 			if(isDir(accepted)) {
14810 				// FIXME: would be kinda nice to support ~ and collapse these paths too
14811 				// FIXME: would also be nice to actually show the "Looking in..." directory and maybe the filters but later.
14812 				currentDirectory = accepted;
14813 				loadFiles(currentDirectory, processedFilters);
14814 				lineEdit.content = "";
14815 				return;
14816 			}
14817 
14818 			if(onOK)
14819 				onOK(accepted);
14820 		}
14821 		close();
14822 	}
14823 
14824 	override void Cancel() {
14825 		if(onCancel)
14826 			onCancel();
14827 		close();
14828 	}
14829 }
14830 
14831 private bool isDir(string name) {
14832 	version(Windows) {
14833 		auto ws = WCharzBuffer(name);
14834 		auto ret = GetFileAttributesW(ws.ptr);
14835 		if(ret == INVALID_FILE_ATTRIBUTES)
14836 			return false;
14837 		return (ret & FILE_ATTRIBUTE_DIRECTORY) != 0;
14838 	} else version(Posix) {
14839 		import core.sys.posix.sys.stat;
14840 		stat_t buf;
14841 		auto ret = stat((name ~ '\0').ptr, &buf);
14842 		if(ret == -1)
14843 			return false; // I could probably check more specific errors tbh
14844 		return (buf.st_mode & S_IFMT) == S_IFDIR;
14845 	} else return false;
14846 }
14847 
14848 /*
14849 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775947%28v=vs.85%29.aspx#check_boxes
14850 http://msdn.microsoft.com/en-us/library/windows/desktop/ms633574%28v=vs.85%29.aspx
14851 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775943%28v=vs.85%29.aspx
14852 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775951%28v=vs.85%29.aspx
14853 http://msdn.microsoft.com/en-us/library/windows/desktop/ms632680%28v=vs.85%29.aspx
14854 http://msdn.microsoft.com/en-us/library/windows/desktop/ms644996%28v=vs.85%29.aspx#message_box
14855 http://www.sbin.org/doc/Xlib/chapt_03.html
14856 
14857 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760433%28v=vs.85%29.aspx
14858 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760446%28v=vs.85%29.aspx
14859 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760443%28v=vs.85%29.aspx
14860 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760476%28v=vs.85%29.aspx
14861 */
14862 
14863 
14864 // These are all for setMenuAndToolbarFromAnnotatedCode
14865 /// This item in the menu will be preceded by a separator line
14866 /// Group: generating_from_code
14867 struct separator {}
14868 deprecated("It was misspelled, use separator instead") alias seperator = separator;
14869 /// Program-wide keyboard shortcut to trigger the action
14870 /// Group: generating_from_code
14871 struct accelerator { string keyString; }
14872 /// tells which menu the action will be on
14873 /// Group: generating_from_code
14874 struct menu { string name; }
14875 /// Describes which toolbar section the action appears on
14876 /// Group: generating_from_code
14877 struct toolbar { string groupName; }
14878 ///
14879 /// Group: generating_from_code
14880 struct icon { ushort id; }
14881 ///
14882 /// Group: generating_from_code
14883 struct label { string label; }
14884 ///
14885 /// Group: generating_from_code
14886 struct hotkey { dchar ch; }
14887 ///
14888 /// Group: generating_from_code
14889 struct tip { string tip; }
14890 
14891 
14892 /++
14893 	Observes and allows inspection of an object via automatic gui
14894 +/
14895 /// Group: generating_from_code
14896 ObjectInspectionWindow objectInspectionWindow(T)(T t) if(is(T == class)) {
14897 	return new ObjectInspectionWindowImpl!(T)(t);
14898 }
14899 
14900 class ObjectInspectionWindow : Window {
14901 	this(int a, int b, string c) {
14902 		super(a, b, c);
14903 	}
14904 
14905 	abstract void readUpdatesFromObject();
14906 }
14907 
14908 class ObjectInspectionWindowImpl(T) : ObjectInspectionWindow {
14909 	T t;
14910 	this(T t) {
14911 		this.t = t;
14912 
14913 		super(300, 400, "ObjectInspectionWindow - " ~ T.stringof);
14914 
14915 		foreach(memberName; __traits(derivedMembers, T)) {{
14916 			alias member = I!(__traits(getMember, t, memberName))[0];
14917 			alias type = typeof(member);
14918 			static if(is(type == int)) {
14919 				auto le = new LabeledLineEdit(memberName ~ ": ", this);
14920 				//le.addEventListener("char", (Event ev) {
14921 					//if((ev.character < '0' || ev.character > '9') && ev.character != '-')
14922 						//ev.preventDefault();
14923 				//});
14924 				le.addEventListener(EventType.change, (Event ev) {
14925 					__traits(getMember, t, memberName) = cast(type) stringToLong(ev.stringValue);
14926 				});
14927 
14928 				updateMemberDelegates[memberName] = () {
14929 					le.content = toInternal!string(__traits(getMember, t, memberName));
14930 				};
14931 			}
14932 		}}
14933 	}
14934 
14935 	void delegate()[string] updateMemberDelegates;
14936 
14937 	override void readUpdatesFromObject() {
14938 		foreach(k, v; updateMemberDelegates)
14939 			v();
14940 	}
14941 }
14942 
14943 /++
14944 	Creates a dialog based on a data structure.
14945 
14946 	---
14947 	dialog((YourStructure value) {
14948 		// the user filled in the struct and clicked OK,
14949 		// you can check the members now
14950 	});
14951 	---
14952 
14953 	Params:
14954 		initialData = the initial value to show in the dialog. It will not modify this unless
14955 		it is a class then it might, no promises.
14956 
14957 	History:
14958 		The overload that lets you specify `initialData` was added on December 30, 2021 (dub v10.5)
14959 +/
14960 /// Group: generating_from_code
14961 void dialog(T)(void delegate(T) onOK, void delegate() onCancel = null, string title = T.stringof) {
14962 	dialog(T.init, onOK, onCancel, title);
14963 }
14964 /// ditto
14965 void dialog(T)(T initialData, void delegate(T) onOK, void delegate() onCancel = null, string title = T.stringof) {
14966 	auto dg = new AutomaticDialog!T(initialData, onOK, onCancel, title);
14967 	dg.show();
14968 }
14969 
14970 private static template I(T...) { alias I = T; }
14971 
14972 
14973 private string beautify(string name, char space = ' ', bool allLowerCase = false) {
14974 	if(name == "id")
14975 		return allLowerCase ? name : "ID";
14976 
14977 	char[160] buffer;
14978 	int bufferIndex = 0;
14979 	bool shouldCap = true;
14980 	bool shouldSpace;
14981 	bool lastWasCap;
14982 	foreach(idx, char ch; name) {
14983 		if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
14984 
14985 		if((ch >= 'A' && ch <= 'Z') || ch == '_') {
14986 			if(lastWasCap) {
14987 				// two caps in a row, don't change. Prolly acronym.
14988 			} else {
14989 				if(idx)
14990 					shouldSpace = true; // new word, add space
14991 			}
14992 
14993 			lastWasCap = true;
14994 		} else {
14995 			lastWasCap = false;
14996 		}
14997 
14998 		if(shouldSpace) {
14999 			buffer[bufferIndex++] = space;
15000 			if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
15001 			shouldSpace = false;
15002 		}
15003 		if(shouldCap) {
15004 			if(ch >= 'a' && ch <= 'z')
15005 				ch -= 32;
15006 			shouldCap = false;
15007 		}
15008 		if(allLowerCase && ch >= 'A' && ch <= 'Z')
15009 			ch += 32;
15010 		buffer[bufferIndex++] = ch;
15011 	}
15012 	return buffer[0 .. bufferIndex].idup;
15013 }
15014 
15015 /++
15016 	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.
15017 +/
15018 class AutomaticDialog(T) : Dialog {
15019 	T t;
15020 
15021 	void delegate(T) onOK;
15022 	void delegate() onCancel;
15023 
15024 	override int paddingTop() { return defaultLineHeight; }
15025 	override int paddingBottom() { return defaultLineHeight; }
15026 	override int paddingRight() { return defaultLineHeight; }
15027 	override int paddingLeft() { return defaultLineHeight; }
15028 
15029 	this(T initialData, void delegate(T) onOK, void delegate() onCancel, string title) {
15030 		assert(onOK !is null);
15031 
15032 		t = initialData;
15033 
15034 		static if(is(T == class)) {
15035 			if(t is null)
15036 				t = new T();
15037 		}
15038 		this.onOK = onOK;
15039 		this.onCancel = onCancel;
15040 		super(400, cast(int)(__traits(allMembers, T).length * 2) * (defaultLineHeight + 4 + 2) + Window.lineHeight + 56, title);
15041 
15042 		static if(is(T == class))
15043 			this.addDataControllerWidget(t);
15044 		else
15045 			this.addDataControllerWidget(&t);
15046 
15047 		auto hl = new HorizontalLayout(this);
15048 		auto stretch = new HorizontalSpacer(hl); // to right align
15049 		auto ok = new CommandButton("OK", hl);
15050 		auto cancel = new CommandButton("Cancel", hl);
15051 		ok.addEventListener(EventType.triggered, &OK);
15052 		cancel.addEventListener(EventType.triggered, &Cancel);
15053 
15054 		this.addEventListener((KeyDownEvent ev) {
15055 			if(ev.key == Key.Enter || ev.key == Key.PadEnter) {
15056 				ok.focus();
15057 				OK();
15058 				ev.preventDefault();
15059 			}
15060 			if(ev.key == Key.Escape) {
15061 				Cancel();
15062 				ev.preventDefault();
15063 			}
15064 		});
15065 
15066 		this.addEventListener((scope ClosedEvent ce) {
15067 			if(onCancel)
15068 				onCancel();
15069 		});
15070 
15071 		//this.children[0].focus();
15072 	}
15073 
15074 	override void OK() {
15075 		onOK(t);
15076 		close();
15077 	}
15078 
15079 	override void Cancel() {
15080 		if(onCancel)
15081 			onCancel();
15082 		close();
15083 	}
15084 }
15085 
15086 private template baseClassCount(Class) {
15087 	private int helper() {
15088 		int count = 0;
15089 		static if(is(Class bases == super)) {
15090 			foreach(base; bases)
15091 				static if(is(base == class))
15092 					count += 1 + baseClassCount!base;
15093 		}
15094 		return count;
15095 	}
15096 
15097 	enum int baseClassCount = helper();
15098 }
15099 
15100 private long stringToLong(string s) {
15101 	long ret;
15102 	if(s.length == 0)
15103 		return ret;
15104 	bool negative = s[0] == '-';
15105 	if(negative)
15106 		s = s[1 .. $];
15107 	foreach(ch; s) {
15108 		if(ch >= '0' && ch <= '9') {
15109 			ret *= 10;
15110 			ret += ch - '0';
15111 		}
15112 	}
15113 	if(negative)
15114 		ret = -ret;
15115 	return ret;
15116 }
15117 
15118 
15119 interface ReflectableProperties {
15120 	/++
15121 		Iterates the event's properties as strings. Note that keys may be repeated and a get property request may
15122 		call your sink with `null`. It it does, it means the key either doesn't request or cannot be represented by
15123 		json in the current implementation.
15124 
15125 		This is auto-implemented for you if you mixin [RegisterGetters] in your child classes and only have
15126 		properties of type `bool`, `int`, `double`, or `string`. For other ones, you will need to do it yourself
15127 		as of the June 2, 2021 release.
15128 
15129 		History:
15130 			Added June 2, 2021.
15131 
15132 		See_Also: [getPropertyAsString], [setPropertyFromString]
15133 	+/
15134 	void getPropertiesList(scope void delegate(string name) sink) const;// @nogc pure nothrow;
15135 	/++
15136 		Requests a property to be delivered to you as a string, through your `sink` delegate.
15137 
15138 		If the `value` is null, it means the property could not be retreived. If `valueIsJson`, it should
15139 		be interpreted as json, otherwise, it is just a plain string.
15140 
15141 		The sink should always be called exactly once for each call (it is basically a return value, but it might
15142 		use a local buffer it maintains instead of allocating a return value).
15143 
15144 		History:
15145 			Added June 2, 2021.
15146 
15147 		See_Also: [getPropertiesList], [setPropertyFromString]
15148 	+/
15149 	void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink);
15150 	/++
15151 		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.
15152 
15153 		History:
15154 			Added June 2, 2021.
15155 
15156 		See_Also: [getPropertiesList], [getPropertyAsString], [SetPropertyResult]
15157 	+/
15158 	SetPropertyResult setPropertyFromString(string name, scope const(char)[] str, bool strIsJson);
15159 
15160 	/// [setPropertyFromString] possible return values
15161 	enum SetPropertyResult {
15162 		success = 0, /// the property has been successfully set to the request value
15163 		notPermitted = -1, /// the property exists but it cannot be changed at this time
15164 		notImplemented = -2, /// the set function is not implemented for the given property (which may or may not exist)
15165 		noSuchProperty = -3, /// there is no property by that name
15166 		wrongFormat = -4, /// the string was given in the wrong format, e.g. passing "two" for an int value
15167 		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)
15168 	}
15169 
15170 	/++
15171 		You can mix this in to get an implementation in child classes. This does [setPropertyFromString].
15172 
15173 		Your original base class, however, must implement its own methods. I recommend doing the initial ones by hand.
15174 
15175 		For [Widget] and [Event], the library provides [Widget.Register] and [Event.Register] that call these for you, so you should
15176 		rarely need to use these building blocks directly.
15177 	+/
15178 	mixin template RegisterSetters() {
15179 		override SetPropertyResult setPropertyFromString(string name, scope const(char)[] value, bool valueIsJson) {
15180 			switch(name) {
15181 				foreach(memberName; __traits(derivedMembers, typeof(this))) {
15182 					case memberName:
15183 						static if(is(typeof(__traits(getMember, this, memberName)) : const bool)) {
15184 							if(value != "true" && value != "false")
15185 								return SetPropertyResult.wrongFormat;
15186 							__traits(getMember, this, memberName) = value == "true" ? true : false;
15187 							return SetPropertyResult.success;
15188 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const long)) {
15189 							import core.stdc.stdlib;
15190 							char[128] zero = 0;
15191 							if(buffer.length + 1 >= zero.length)
15192 								return SetPropertyResult.wrongFormat;
15193 							zero[0 .. buffer.length] = buffer[];
15194 							__traits(getMember, this, memberName) = strtol(buffer.ptr, null, 10);
15195 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const double)) {
15196 							import core.stdc.stdlib;
15197 							char[128] zero = 0;
15198 							if(buffer.length + 1 >= zero.length)
15199 								return SetPropertyResult.wrongFormat;
15200 							zero[0 .. buffer.length] = buffer[];
15201 							__traits(getMember, this, memberName) = strtod(buffer.ptr, null, 10);
15202 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const string)) {
15203 							__traits(getMember, this, memberName) = value.idup;
15204 						} else {
15205 							return SetPropertyResult.notImplemented;
15206 						}
15207 
15208 				}
15209 				default:
15210 					return super.setPropertyFromString(name, value, valueIsJson);
15211 			}
15212 		}
15213 	}
15214 
15215 	/++
15216 		You can mix this in to get an implementation in child classes. This does [getPropertyAsString] and [getPropertiesList].
15217 
15218 		Your original base class, however, must implement its own methods. I recommend doing the initial ones by hand.
15219 
15220 		For [Widget] and [Event], the library provides [Widget.Register] and [Event.Register] that call these for you, so you should
15221 		rarely need to use these building blocks directly.
15222 	+/
15223 	mixin template RegisterGetters() {
15224 		override void getPropertiesList(scope void delegate(string name) sink) const {
15225 			super.getPropertiesList(sink);
15226 
15227 			foreach(memberName; __traits(derivedMembers, typeof(this))) {
15228 				sink(memberName);
15229 			}
15230 		}
15231 		override void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink) {
15232 			switch(name) {
15233 				foreach(memberName; __traits(derivedMembers, typeof(this))) {
15234 					case memberName:
15235 						static if(is(typeof(__traits(getMember, this, memberName)) : const bool)) {
15236 							sink(name, __traits(getMember, this, memberName) ? "true" : "false", true);
15237 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const long)) {
15238 							import core.stdc.stdio;
15239 							char[32] buffer;
15240 							auto len = snprintf(buffer.ptr, buffer.length, "%lld", cast(long) __traits(getMember, this, memberName));
15241 							sink(name, buffer[0 .. len], true);
15242 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const double)) {
15243 							import core.stdc.stdio;
15244 							char[32] buffer;
15245 							auto len = snprintf(buffer.ptr, buffer.length, "%f", cast(double) __traits(getMember, this, memberName));
15246 							sink(name, buffer[0 .. len], true);
15247 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const string)) {
15248 							sink(name, __traits(getMember, this, memberName), false);
15249 							//sinkJsonString(memberName, __traits(getMember, this, memberName), sink);
15250 						} else {
15251 							sink(name, null, true);
15252 						}
15253 
15254 					return;
15255 				}
15256 				default:
15257 					return super.getPropertyAsString(name, sink);
15258 			}
15259 		}
15260 	}
15261 }
15262 
15263 private struct Stack(T) {
15264 	this(int maxSize) {
15265 		internalLength = 0;
15266 		arr = initialBuffer[];
15267 	}
15268 
15269 	///.
15270 	void push(T t) {
15271 		if(internalLength >= arr.length) {
15272 			auto oldarr = arr;
15273 			if(arr.length < 4096)
15274 				arr = new T[arr.length * 2];
15275 			else
15276 				arr = new T[arr.length + 4096];
15277 			arr[0 .. oldarr.length] = oldarr[];
15278 		}
15279 
15280 		arr[internalLength] = t;
15281 		internalLength++;
15282 	}
15283 
15284 	///.
15285 	T pop() {
15286 		assert(internalLength);
15287 		internalLength--;
15288 		return arr[internalLength];
15289 	}
15290 
15291 	///.
15292 	T peek() {
15293 		assert(internalLength);
15294 		return arr[internalLength - 1];
15295 	}
15296 
15297 	///.
15298 	@property bool empty() {
15299 		return internalLength ? false : true;
15300 	}
15301 
15302 	///.
15303 	private T[] arr;
15304 	private size_t internalLength;
15305 	private T[64] initialBuffer;
15306 	// 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),
15307 	// using this saves us a bunch of trips to the GC. In my last profiling, I got about a 50x improvement in the push()
15308 	// function thanks to this, and push() was actually one of the slowest individual functions in the code!
15309 }
15310 
15311 /// 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.
15312 private struct WidgetStream {
15313 
15314 	///.
15315 	@property Widget front() {
15316 		return current.widget;
15317 	}
15318 
15319 	/// Use Widget.tree instead.
15320 	this(Widget start) {
15321 		current.widget = start;
15322 		current.childPosition = -1;
15323 		isEmpty = false;
15324 		stack = typeof(stack)(0);
15325 	}
15326 
15327 	/*
15328 		Handle it
15329 		handle its children
15330 
15331 	*/
15332 
15333 	///.
15334 	void popFront() {
15335 	    more:
15336 	    	if(isEmpty) return;
15337 
15338 		// FIXME: the profiler says this function is somewhat slow (noticeable because it can be called a lot of times)
15339 
15340 		current.childPosition++;
15341 		if(current.childPosition >= current.widget.children.length) {
15342 			if(stack.empty())
15343 				isEmpty = true;
15344 			else {
15345 				current = stack.pop();
15346 				goto more;
15347 			}
15348 		} else {
15349 			stack.push(current);
15350 			current.widget = current.widget.children[current.childPosition];
15351 			current.childPosition = -1;
15352 		}
15353 	}
15354 
15355 	///.
15356 	@property bool empty() {
15357 		return isEmpty;
15358 	}
15359 
15360 	private:
15361 
15362 	struct Current {
15363 		Widget widget;
15364 		int childPosition;
15365 	}
15366 
15367 	Current current;
15368 
15369 	Stack!(Current) stack;
15370 
15371 	bool isEmpty;
15372 }
15373 
15374 
15375 /+
15376 
15377 	I could fix up the hierarchy kinda like this
15378 
15379 	class Widget {
15380 		Widget[] children() { return null; }
15381 	}
15382 	interface WidgetContainer {
15383 		Widget asWidget();
15384 		void addChild(Widget w);
15385 
15386 		// alias asWidget this; // but meh
15387 	}
15388 
15389 	Widget can keep a (Widget parent) ctor, but it should prolly deprecate and tell people to instead change their ctors to take WidgetContainer instead.
15390 
15391 	class Layout : Widget, WidgetContainer {}
15392 
15393 	class Window : WidgetContainer {}
15394 
15395 
15396 	All constructors that previously took Widgets should now take WidgetContainers instead
15397 
15398 
15399 
15400 	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".
15401 +/
15402 
15403 /+
15404 	LAYOUTS 2.0
15405 
15406 	can just be assigned as a function. assigning a new one will cause it to be immediately called.
15407 
15408 	they simply are responsible for the recomputeChildLayout. If this pointer is null, it uses the default virtual one.
15409 
15410 	recomputeChildLayout only really needs a property accessor proxy... just the layout info too.
15411 
15412 	and even Paint can just use computedStyle...
15413 
15414 		background color
15415 		font
15416 		border color and style
15417 
15418 	And actually the style proxy can offer some helper routines to draw these like the draw 3d box
15419 		please note that many widgets and in some modes will completely ignore properties as they will.
15420 		they are just hints you set, not promises.
15421 
15422 
15423 
15424 
15425 
15426 	So generally the existing virtual functions are just the default for the class. But individual objects
15427 	or stylesheets can override this. The virtual ones count as tag-level specificity in css.
15428 +/
15429 
15430 /++
15431 	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.
15432 
15433 	History:
15434 		Added May 24, 2021.
15435 +/
15436 struct WidgetBackground {
15437 	/++
15438 		A background with the given solid color.
15439 	+/
15440 	this(Color color) {
15441 		this.color = color;
15442 	}
15443 
15444 	this(WidgetBackground bg) {
15445 		this = bg;
15446 	}
15447 
15448 	/++
15449 		Creates a widget from the string.
15450 
15451 		Currently, it only supports solid colors via [Color.fromString], but it will likely be expanded in the future to something more like css.
15452 	+/
15453 	static WidgetBackground fromString(string s) {
15454 		return WidgetBackground(Color.fromString(s));
15455 	}
15456 
15457 	/++
15458 		The background is not necessarily a solid color, but you can always specify a color as a fallback.
15459 
15460 		History:
15461 			Made `public` on December 18, 2022 (dub v10.10).
15462 	+/
15463 	Color color;
15464 }
15465 
15466 /++
15467 	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!)
15468 
15469 	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.
15470 
15471 	You should not inherit from this directly, but instead use [VisualTheme].
15472 
15473 	History:
15474 		Added May 8, 2021
15475 +/
15476 abstract class BaseVisualTheme {
15477 	/// Don't implement this, instead use [VisualTheme] and implement `paint` methods on specific subclasses you want to override.
15478 	abstract void doPaint(Widget widget, WidgetPainter painter);
15479 
15480 	/+
15481 	/// Don't implement this, instead use [VisualTheme] and implement `StyleOverride` aliases on specific subclasses you want to override.
15482 	abstract void useStyleProperties(Widget w, scope void delegate(scope Widget.Style props) dg);
15483 	+/
15484 
15485 	/++
15486 		Returns the property as a string, or null if it was not overridden in the style definition. The idea here is something like css,
15487 		where the interpretation of the string varies for each property and may include things like measurement units.
15488 	+/
15489 	abstract string getPropertyString(Widget widget, string propertyName);
15490 
15491 	/++
15492 		Default background color of the window. Widgets also use this to simulate transparency.
15493 
15494 		Probably some shade of grey.
15495 	+/
15496 	abstract Color windowBackgroundColor();
15497 	abstract Color widgetBackgroundColor();
15498 	abstract Color foregroundColor();
15499 	abstract Color lightAccentColor();
15500 	abstract Color darkAccentColor();
15501 
15502 	/++
15503 		Colors used to indicate active selections in lists and text boxes, etc.
15504 	+/
15505 	abstract Color selectionForegroundColor();
15506 	/// ditto
15507 	abstract Color selectionBackgroundColor();
15508 
15509 	deprecated("Use selectionForegroundColor and selectionBackgroundColor instead") Color selectionColor() { return selectionBackgroundColor(); }
15510 
15511 	abstract OperatingSystemFont defaultFont();
15512 
15513 	private OperatingSystemFont defaultFontCache_;
15514 	private bool defaultFontCachePopulated;
15515 	private OperatingSystemFont defaultFontCached() {
15516 		if(!defaultFontCachePopulated) {
15517 			// FIXME: set this to false if X disconnect or if visual theme changes
15518 			defaultFontCache_ = defaultFont();
15519 			defaultFontCachePopulated = true;
15520 		}
15521 		return defaultFontCache_;
15522 	}
15523 }
15524 
15525 /+
15526 	A widget should have:
15527 		classList
15528 		dataset
15529 		attributes
15530 		computedStyles
15531 		state (persistent)
15532 		dynamic state (focused, hover, etc)
15533 +/
15534 
15535 // visualTheme.computedStyle(this).paddingLeft
15536 
15537 
15538 /++
15539 	This is your entry point to create your own visual theme for custom widgets.
15540 +/
15541 abstract class VisualTheme(CRTP) : BaseVisualTheme {
15542 	override string getPropertyString(Widget widget, string propertyName) {
15543 		return null;
15544 	}
15545 
15546 	/+
15547 		mixin StyleOverride!Widget
15548 	final override void useStyleProperties(Widget w, scope void delegate(scope Widget.Style props) dg) {
15549 		w.useStyleProperties(dg);
15550 	}
15551 	+/
15552 
15553 	final override void doPaint(Widget widget, WidgetPainter painter) {
15554 		auto derived = cast(CRTP) cast(void*) this;
15555 
15556 		scope void delegate(Widget, WidgetPainter) bestMatch;
15557 		int bestMatchScore;
15558 
15559 		static if(__traits(hasMember, CRTP, "paint"))
15560 		foreach(overload; __traits(getOverloads, CRTP, "paint")) {
15561 			static if(is(typeof(overload) Params == __parameters)) {
15562 				static assert(Params.length == 2);
15563 				static assert(is(Params[0] : Widget));
15564 				static assert(is(Params[1] == WidgetPainter));
15565 				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);
15566 
15567 				alias type = Params[0];
15568 				if(cast(type) widget) {
15569 					auto score = baseClassCount!type;
15570 
15571 					if(score > bestMatchScore) {
15572 						bestMatch = cast(typeof(bestMatch)) &__traits(child, derived, overload);
15573 						bestMatchScore = score;
15574 					}
15575 				}
15576 			} else static assert(0, "paint should be a method.");
15577 		}
15578 
15579 		if(bestMatch)
15580 			bestMatch(widget, painter);
15581 		else
15582 			widget.paint(painter);
15583 	}
15584 
15585 	// 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
15586 	override Color windowBackgroundColor() { return Color(212, 212, 212); }
15587 	override Color widgetBackgroundColor() { return Color.white; }
15588 	override Color foregroundColor() { return Color.black; }
15589 	override Color darkAccentColor() { return Color(172, 172, 172); }
15590 	override Color lightAccentColor() { return Color(223, 223, 223); }
15591 	override Color selectionForegroundColor() { return Color.white; }
15592 	override Color selectionBackgroundColor() { return Color(0, 0, 128); }
15593 	override OperatingSystemFont defaultFont() { return null; } // will just use the default out of simpledisplay's xfontstr
15594 
15595 	private static struct Cached {
15596 		// i prolly want to do this
15597 	}
15598 }
15599 
15600 final class DefaultVisualTheme : VisualTheme!DefaultVisualTheme {
15601 	/+
15602 	OperatingSystemFont defaultFont() { return new OperatingSystemFont("Times New Roman", 8, FontWeight.medium); }
15603 	Color windowBackgroundColor() { return Color(242, 242, 242); }
15604 	Color darkAccentColor() { return windowBackgroundColor; }
15605 	Color lightAccentColor() { return windowBackgroundColor; }
15606 	+/
15607 }
15608 
15609 /++
15610 	Event fired when an [Observeable] variable changes. You will want to add an event listener referencing
15611 	the field like `widget.addEventListener((scope StateChanged!(Whatever.field) ev) { });`
15612 
15613 	History:
15614 		Moved from minigui_addons.webview to main minigui on November 27, 2021 (dub v10.4)
15615 +/
15616 class StateChanged(alias field) : Event {
15617 	enum EventString = __traits(identifier, __traits(parent, field)) ~ "." ~ __traits(identifier, field) ~ ":change";
15618 	override bool cancelable() const { return false; }
15619 	this(Widget target, typeof(field) newValue) {
15620 		this.newValue = newValue;
15621 		super(EventString, target);
15622 	}
15623 
15624 	typeof(field) newValue;
15625 }
15626 
15627 /++
15628 	Convenience function to add a `triggered` event listener.
15629 
15630 	Its implementation is simply `w.addEventListener("triggered", dg);`
15631 
15632 	History:
15633 		Added November 27, 2021 (dub v10.4)
15634 +/
15635 void addWhenTriggered(Widget w, void delegate() dg) {
15636 	w.addEventListener("triggered", dg);
15637 }
15638 
15639 /++
15640 	Observable varables can be added to widgets and when they are changed, it fires
15641 	off a [StateChanged] event so you can react to it.
15642 
15643 	It is implemented as a getter and setter property, along with another helper you
15644 	can use to subscribe whith is `name_changed`. You can also subscribe to the [StateChanged]
15645 	event through the usual means. Just give the name of the variable. See [StateChanged] for an
15646 	example.
15647 
15648 	History:
15649 		Moved from minigui_addons.webview to main minigui on November 27, 2021 (dub v10.4)
15650 +/
15651 mixin template Observable(T, string name) {
15652 	private T backing;
15653 
15654 	mixin(q{
15655 		void } ~ name ~ q{_changed (void delegate(T) dg) {
15656 			this.addEventListener((StateChanged!this_thing ev) {
15657 				dg(ev.newValue);
15658 			});
15659 		}
15660 
15661 		@property T } ~ name ~ q{ () {
15662 			return backing;
15663 		}
15664 
15665 		@property void } ~ name ~ q{ (T t) {
15666 			backing = t;
15667 			auto event = new StateChanged!this_thing(this, t);
15668 			event.dispatch();
15669 		}
15670 	});
15671 
15672 	mixin("private alias this_thing = " ~ name ~ ";");
15673 }
15674 
15675 
15676 private bool startsWith(string test, string thing) {
15677 	if(test.length < thing.length)
15678 		return false;
15679 	return test[0 .. thing.length] == thing;
15680 }
15681 
15682 private bool endsWith(string test, string thing) {
15683 	if(test.length < thing.length)
15684 		return false;
15685 	return test[$ - thing.length .. $] == thing;
15686 }
15687 
15688 // still do layout delegation
15689 // and... split off Window from Widget.
15690 
15691 version(minigui_screenshots)
15692 struct Screenshot {
15693 	string name;
15694 }
15695 
15696 version(minigui_screenshots)
15697 static if(__VERSION__ > 2092)
15698 mixin(q{
15699 shared static this() {
15700 	import core.runtime;
15701 
15702 	static UnitTestResult screenshotMagic() {
15703 		string name;
15704 
15705 		import arsd.png;
15706 
15707 		auto results = new Window();
15708 		auto button = new Button("do it", results);
15709 
15710 		Window.newWindowCreated = delegate(Window w) {
15711 			Timer timer;
15712 			timer = new Timer(250, {
15713 				auto img = w.win.takeScreenshot();
15714 				timer.destroy();
15715 
15716 				version(Windows)
15717 					writePng("/var/www/htdocs/minigui-screenshots/windows/" ~ name ~ ".png", img);
15718 				else
15719 					writePng("/var/www/htdocs/minigui-screenshots/linux/" ~ name ~ ".png", img);
15720 
15721 				w.close();
15722 			});
15723 		};
15724 
15725 		button.addWhenTriggered( {
15726 
15727 		foreach(test; __traits(getUnitTests, mixin(__MODULE__))) {
15728 			name = null;
15729 			static foreach(attr; __traits(getAttributes, test)) {
15730 				static if(is(typeof(attr) == Screenshot))
15731 					name = attr.name;
15732 			}
15733 			if(name.length) {
15734 				test();
15735 			}
15736 		}
15737 
15738 		});
15739 
15740 		results.loop();
15741 
15742 		return UnitTestResult(0, 0, false, false);
15743 	}
15744 
15745 
15746 	Runtime.extendedModuleUnitTester = &screenshotMagic;
15747 }
15748 });
15749 version(minigui_screenshots) {
15750 	version(unittest)
15751 		void main() {}
15752 	else static assert(0, "dont forget the -unittest flag to dmd");
15753 }
15754 
15755 // FIXME: i called hotkey accelerator in some places. hotkey = key when menu is active like E&xit. accelerator = global shortcut.
15756 // FIXME: make multiple accelerators disambiguate based ona rgs
15757 // FIXME: MainWindow ctor should have same arg order as Window
15758 // FIXME: mainwindow ctor w/ client area size instead of total size.
15759 // 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.
15760 // FIXME: tri-state checkbox
15761 // FIXME: subordinate controls grouping...