1 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb775498%28v=vs.85%29.aspx
2 
3 // for responsive design, a collapsible widget that if it doesn't have enough room, it just automatically becomes a "more" button or whatever.
4 
5 // responsive minigui, menu search, and file open with a preview hook on the side.
6 
7 // FIXME: add menu checkbox and menu icon eventually
8 
9 /*
10 
11 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
12 
13 the virtual functions remain as the default calculated values. then the reads go through some proxy object that can override it...
14 */
15 
16 // FIXME: text label must be copyable to the clipboard, at least as a full chunk.
17 
18 // FIXME: opt-in file picker widget with image support
19 
20 // FIXME: number widget
21 
22 // https://www.codeguru.com/cpp/controls/buttonctrl/advancedbuttons/article.php/c5161/Native-Win32-ThemeAware-OwnerDraw-Controls-No-MFC.htm
23 // https://docs.microsoft.com/en-us/windows/win32/controls/using-visual-styles
24 
25 // osx style menu search.
26 
27 // would be cool for a scroll bar to have marking capabilities
28 // kinda like vim's marks just on clicks etc and visual representation
29 // generically. may be cool to add an up arrow to the bottom too
30 //
31 // leave a shadow of where you last were for going back easily
32 
33 // So a window needs to have a selection, and that can be represented by a type. This is manipulated by various
34 // functions like cut, copy, paste. Widgets can have a selection and that would assert teh selection ownership for
35 // the window.
36 
37 // so what about context menus?
38 
39 // https://docs.microsoft.com/en-us/windows/desktop/Controls/about-custom-draw
40 
41 // FIXME: make the scroll thing go to bottom when the content changes.
42 
43 // 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
44 
45 // FIXME: the scroll area MUST be fixed to use the proper apis under the hood.
46 
47 
48 // FIXME: add a command search thingy built in and implement tip.
49 // FIXME: omg omg what if menu functions have arguments and it can pop up a gui or command line script them?!
50 
51 // On Windows:
52 // FIXME: various labels look broken in high contrast mode
53 // FIXME: changing themes while the program is upen doesn't trigger a redraw
54 
55 // add note about manifest to documentation. also icons.
56 
57 // a pager control is just a horizontal scroll area just with arrows on the sides instead of a scroll bar
58 // FIXME: clear the corner of scrollbars if they pop up
59 
60 // minigui needs to have a stdout redirection for gui mode on windows writeln
61 
62 // I kinda wanna do state reacting. sort of. idk tho
63 
64 // need a viewer widget that works like a web page - arrows scroll down consistently
65 
66 // I want a nanovega widget, and a svg widget with some kind of event handlers attached to the inside.
67 
68 // FIXME: the menus should be a bit more discoverable, at least a single click to open the others instead of two.
69 // and help info about menu items.
70 // and search in menus?
71 
72 // FIXME: a scroll area event signaling when a thing comes into view might be good
73 // FIXME: arrow key navigation and accelerators in dialog boxes will be a must
74 
75 // FIXME: unify Windows style line endings
76 
77 /*
78 	TODO:
79 
80 	pie menu
81 
82 	class Form with submit behavior -- see AutomaticDialog
83 
84 	disabled widgets and menu items
85 
86 	event cleanup
87 	tooltips.
88 	api improvements
89 
90 	margins are kinda broken, they don't collapse like they should. at least.
91 
92 	a table form btw would be a horizontal layout of vertical layouts holding each column
93 	that would give the same width things
94 */
95 
96 /*
97 
98 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
99 */
100 
101 /++
102 	minigui is a smallish GUI widget library, aiming to be on par with at least
103 	HTML4 forms and a few other expected gui components. It uses native controls
104 	on Windows and does its own thing on Linux (Mac is not currently supported but
105 	may be later, and should use native controls) to keep size down. The Linux
106 	appearance is similar to Windows 95 and avoids using images to maintain network
107 	efficiency on remote X connections, though you can customize that.
108 
109 
110 	minigui's only required dependencies are [arsd.simpledisplay] and [arsd.color],
111 	on which it is built. simpledisplay provides the low-level interfaces and minigui
112 	builds the concept of widgets inside the windows on top of it.
113 
114 	Its #1 goal is to be useful without being large and complicated like GTK and Qt.
115 	It isn't hugely concerned with appearance - on Windows, it just uses the native
116 	controls and native theme, and on Linux, it keeps it simple and I may change that
117 	at any time, though after May 2021, you can customize some things with css-inspired
118 	[Widget.Style] classes. (On Windows, if you compile with `-version=custom_widgets`,
119 	you can use the custom implementation there too, but... you shouldn't.)
120 
121 	The event model is similar to what you use in the browser with Javascript and the
122 	layout engine tries to automatically fit things in, similar to a css flexbox.
123 
124 	FOR BEST RESULTS: be sure to link with the appropriate subsystem command
125 	`-L/SUBSYSTEM:WINDOWS:5.0`, for example, because otherwise you'll get a
126 	console and other visual bugs.
127 
128 	HTML_To_Classes:
129 	$(SMALL_TABLE
130 		HTML Code | Minigui Class
131 
132 		`<input type="text">` | [LineEdit]
133 		`<textarea>` | [TextEdit]
134 		`<select>` | [DropDownSelection]
135 		`<input type="checkbox">` | [Checkbox]
136 		`<input type="radio">` | [Radiobox]
137 		`<button>` | [Button]
138 	)
139 
140 
141 	Stretchiness:
142 		The default is 4. You can use larger numbers for things that should
143 		consume a lot of space, and lower numbers for ones that are better at
144 		smaller sizes.
145 
146 	Overlapped_input:
147 		COMING EVENTUALLY:
148 		minigui will include a little bit of I/O functionality that just works
149 		with the event loop. If you want to get fancy, I suggest spinning up
150 		another thread and posting events back and forth.
151 
152 	$(H2 Add ons)
153 		See the `minigui_addons` directory in the arsd repo for some add on widgets
154 		you can import separately too.
155 
156 	$(H3 XML definitions)
157 		If you use [arsd.minigui_xml], you can create widget trees from XML at runtime.
158 
159 	$(H3 Scriptability)
160 		minigui is compatible with [arsd.script]. If you see `@scriptable` on a method
161 		in this documentation, it means you can call it from the script language.
162 
163 		Tip: to allow easy creation of widget trees from script, import [arsd.minigui_xml]
164 		and make [arsd.minigui_xml.makeWidgetFromString] available to your script:
165 
166 		---
167 		import arsd.minigui_xml;
168 		import arsd.script;
169 
170 		var globals = var.emptyObject;
171 		globals.makeWidgetFromString = &makeWidgetFromString;
172 
173 		// this now works
174 		interpret(`var window = makeWidgetFromString("<MainWindow />");`, globals);
175 		---
176 
177 		More to come.
178 
179 	History:
180 		Minigui had mostly additive changes or bug fixes since its inception until May 2021.
181 
182 		In May 2021 (dub v10.0), minigui got an overhaul. If it was versioned independently, I'd
183 		tag this as version 2.0.
184 
185 		Among the changes:
186 		$(LIST
187 			* 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.
188 
189 			See [Event] for details.
190 
191 			* 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.
192 
193 			See [DoubleClickEvent] for details.
194 
195 			* 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.
196 
197 			See [Widget.Style] for details.
198 
199 			// * A widget must now opt in to receiving keyboard focus, rather than opting out.
200 
201 			* 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.
202 
203 			* 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.
204 
205 			* [LabeledLineEdit] changed its default layout to vertical instead of horizontal. You can restore the old behavior by passing a `TextAlignment` argument to the constructor.
206 
207 			* 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.
208 
209 			* Various non-breaking additions.
210 		)
211 +/
212 module arsd.minigui;
213 
214 /++
215 	This hello world sample will have an oversized button, but that's ok, you see your first window!
216 +/
217 version(Demo)
218 unittest {
219 	import arsd.minigui;
220 
221 	void main() {
222 		auto window = new MainWindow();
223 
224 		auto hello = new TextLabel("Hello, world!", TextAlignment.Center, window);
225 		auto button = new Button("Close", window);
226 		button.addWhenTriggered({
227 			window.close();
228 		});
229 
230 		window.loop();
231 	}
232 
233 	main(); // exclude from docs
234 }
235 
236 /++
237 	This example shows one way you can partition your window into a header
238 	and sidebar. Here, the header and sidebar have a fixed width, while the
239 	rest of the content sizes with the window.
240 
241 	It might be a new way of thinking about window layout to do things this
242 	way - perhaps [GridLayout] more matches your style of thought - but the
243 	concept here is to partition the window into sub-boxes with a particular
244 	size, then partition those boxes into further boxes.
245 
246 	$(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.)
247 
248 	So to make the header, start with a child layout that has a max height.
249 	It will use that space from the top, then the remaining children will
250 	split the remaining area, meaning you can think of is as just being another
251 	box you can split again. Keep splitting until you have the look you desire.
252 +/
253 // https://github.com/adamdruppe/arsd/issues/310
254 version(minigui_screenshots)
255 @Screenshot("layout")
256 unittest {
257 	import arsd.minigui;
258 
259 	// This helper class is just to help make the layout boxes visible.
260 	// think of it like a <div style="background-color: whatever;"></div> in HTML.
261 	class ColorWidget : Widget {
262 		this(Color color, Widget parent) {
263 			this.color = color;
264 			super(parent);
265 		}
266 		Color color;
267 		class Style : Widget.Style {
268 			override WidgetBackground background() { return WidgetBackground(color); }
269 		}
270 		mixin OverrideStyle!Style;
271 	}
272 
273 	void main() {
274 		auto window = new Window;
275 
276 		// the key is to give it a max height. This is one way to do it:
277 		auto header = new class HorizontalLayout {
278 			this() { super(window); }
279 			override int maxHeight() { return 50; }
280 		};
281 		// this next line is a shortcut way of doing it too, but it only works
282 		// for HorizontalLayout and VerticalLayout, and is less explicit, so it
283 		// is good to know how to make a new class like above anyway.
284 		// auto header = new HorizontalLayout(50, window);
285 
286 		auto bar = new HorizontalLayout(window);
287 
288 		// or since this is so common, VerticalLayout and HorizontalLayout both
289 		// can just take an argument in their constructor for max width/height respectively
290 
291 		// (could have tone this above too, but I wanted to demo both techniques)
292 		auto left = new VerticalLayout(100, bar);
293 
294 		// and this is the main section's container. A plain Widget instance is good enough here.
295 		auto container = new Widget(bar);
296 
297 		// and these just add color to the containers we made above for the screenshot.
298 		// in a real application, you can just add your actual controls instead of these.
299 		auto headerColorBox = new ColorWidget(Color.teal, header);
300 		auto leftColorBox = new ColorWidget(Color.green, left);
301 		auto rightColorBox = new ColorWidget(Color.purple, container);
302 
303 		window.loop();
304 	}
305 
306 	main(); // exclude from docs
307 }
308 
309 
310 public import arsd.simpledisplay;
311 /++
312 	Convenience import to override the Windows GDI Rectangle function (you can still use it through fully-qualified imports)
313 
314 	History:
315 		Was private until May 15, 2021.
316 +/
317 public alias Rectangle = arsd.color.Rectangle; // I specifically want this in here, not the win32 GDI Rectangle()
318 
319 version(Windows) {
320 	import core.sys.windows.winnls;
321 	import core.sys.windows.windef;
322 	import core.sys.windows.basetyps;
323 	import core.sys.windows.winbase;
324 	import core.sys.windows.winuser;
325 	import core.sys.windows.wingdi;
326 	static import gdi = core.sys.windows.wingdi;
327 }
328 
329 version(Windows) {
330 	version(minigui_manifest) {} else version=minigui_no_manifest;
331 
332 	version(minigui_no_manifest) {} else
333 	static if(__VERSION__ >= 2_083)
334 	version(CRuntime_Microsoft) { // FIXME: mingw?
335 		// assume we want commctrl6 whenever possible since there's really no reason not to
336 		// and this avoids some of the manifest hassle
337 		pragma(linkerDirective, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"");
338 	}
339 }
340 
341 // this is a hack to call the original window procedure on native win32 widgets if our event listener thing prevents default.
342 private bool lastDefaultPrevented;
343 
344 /// Methods marked with this are available from scripts if added to the [arsd.script] engine.
345 alias scriptable = arsd_jsvar_compatible;
346 
347 version(Windows) {
348 	// use native widgets when available unless specifically asked otherwise
349 	version(custom_widgets) {
350 		enum bool UsingCustomWidgets = true;
351 		enum bool UsingWin32Widgets = false;
352 	} else {
353 		version = win32_widgets;
354 		enum bool UsingCustomWidgets = false;
355 		enum bool UsingWin32Widgets = true;
356 	}
357 	// and native theming when needed
358 	//version = win32_theming;
359 } else {
360 	enum bool UsingCustomWidgets = true;
361 	enum bool UsingWin32Widgets = false;
362 	version=custom_widgets;
363 }
364 
365 
366 
367 /*
368 
369 	The main goals of minigui.d are to:
370 		1) Provide basic widgets that just work in a lightweight lib.
371 		   I basically want things comparable to a plain HTML form,
372 		   plus the easy and obvious things you expect from Windows
373 		   apps like a menu.
374 		2) Use native things when possible for best functionality with
375 		   least library weight.
376 		3) Give building blocks to provide easy extension for your
377 		   custom widgets, or hooking into additional native widgets
378 		   I didn't wrap.
379 		4) Provide interfaces for easy interaction between third
380 		   party minigui extensions. (event model, perhaps
381 		   signals/slots, drop-in ease of use bits.)
382 		5) Zero non-system dependencies, including Phobos as much as
383 		   I reasonably can. It must only import arsd.color and
384 		   my simpledisplay.d. If you need more, it will have to be
385 		   an extension module.
386 		6) An easy layout system that generally works.
387 
388 	A stretch goal is to make it easy to make gui forms with code,
389 	some kind of resource file (xml?) and even a wysiwyg designer.
390 
391 	Another stretch goal is to make it easy to hook data into the gui,
392 	including from reflection. So like auto-generate a form from a
393 	function signature or struct definition, or show a list from an
394 	array that automatically updates as the array is changed. Then,
395 	your program focuses on the data more than the gui interaction.
396 
397 
398 
399 	STILL NEEDED:
400 		* combo box. (this is diff than select because you can free-form edit too. more like a lineedit with autoselect)
401 		* slider
402 		* listbox
403 		* spinner
404 		* label?
405 		* rich text
406 */
407 
408 
409 /+
410 	enum LayoutMethods {
411 		 verticalFlex,
412 		 horizontalFlex,
413 		 inlineBlock, // left to right, no stretch, goes to next line as needed
414 		 static, // just set to x, y
415 		 verticalNoStretch, // browser style default
416 
417 		 inlineBlockFlex, // goes left to right, flexing, but when it runs out of space, it spills into next line
418 
419 		 grid, // magic
420 	}
421 +/
422 
423 /++
424 	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.
425 
426 
427 	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.
428 
429 	---
430 	class MinimalWidget : Widget {
431 		this(Widget parent) {
432 			super(parent);
433 		}
434 	}
435 	---
436 
437 	$(SIDEBAR
438 		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.
439 	)
440 
441 	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.
442 
443 	Among the things you'll most likely want to change in your custom widget:
444 
445 	$(LIST
446 		* 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.)
447 
448 		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.
449 
450 		Do this $(I after) calling the `super` constructor.
451 
452 		* 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.
453 
454 		Generally, painting is a job for leaf widgets, since child widgets would obscure your drawing area anyway. However, it is your decision.
455 
456 		* 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.
457 
458 		* 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.
459 	)
460 
461 	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.
462 
463 	It is also possible to embed a [SimpleWindow]-based native window inside a widget. See [OpenGlWidget]'s source code as an example.
464 
465 	Your own custom-drawn and native system controls can exist side-by-side.
466 
467 	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.
468 +/
469 class Widget : ReflectableProperties {
470 
471 	private bool willDraw() {
472 		return true;
473 	}
474 
475 	/+
476 	/++
477 		Calling this directly after constructor can give you a reflectable object as-needed so you don't pay for what you don't need.
478 
479 		History:
480 			Added September 15, 2021
481 			implemented.... ???
482 	+/
483 	void prepareReflection(this This)() {
484 
485 	}
486 	+/
487 
488 	private bool _enabled = true;
489 
490 	/++
491 		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.
492 
493 		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.
494 
495 		History:
496 			Added November 23, 2021 (dub v10.4)
497 
498 			Warning: the specific behavior of disabling with parents may change in the future.
499 		Bugs:
500 			Currently only implemented for widgets backed by native Windows controls.
501 
502 		See_Also: [disabledReason], [disabledBy]
503 	+/
504 	@property bool enabled() {
505 		return disabledBy() is null;
506 	}
507 
508 	/// ditto
509 	@property void enabled(bool yes) {
510 		_enabled = yes;
511 		version(win32_widgets) {
512 			if(hwnd)
513 				EnableWindow(hwnd, yes);
514 		}
515 		setDynamicState(DynamicState.disabled, yes);
516 	}
517 
518 	private string disabledReason_;
519 
520 	/++
521 		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.
522 
523 		Setting this does NOT disable the widget. You need to call `enabled = false;` separately. It does set the data though.
524 
525 		History:
526 			Added November 23, 2021 (dub v10.4)
527 		See_Also: [enabled], [disabledBy]
528 	+/
529 	@property string disabledReason() {
530 		auto w = disabledBy();
531 		return (w is null) ? null : w.disabledReason_;
532 	}
533 
534 	/// ditto
535 	@property void disabledReason(string reason) {
536 		disabledReason_ = reason;
537 	}
538 
539 	/++
540 		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.
541 
542 		History:
543 			Added November 25, 2021 (dub v10.4)
544 		See_Also: [enabled], [disabledReason]
545 	+/
546 	Widget disabledBy() {
547 		Widget p = this;
548 		while(p) {
549 			if(!p._enabled) 
550 				return p;
551 			p = p.parent;
552 		}
553 		return null;
554 	}
555 
556 	/// Implementations of [ReflectableProperties] interface. See the interface for details.
557 	SetPropertyResult setPropertyFromString(string name, scope const(char)[] value, bool valueIsJson) {
558 		if(valueIsJson)
559 			return SetPropertyResult.wrongFormat;
560 		switch(name) {
561 			case "name":
562 				this.name = value.idup;
563 				return SetPropertyResult.success;
564 			case "statusTip":
565 				this.statusTip = value.idup;
566 				return SetPropertyResult.success;
567 			default:
568 				return SetPropertyResult.noSuchProperty;
569 		}
570 	}
571 	/// ditto
572 	void getPropertiesList(scope void delegate(string name) sink) const {
573 		sink("name");
574 		sink("statusTip");
575 	}
576 	/// ditto
577 	void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink) {
578 		switch(name) {
579 			case "name":
580 				sink(name, this.name, false);
581 				return;
582 			case "statusTip":
583 				sink(name, this.statusTip, false);
584 				return;
585 			default:
586 				sink(name, null, true);
587 		}
588 	}
589 
590 	/++
591 		Scales the given value to the system-reported DPI for the monitor on which the widget resides.
592 
593 		History:
594 			Added November 25, 2021 (dub v10.5)
595 			`Point` overload added January 12, 2022 (dub v10.6)
596 	+/
597 	int scaleWithDpi(int value, int assumedDpi = 96) {
598 		// avoid potential overflow with common special values
599 		if(value == int.max)
600 			return int.max;
601 		if(value == int.min)
602 			return int.min;
603 		if(value == 0)
604 			return 0;
605 
606 		auto divide = (parentWindow && parentWindow.win) ? parentWindow.win.actualDpi : assumedDpi;
607 		//divide = 138;
608 		// 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.
609 		// this also covers the case when actualDpi returns 0.
610 		if(divide < 96)
611 			divide = 96;
612 		return value * divide / assumedDpi;
613 	}
614 
615 	/// ditto
616 	Point scaleWithDpi(Point value, int assumedDpi = 96) {
617 		return Point(scaleWithDpi(value.x, assumedDpi), scaleWithDpi(value.y, assumedDpi));
618 	}
619 
620 	// avoid this it just forwards to a soon-to-be-deprecated function and is not remotely stable
621 	// I'll think up something better eventually
622 	protected final int defaultLineHeight() {
623 		auto cs = getComputedStyle();
624 		if(cs.font && !cs.font.isNull)
625 			return cs.font.height() * 5 / 4;
626 		else
627 			return scaleWithDpi(Window.lineHeight);
628 	}
629 
630 	protected final int defaultTextWidth(const(char)[] text) {
631 		auto cs = getComputedStyle();
632 		if(cs.font && !cs.font.isNull)
633 			return cs.font.stringWidth(text);
634 		else
635 			return scaleWithDpi(Window.lineHeight * cast(int) text.length / 2);
636 	}
637 
638 	/++
639 		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.
640 
641 		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.
642 
643 		History:
644 			Added May 22, 2021
645 	+/
646 	protected bool encapsulatedChildren() {
647 		return false;
648 	}
649 
650 	private void privateDpiChanged() {
651 		dpiChanged();
652 		foreach(child; children)
653 			child.privateDpiChanged();
654 	}
655 
656 	/++
657 		Virtual hook to update any caches or fonts you need on the event of a dpi scaling change.
658 
659 		History:
660 			Added January 12, 2022 (dub v10.6)
661 	+/
662 	protected void dpiChanged() {
663 
664 	}
665 
666 	// Default layout properties {
667 
668 		int minWidth() { return 0; }
669 		int minHeight() {
670 			// default widgets have a vertical layout, therefore the minimum height is the sum of the contents
671 			int sum = 0;
672 			foreach(child; children) {
673 				sum += child.minHeight();
674 				sum += child.marginTop();
675 				sum += child.marginBottom();
676 			}
677 
678 			return sum;
679 		}
680 		int maxWidth() { return int.max; }
681 		int maxHeight() { return int.max; }
682 		int widthStretchiness() { return 4; }
683 		int heightStretchiness() { return 4; }
684 
685 		/++
686 			Where stretchiness will grow from the flex basis, this shrinkiness will let it get smaller if needed to make room for other items.
687 
688 			History:
689 				Added June 15, 2021 (dub v10.1)
690 		+/
691 		int widthShrinkiness() { return 0; }
692 		/// ditto
693 		int heightShrinkiness() { return 0; }
694 
695 		/++
696 			The initial size of the widget for layout calculations. Default is 0.
697 
698 			See_Also: [https://developer.mozilla.org/en-US/docs/Web/CSS/flex-basis|CSS flex-basis]
699 
700 			History:
701 				Added June 15, 2021 (dub v10.1)
702 		+/
703 		int flexBasisWidth() { return 0; }
704 		/// ditto
705 		int flexBasisHeight() { return 0; }
706 
707 		int marginLeft() { return 0; }
708 		int marginRight() { return 0; }
709 		int marginTop() { return 0; }
710 		int marginBottom() { return 0; }
711 		int paddingLeft() { return 0; }
712 		int paddingRight() { return 0; }
713 		int paddingTop() { return 0; }
714 		int paddingBottom() { return 0; }
715 		//LinePreference linePreference() { return LinePreference.PreferOwnLine; }
716 
717 		private bool recomputeChildLayoutRequired = true;
718 		private static class RecomputeEvent {}
719 		private __gshared rce = new RecomputeEvent();
720 		protected final void queueRecomputeChildLayout() {
721 			recomputeChildLayoutRequired = true;
722 
723 			if(this.parentWindow) {
724 				auto sw = this.parentWindow.win;
725 				assert(sw !is null);
726 				if(!sw.eventQueued!RecomputeEvent) {
727 					sw.postEvent(rce);
728 					// import std.stdio; writeln("redraw requested from ", file,":",line," ", this.parentWindow.win.impl.window);
729 				}
730 			}
731 
732 		}
733 
734 		protected final void recomputeChildLayoutEntry() {
735 			if(recomputeChildLayoutRequired) {
736 				recomputeChildLayout();
737 				recomputeChildLayoutRequired = false;
738 				redraw();
739 			} else {
740 				// I still need to check the tree just in case one of them was queued up
741 				// and the event came up here instead of there.
742 				foreach(child; children)
743 					child.recomputeChildLayoutEntry();
744 			}
745 		}
746 
747 		// this function should (almost) never be called directly anymore... call recomputeChildLayoutEntry when executing it and queueRecomputeChildLayout if you just want it done soon
748 		void recomputeChildLayout() {
749 			.recomputeChildLayout!"height"(this);
750 		}
751 
752 	// }
753 
754 
755 	/++
756 		Returns the style's tag name string this object uses.
757 
758 		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.
759 
760 		This tag may never be used, it is just available for the [VisualTheme.getPropertyString] if it chooses to do something like CSS.
761 
762 		History:
763 			Added May 10, 2021
764 	+/
765 	string styleTagName() const {
766 		string n = typeid(this).name;
767 		foreach_reverse(idx, ch; n)
768 			if(ch == '.') {
769 				n = n[idx + 1 .. $];
770 				break;
771 			}
772 		return n;
773 	}
774 
775 	/// API for the [styleClassList]
776 	static struct ClassList {
777 		private Widget widget;
778 
779 		///
780 		void add(string s) {
781 			widget.styleClassList_ ~= s;
782 		}
783 
784 		///
785 		void remove(string s) {
786 			foreach(idx, s1; widget.styleClassList_)
787 				if(s1 == s) {
788 					widget.styleClassList_[idx] = widget.styleClassList_[$-1];
789 					widget.styleClassList_ = widget.styleClassList_[0 .. $-1];
790 					widget.styleClassList_.assumeSafeAppend();
791 					return;
792 				}
793 		}
794 
795 		/// Returns true if it was added, false if it was removed.
796 		bool toggle(string s) {
797 			if(contains(s)) {
798 				remove(s);
799 				return false;
800 			} else {
801 				add(s);
802 				return true;
803 			}
804 		}
805 
806 		///
807 		bool contains(string s) const {
808 			foreach(s1; widget.styleClassList_)
809 				if(s1 == s)
810 					return true;
811 			return false;
812 
813 		}
814 	}
815 
816 	private string[] styleClassList_;
817 
818 	/++
819 		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.
820 
821 		It has no inherent meaning, it is really just a place to put some metadata tags on individual objects.
822 
823 		History:
824 			Added May 10, 2021
825 	+/
826 	inout(ClassList) styleClassList() inout {
827 		return cast(inout(ClassList)) ClassList(cast() this);
828 	}
829 
830 	/++
831 		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.
832 
833 		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.
834 
835 		The upper 32 bits are available for your own extensions.
836 
837 		History:
838 			Added May 10, 2021
839 	+/
840 	enum DynamicState : ulong {
841 		focus = (1 << 0), /// the widget currently has the keyboard focus
842 		hover = (1 << 1), /// the mouse is currently hovering over the widget (may not always be updated)
843 		valid = (1 << 2), /// the widget's content has been validated and it passed (do not set if not validation has been performed!)
844 		invalid = (1 << 3), /// the widget's content has been validated and it failed (do not set if not validation has been performed!)
845 		checked = (1 << 4), /// the widget is toggleable and currently toggled on
846 		selected = (1 << 5), /// the widget represents one option of many and is currently selected, but is not necessarily focused nor checked.
847 		disabled = (1 << 6), /// the widget is currently unable to perform its designated task
848 		indeterminate = (1 << 7), /// the widget has tri-state and is between checked and not checked
849 		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.
850 
851 		USER_BEGIN = (1UL << 32),
852 	}
853 
854 	// I want to add the primary and cancel styles to buttons at least at some point somehow.
855 
856 	/// ditto
857 	@property ulong dynamicState() { return dynamicState_; }
858 	/// ditto
859 	@property ulong dynamicState(ulong newValue) {
860 		if(dynamicState != newValue) {
861 			auto old = dynamicState_;
862 			dynamicState_ = newValue;
863 
864 			useStyleProperties((scope Widget.Style s) {
865 				if(s.variesWithState(old ^ newValue))
866 					redraw();
867 			});
868 		}
869 		return dynamicState_;
870 	}
871 
872 	/// ditto
873 	void setDynamicState(ulong flags, bool state) {
874 		auto ds = dynamicState_;
875 		if(state)
876 			ds |= flags;
877 		else
878 			ds &= ~flags;
879 
880 		dynamicState = ds;
881 	}
882 
883 	private ulong dynamicState_;
884 
885 	deprecated("Use dynamic styles instead now") {
886 		Color backgroundColor() { return backgroundColor_; }
887 		void backgroundColor(Color c){ this.backgroundColor_ = c; }
888 
889 		MouseCursor cursor() { return GenericCursor.Default; }
890 	} private Color backgroundColor_ = Color.transparent;
891 
892 
893 	/++
894 		Style properties are defined as an accessory class so they can be referenced and overridden independently.
895 
896 		It is here so there can be a specificity switch.
897 
898 		See [OverrideStyle] for a helper function to use your own.
899 
900 		History:
901 			Added May 11, 2021
902 	+/
903 	static class Style/* : StyleProperties*/ {
904 		public Widget widget; // public because the mixin template needs access to it
905 
906 		/++
907 			You must override this to trigger automatic redraws if you ever uses the `dynamicState` flag in your style.
908 
909 			History:
910 				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.
911 		+/
912 		bool variesWithState(ulong dynamicStateFlags) {
913 			version(win32_widgets) {
914 				if(widget.hwnd)
915 					return false;
916 			}
917 			return widget.tabStop && ((dynamicStateFlags & DynamicState.focus) ? true : false);
918 		}
919 
920 		///
921 		Color foregroundColor() {
922 			return WidgetPainter.visualTheme.foregroundColor;
923 		}
924 
925 		///
926 		WidgetBackground background() {
927 			// the default is a "transparent" background, which means
928 			// it goes as far up as it can to get the color
929 			if (widget.backgroundColor_ != Color.transparent)
930 				return WidgetBackground(widget.backgroundColor_);
931 			if (widget.parent)
932 				return widget.parent.getComputedStyle.background;
933 			return WidgetBackground(widget.backgroundColor_);
934 		}
935 
936 		private static OperatingSystemFont fontCached_;
937 		private OperatingSystemFont fontCached() {
938 			if(fontCached_ is null)
939 				fontCached_ = font();
940 			return fontCached_;
941 		}
942 
943 		/++
944 			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.
945 		+/
946 		OperatingSystemFont font() {
947 			return null;
948 		}
949 
950 		/++
951 			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.
952 
953 			You can return a member of [GenericCursor] or your own [MouseCursor] instance.
954 
955 			History:
956 				Was previously a method directly on [Widget], moved to [Widget.Style] on May 12, 2021
957 		+/
958 		MouseCursor cursor() {
959 			return GenericCursor.Default;
960 		}
961 
962 		FrameStyle borderStyle() {
963 			return FrameStyle.none;
964 		}
965 
966 		/++
967 		+/
968 		Color borderColor() {
969 			return Color.transparent;
970 		}
971 
972 		FrameStyle outlineStyle() {
973 			if(widget.dynamicState & DynamicState.focus)
974 				return FrameStyle.dotted;
975 			else
976 				return FrameStyle.none;
977 		}
978 
979 		Color outlineColor() {
980 			return foregroundColor;
981 		}
982 	}
983 
984 	/++
985 		This mixin overrides the [useStyleProperties] method to direct it toward your own style class.
986 		The basic usage is simple:
987 
988 		---
989 		static class Style : YourParentClass.Style { /* YourParentClass is frequently Widget, of course, but not always */
990 			// override style hints as-needed here
991 		}
992 		OverrideStyle!Style; // add the method
993 		---
994 
995 		$(TIP
996 			While the class is not forced to be `static`, for best results, it should be. A non-static class
997 			can not be inherited by other objects whereas the static one can. A property on the base class,
998 			called [Widget.Style.widget|widget], is available for you to access its properties.
999 		)
1000 
1001 		This exists just because [useStyleProperties] has a somewhat convoluted signature and its overrides must
1002 		repeat them. Moreover, its implementation uses a stack class to optimize GC pressure from small fetches
1003 		and that's a little tedious to repeat in your child classes too when you only care about changing the type.
1004 
1005 
1006 		It also has a further facility to pick a wholly differnet class based on the [DynamicState] of the Widget.
1007 		You may also just override `variesWithState` when you use this flag.
1008 
1009 		---
1010 		mixin OverrideStyle!(
1011 			DynamicState.focus, YourFocusedStyle,
1012 			DynamicState.hover, YourHoverStyle,
1013 			YourDefaultStyle
1014 		)
1015 		---
1016 
1017 		It checks if `dynamicState` matches the state and if so, returns the object given.
1018 
1019 		If there is no state mask given, the next one matches everything. The first match given is used.
1020 
1021 		However, since in most cases you'll want check state inside your individual methods, you probably won't
1022 		find much use for this whole-class swap out.
1023 
1024 		History:
1025 			Added May 16, 2021
1026 	+/
1027 	static protected mixin template OverrideStyle(S...) {
1028 		static import amg = arsd.minigui;
1029 		override void useStyleProperties(scope void delegate(scope amg.Widget.Style props) dg) {
1030 			ulong mask = 0;
1031 			foreach(idx, thing; S) {
1032 				static if(is(typeof(thing) : ulong)) {
1033 					mask = thing;
1034 				} else {
1035 					if(!(idx & 1) || (this.dynamicState & mask) == mask) {
1036 						//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.");
1037 						scope amg.Widget.Style s = new thing();
1038 						s.widget = this;
1039 						dg(s);
1040 						return;
1041 					}
1042 				}
1043 			}
1044 		}
1045 	}
1046 	/++
1047 		You can override this by hand, or use the [OverrideStyle] helper which is a bit less verbose.
1048 	+/
1049 	void useStyleProperties(scope void delegate(scope Style props) dg) {
1050 		scope Style s = new Style();
1051 		s.widget = this;
1052 		dg(s);
1053 	}
1054 
1055 
1056 	protected void sendResizeEvent() {
1057 		this.emit!ResizeEvent();
1058 	}
1059 
1060 	Menu contextMenu(int x, int y) { return null; }
1061 
1062 	final bool showContextMenu(int x, int y, int screenX = -2, int screenY = -2) {
1063 		if(parentWindow is null || parentWindow.win is null) return false;
1064 
1065 		auto menu = this.contextMenu(x, y);
1066 		if(menu is null)
1067 			return false;
1068 
1069 		version(win32_widgets) {
1070 			// FIXME: if it is -1, -1, do it at the current selection location instead
1071 			// tho the corner of the window, whcih it does now, isn't the literal worst.
1072 
1073 			if(screenX < 0 && screenY < 0) {
1074 				auto p = this.globalCoordinates();
1075 				if(screenX == -2)
1076 					p.x += x;
1077 				if(screenY == -2)
1078 					p.y += y;
1079 
1080 				screenX = p.x;
1081 				screenY = p.y;
1082 			}
1083 
1084 			if(!TrackPopupMenuEx(menu.handle, 0, screenX, screenY, parentWindow.win.impl.hwnd, null))
1085 				throw new Exception("TrackContextMenuEx");
1086 		} else version(custom_widgets) {
1087 			menu.popup(this, x, y);
1088 		}
1089 
1090 		return true;
1091 	}
1092 
1093 	/++
1094 		Removes this widget from its parent.
1095 
1096 		History:
1097 			`removeWidget` was made `final` on May 11, 2021.
1098 	+/
1099 	@scriptable
1100 	final void removeWidget() {
1101 		auto p = this.parent;
1102 		if(p) {
1103 			int item;
1104 			for(item = 0; item < p._children.length; item++)
1105 				if(p._children[item] is this)
1106 					break;
1107 			auto idx = item;
1108 			for(; item < p._children.length - 1; item++)
1109 				p._children[item] = p._children[item + 1];
1110 			p._children = p._children[0 .. $-1];
1111 
1112 			this.parent.widgetRemoved(idx, this);
1113 			//this.parent = null;
1114 		}
1115 		version(win32_widgets) {
1116 			removeAllChildren();
1117 			if(hwnd) {
1118 				DestroyWindow(hwnd);
1119 				hwnd = null;
1120 			}
1121 		}
1122 	}
1123 
1124 	/++
1125 		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.
1126 
1127 		History:
1128 			Added September 19, 2021
1129 	+/
1130 	protected void widgetRemoved(size_t oldIndex, Widget oldReference) { }
1131 
1132 	/++
1133 		Removes all child widgets from `this`. You should not use the removed widgets again.
1134 
1135 		Note that on Windows, it also destroys the native handles for the removed children recursively.
1136 
1137 		History:
1138 			Added July 1, 2021 (dub v10.2)
1139 	+/
1140 	void removeAllChildren() {
1141 		version(win32_widgets)
1142 		foreach(child; _children) {
1143 			child.removeAllChildren();
1144 			if(child.hwnd) {
1145 				DestroyWindow(child.hwnd);
1146 				child.hwnd = null;
1147 			}
1148 		}
1149 		auto orig = this._children;
1150 		this._children = null;
1151 		foreach(idx, w; orig)
1152 			this.widgetRemoved(idx, w);
1153 	}
1154 
1155 	/++
1156 		Calls [getByName] with the generic type of Widget. Meant for script interop where instantiating a template is impossible.
1157 	+/
1158 	@scriptable
1159 	Widget getChildByName(string name) {
1160 		return getByName(name);
1161 	}
1162 	/++
1163 		Finds the nearest descendant with the requested type and [name]. May return `this`.
1164 	+/
1165 	final WidgetClass getByName(WidgetClass = Widget)(string name) {
1166 		if(this.name == name)
1167 			if(auto c = cast(WidgetClass) this)
1168 				return c;
1169 		foreach(child; children) {
1170 			auto w = child.getByName(name);
1171 			if(auto c = cast(WidgetClass) w)
1172 				return c;
1173 		}
1174 		return null;
1175 	}
1176 
1177 	/++
1178 		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.
1179 		Names should be unique in a window.
1180 
1181 		See_Also: [getByName], [getChildByName]
1182 	+/
1183 	@scriptable string name;
1184 
1185 	private EventHandler[][string] bubblingEventHandlers;
1186 	private EventHandler[][string] capturingEventHandlers;
1187 
1188 	/++
1189 		Default event handlers. These are called on the appropriate
1190 		event unless [Event.preventDefault] is called on the event at
1191 		some point through the bubbling process.
1192 
1193 
1194 		If you are implementing your own widget and want to add custom
1195 		events, you should follow the same pattern here: create a virtual
1196 		function named `defaultEventHandler_eventname` with the implementation,
1197 		then, override [setupDefaultEventHandlers] and add a wrapped caller to
1198 		`defaultEventHandlers["eventname"]`. It should be wrapped like so:
1199 		`defaultEventHandlers["eventname"] = (Widget t, Event event) { t.defaultEventHandler_name(event); };`.
1200 		This ensures virtual dispatch based on the correct subclass.
1201 
1202 		Also, don't forget to call `super.setupDefaultEventHandlers();` too in your
1203 		overridden version.
1204 
1205 		You only need to do that on parent classes adding NEW event types. If you
1206 		just want to change the default behavior of an existing event type in a subclass,
1207 		you override the function (and optionally call `super.method_name`) like normal.
1208 
1209 	+/
1210 	protected EventHandler[string] defaultEventHandlers;
1211 
1212 	/// ditto
1213 	void setupDefaultEventHandlers() {
1214 		defaultEventHandlers["click"] = (Widget t, Event event) { t.defaultEventHandler_click(cast(ClickEvent) event); };
1215 		defaultEventHandlers["dblclick"] = (Widget t, Event event) { t.defaultEventHandler_dblclick(cast(DoubleClickEvent) event); };
1216 		defaultEventHandlers["keydown"] = (Widget t, Event event) { t.defaultEventHandler_keydown(cast(KeyDownEvent) event); };
1217 		defaultEventHandlers["keyup"] = (Widget t, Event event) { t.defaultEventHandler_keyup(cast(KeyUpEvent) event); };
1218 		defaultEventHandlers["mouseover"] = (Widget t, Event event) { t.defaultEventHandler_mouseover(cast(MouseOverEvent) event); };
1219 		defaultEventHandlers["mouseout"] = (Widget t, Event event) { t.defaultEventHandler_mouseout(cast(MouseOutEvent) event); };
1220 		defaultEventHandlers["mousedown"] = (Widget t, Event event) { t.defaultEventHandler_mousedown(cast(MouseDownEvent) event); };
1221 		defaultEventHandlers["mouseup"] = (Widget t, Event event) { t.defaultEventHandler_mouseup(cast(MouseUpEvent) event); };
1222 		defaultEventHandlers["mouseenter"] = (Widget t, Event event) { t.defaultEventHandler_mouseenter(cast(MouseEnterEvent) event); };
1223 		defaultEventHandlers["mouseleave"] = (Widget t, Event event) { t.defaultEventHandler_mouseleave(cast(MouseLeaveEvent) event); };
1224 		defaultEventHandlers["mousemove"] = (Widget t, Event event) { t.defaultEventHandler_mousemove(cast(MouseMoveEvent) event); };
1225 		defaultEventHandlers["char"] = (Widget t, Event event) { t.defaultEventHandler_char(cast(CharEvent) event); };
1226 		defaultEventHandlers["triggered"] = (Widget t, Event event) { t.defaultEventHandler_triggered(event); };
1227 		defaultEventHandlers["change"] = (Widget t, Event event) { t.defaultEventHandler_change(event); };
1228 		defaultEventHandlers["focus"] = (Widget t, Event event) { t.defaultEventHandler_focus(event); };
1229 		defaultEventHandlers["blur"] = (Widget t, Event event) { t.defaultEventHandler_blur(event); };
1230 		defaultEventHandlers["focusin"] = (Widget t, Event event) { t.defaultEventHandler_focusin(event); };
1231 		defaultEventHandlers["focusout"] = (Widget t, Event event) { t.defaultEventHandler_focusout(event); };
1232 	}
1233 
1234 	/// ditto
1235 	void defaultEventHandler_click(ClickEvent event) {}
1236 	/// ditto
1237 	void defaultEventHandler_dblclick(DoubleClickEvent event) {}
1238 	/// ditto
1239 	void defaultEventHandler_keydown(KeyDownEvent event) {}
1240 	/// ditto
1241 	void defaultEventHandler_keyup(KeyUpEvent event) {}
1242 	/// ditto
1243 	void defaultEventHandler_mousedown(MouseDownEvent event) {
1244 		if(event.button == MouseButton.left) {
1245 			if(this.tabStop)
1246 				this.focus();
1247 		}
1248 	}
1249 	/// ditto
1250 	void defaultEventHandler_mouseover(MouseOverEvent event) {}
1251 	/// ditto
1252 	void defaultEventHandler_mouseout(MouseOutEvent event) {}
1253 	/// ditto
1254 	void defaultEventHandler_mouseup(MouseUpEvent event) {}
1255 	/// ditto
1256 	void defaultEventHandler_mousemove(MouseMoveEvent event) {}
1257 	/// ditto
1258 	void defaultEventHandler_mouseenter(MouseEnterEvent event) {}
1259 	/// ditto
1260 	void defaultEventHandler_mouseleave(MouseLeaveEvent event) {}
1261 	/// ditto
1262 	void defaultEventHandler_char(CharEvent event) {}
1263 	/// ditto
1264 	void defaultEventHandler_triggered(Event event) {}
1265 	/// ditto
1266 	void defaultEventHandler_change(Event event) {}
1267 	/// ditto
1268 	void defaultEventHandler_focus(Event event) {}
1269 	/// ditto
1270 	void defaultEventHandler_blur(Event event) {}
1271 	/// ditto
1272 	void defaultEventHandler_focusin(Event event) {}
1273 	/// ditto
1274 	void defaultEventHandler_focusout(Event event) {}
1275 
1276 	/++
1277 		[Event]s use a Javascript-esque model. See more details on the [Event] page.
1278 
1279 		[addEventListener] returns an opaque handle that you can later pass to [removeEventListener].
1280 
1281 		addDirectEventListener just inserts a check `if(e.target !is this) return;` meaning it opts out
1282 		of participating in handler delegation.
1283 
1284 		$(TIP
1285 			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.
1286 		)
1287 	+/
1288 	EventListener addDirectEventListener(string event, void delegate() handler, bool useCapture = false) {
1289 		return addEventListener(event, (Widget, scope Event e) {
1290 			if(e.srcElement is this)
1291 				handler();
1292 		}, useCapture);
1293 	}
1294 
1295 	/// ditto
1296 	EventListener addDirectEventListener(string event, void delegate(Event) handler, bool useCapture = false) {
1297 		return addEventListener(event, (Widget, Event e) {
1298 			if(e.srcElement is this)
1299 				handler(e);
1300 		}, useCapture);
1301 	}
1302 
1303 	/// ditto
1304 	EventListener addDirectEventListener(Handler)(Handler handler, bool useCapture = false) {
1305 		static if(is(Handler Fn == delegate)) {
1306 		static if(is(Fn Params == __parameters)) {
1307 			return addEventListener(EventString!(Params[0]), (Widget, Event e) {
1308 				if(e.srcElement !is this)
1309 					return;
1310 				auto ty = cast(Params[0]) e;
1311 				if(ty !is null)
1312 					handler(ty);
1313 			}, useCapture);
1314 		} else static assert(0);
1315 		} else static assert(0, "Your handler wasn't usable because it wasn't passed a delegate. Use the delegate keyword at the call site.");
1316 	}
1317 
1318 	/// ditto
1319 	@scriptable
1320 	EventListener addEventListener(string event, void delegate() handler, bool useCapture = false) {
1321 		return addEventListener(event, (Widget, scope Event) { handler(); }, useCapture);
1322 	}
1323 
1324 	/// ditto
1325 	EventListener addEventListener(Handler)(Handler handler, bool useCapture = false) {
1326 		static if(is(Handler Fn == delegate)) {
1327 		static if(is(Fn Params == __parameters)) {
1328 			return addEventListener(EventString!(Params[0]), (Widget, Event e) {
1329 				auto ty = cast(Params[0]) e;
1330 				if(ty !is null)
1331 					handler(ty);
1332 			}, useCapture);
1333 		} else static assert(0);
1334 		} else static assert(0, "Your handler wasn't usable because it wasn't passed a delegate. Use the delegate keyword at the call site.");
1335 	}
1336 
1337 	/// ditto
1338 	EventListener addEventListener(string event, void delegate(Event) handler, bool useCapture = false) {
1339 		return addEventListener(event, (Widget, Event e) { handler(e); }, useCapture);
1340 	}
1341 
1342 	/// ditto
1343 	EventListener addEventListener(string event, EventHandler handler, bool useCapture = false) {
1344 		if(event.length > 2 && event[0..2] == "on")
1345 			event = event[2 .. $];
1346 
1347 		if(useCapture)
1348 			capturingEventHandlers[event] ~= handler;
1349 		else
1350 			bubblingEventHandlers[event] ~= handler;
1351 
1352 		return EventListener(this, event, handler, useCapture);
1353 	}
1354 
1355 	/// ditto
1356 	void removeEventListener(string event, EventHandler handler, bool useCapture = false) {
1357 		if(event.length > 2 && event[0..2] == "on")
1358 			event = event[2 .. $];
1359 
1360 		if(useCapture) {
1361 			if(event in capturingEventHandlers)
1362 			foreach(ref evt; capturingEventHandlers[event])
1363 				if(evt is handler) evt = null;
1364 		} else {
1365 			if(event in bubblingEventHandlers)
1366 			foreach(ref evt; bubblingEventHandlers[event])
1367 				if(evt is handler) evt = null;
1368 		}
1369 	}
1370 
1371 	/// ditto
1372 	void removeEventListener(EventListener listener) {
1373 		removeEventListener(listener.event, listener.handler, listener.useCapture);
1374 	}
1375 
1376 	static if(UsingSimpledisplayX11) {
1377 		void discardXConnectionState() {
1378 			foreach(child; children)
1379 				child.discardXConnectionState();
1380 		}
1381 
1382 		void recreateXConnectionState() {
1383 			foreach(child; children)
1384 				child.recreateXConnectionState();
1385 			redraw();
1386 		}
1387 	}
1388 
1389 	/++
1390 		Returns the coordinates of this widget on the screen, relative to the upper left corner of the whole screen.
1391 
1392 		History:
1393 			`globalCoordinates` was made `final` on May 11, 2021.
1394 	+/
1395 	Point globalCoordinates() {
1396 		int x = this.x;
1397 		int y = this.y;
1398 		auto p = this.parent;
1399 		while(p) {
1400 			x += p.x;
1401 			y += p.y;
1402 			p = p.parent;
1403 		}
1404 
1405 		static if(UsingSimpledisplayX11) {
1406 			auto dpy = XDisplayConnection.get;
1407 			arsd.simpledisplay.Window dummyw;
1408 			XTranslateCoordinates(dpy, this.parentWindow.win.impl.window, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
1409 		} else {
1410 			POINT pt;
1411 			pt.x = x;
1412 			pt.y = y;
1413 			MapWindowPoints(this.parentWindow.win.impl.hwnd, null, &pt, 1);
1414 			x = pt.x;
1415 			y = pt.y;
1416 		}
1417 
1418 		return Point(x, y);
1419 	}
1420 
1421 	version(win32_widgets)
1422 	int handleWmDrawItem(DRAWITEMSTRUCT* dis) { return 0; }
1423 
1424 	version(win32_widgets)
1425 	/// Called when a WM_COMMAND is sent to the associated hwnd.
1426 	void handleWmCommand(ushort cmd, ushort id) {}
1427 
1428 	version(win32_widgets)
1429 	/++
1430 		Called when a WM_NOTIFY is sent to the associated hwnd.
1431 
1432 		History:
1433 	+/
1434 	int handleWmNotify(NMHDR* hdr, int code, out int mustReturn) { return 0; }
1435 
1436 	version(win32_widgets)
1437 	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); }
1438 
1439 	/++
1440 		This tip is displayed in the status bar (if there is one in the containing window) when the mouse moves over this widget.
1441 
1442 		Updates to this variable will only be made visible on the next mouse enter event.
1443 	+/
1444 	@scriptable string statusTip;
1445 	// string toolTip;
1446 	// string helpText;
1447 
1448 	/++
1449 		If true, this widget can be focused via keyboard control with the tab key.
1450 
1451 		If false, it is assumed the widget itself does will never receive the keyboard focus (though its childen are free to).
1452 	+/
1453 	bool tabStop = true;
1454 	/++
1455 		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.)
1456 	+/
1457 	int tabOrder;
1458 
1459 	version(win32_widgets) {
1460 		static Widget[HWND] nativeMapping;
1461 		/// The native handle, if there is one.
1462 		HWND hwnd;
1463 		WNDPROC originalWindowProcedure;
1464 
1465 		SimpleWindow simpleWindowWrappingHwnd;
1466 
1467 		// please note it IGNORES your return value and does NOT forward it to Windows!
1468 		int hookedWndProc(UINT iMessage, WPARAM wParam, LPARAM lParam) {
1469 			return 0;
1470 		}
1471 	}
1472 	private bool implicitlyCreated;
1473 
1474 	/// 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.
1475 	int x;
1476 	/// ditto
1477 	int y;
1478 	private int _width;
1479 	private int _height;
1480 	private Widget[] _children;
1481 	private Widget _parent;
1482 	private Window _parentWindow;
1483 
1484 	/++
1485 		Returns the window to which this widget is attached.
1486 
1487 		History:
1488 			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.
1489 	+/
1490 	final @property inout(Window) parentWindow() inout @nogc nothrow pure { return _parentWindow; }
1491 	private @property void parentWindow(Window parent) {
1492 		_parentWindow = parent;
1493 		foreach(child; children)
1494 			child.parentWindow = parent; // please note that this is recursive
1495 	}
1496 
1497 	/++
1498 		Returns the list of the widget's children.
1499 
1500 		History:
1501 			Prior to May 11, 2021, the `Widget[] children` was directly available. Now, only this property getter is available and the actual store is private.
1502 
1503 			Children should be added by the constructor most the time, but if that's impossible, use [addChild] and [removeWidget] to manage the list.
1504 	+/
1505 	final @property inout(Widget)[] children() inout @nogc nothrow pure { return _children; }
1506 
1507 	/++
1508 		Returns the widget's parent.
1509 
1510 		History:
1511 			Prior to May 11, 2021, the `Widget parent` variable was directly available. Now, only this property getter is permitted.
1512 
1513 			The parent should only be managed by the [addChild] and [removeWidget] method.
1514 	+/
1515 	final @property inout(Widget) parent() inout nothrow @nogc pure @safe return { return _parent; }
1516 
1517 	/// The widget's current size.
1518 	final @scriptable public @property int width() const nothrow @nogc pure @safe { return _width; }
1519 	/// ditto
1520 	final @scriptable public @property int height() const nothrow @nogc pure @safe { return _height; }
1521 
1522 	/// Only the layout manager should be calling these.
1523 	final protected @property int width(int a) @safe { return _width = a; }
1524 	/// ditto
1525 	final protected @property int height(int a) @safe { return _height = a; }
1526 
1527 	/++
1528 		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.
1529 
1530 		It is also responsible for calling [sendResizeEvent] to notify other listeners that the widget has changed size.
1531 	+/
1532 	protected void registerMovement() {
1533 		version(win32_widgets) {
1534 			if(hwnd) {
1535 				auto pos = getChildPositionRelativeToParentHwnd(this);
1536 				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
1537 			}
1538 		}
1539 		sendResizeEvent();
1540 	}
1541 
1542 	/// Creates the widget and adds it to the parent.
1543 	this(Widget parent) {
1544 		if(parent !is null)
1545 			parent.addChild(this);
1546 		setupDefaultEventHandlers();
1547 	}
1548 
1549 	/// 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.
1550 	@scriptable
1551 	bool isFocused() {
1552 		return parentWindow && parentWindow.focusedWidget is this;
1553 	}
1554 
1555 	private bool showing_ = true;
1556 	///
1557 	bool showing() { return showing_; }
1558 	///
1559 	bool hidden() { return !showing_; }
1560 	/++
1561 		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.
1562 	+/
1563 	void showing(bool s, bool recalculate = true) {
1564 		auto so = showing_;
1565 		showing_ = s;
1566 		if(s != so) {
1567 			version(win32_widgets)
1568 			if(hwnd)
1569 				ShowWindow(hwnd, s ? SW_SHOW : SW_HIDE);
1570 
1571 			if(parent && recalculate) {
1572 				parent.queueRecomputeChildLayout();
1573 				parent.redraw();
1574 			}
1575 
1576 			foreach(child; children)
1577 				child.showing(s, false);
1578 
1579 		}
1580 		queueRecomputeChildLayout();
1581 		redraw();
1582 	}
1583 	/// Convenience method for `showing = true`
1584 	@scriptable
1585 	void show() {
1586 		showing = true;
1587 	}
1588 	/// Convenience method for `showing = false`
1589 	@scriptable
1590 	void hide() {
1591 		showing = false;
1592 	}
1593 
1594 	///
1595 	@scriptable
1596 	void focus() {
1597 		assert(parentWindow !is null);
1598 		if(isFocused())
1599 			return;
1600 
1601 		if(parentWindow.focusedWidget) {
1602 			// FIXME: more details here? like from and to
1603 			auto from = parentWindow.focusedWidget;
1604 			parentWindow.focusedWidget.setDynamicState(DynamicState.focus, false);
1605 			parentWindow.focusedWidget = null;
1606 			from.emit!BlurEvent();
1607 			this.emit!FocusOutEvent();
1608 		}
1609 
1610 
1611 		version(win32_widgets) {
1612 			if(this.hwnd !is null)
1613 				SetFocus(this.hwnd);
1614 		}
1615 		//else static if(UsingSimpledisplayX11)
1616 			//this.parentWindow.win.focus();
1617 
1618 		parentWindow.focusedWidget = this;
1619 		parentWindow.focusedWidget.setDynamicState(DynamicState.focus, true);
1620 		this.emit!FocusEvent();
1621 		this.emit!FocusInEvent();
1622 	}
1623 
1624 	/+
1625 	/++
1626 		Unfocuses the widget. This may reset
1627 	+/
1628 	@scriptable
1629 	void blur() {
1630 
1631 	}
1632 	+/
1633 
1634 
1635 	/++
1636 		This is called when the widget is added to a window. It gives you a chance to set up event hooks.
1637 
1638 		Update on May 11, 2021: I'm considering removing this method. You can usually achieve these things through looser-coupled methods.
1639 	+/
1640 	void attachedToWindow(Window w) {}
1641 	/++
1642 		Callback when the widget is added to another widget.
1643 
1644 		Update on May 11, 2021: I'm considering removing this method since I've never actually found it useful.
1645 	+/
1646 	void addedTo(Widget w) {}
1647 
1648 	/++
1649 		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.
1650 
1651 		This is available primarily to be overridden. For example, [MainWindow] overrides it to redirect its children into a central widget.
1652 	+/
1653 	protected void addChild(Widget w, int position = int.max) {
1654 		assert(w._parent !is this, "Child cannot be added twice to the same parent");
1655 		assert(w !is this, "Child cannot be its own parent!");
1656 		w._parent = this;
1657 		if(position == int.max || position == children.length) {
1658 			_children ~= w;
1659 		} else {
1660 			assert(position < _children.length);
1661 			_children.length = _children.length + 1;
1662 			for(int i = cast(int) _children.length - 1; i > position; i--)
1663 				_children[i] = _children[i - 1];
1664 			_children[position] = w;
1665 		}
1666 
1667 		this.parentWindow = this._parentWindow;
1668 
1669 		w.addedTo(this);
1670 
1671 		if(this.hidden)
1672 			w.showing = false;
1673 
1674 		if(parentWindow !is null) {
1675 			w.attachedToWindow(parentWindow);
1676 			parentWindow.queueRecomputeChildLayout();
1677 			parentWindow.redraw();
1678 		}
1679 	}
1680 
1681 	/++
1682 		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.
1683 	+/
1684 	Widget getChildAtPosition(int x, int y) {
1685 		// it goes backward so the last one to show gets picked first
1686 		// might use z-index later
1687 		foreach_reverse(child; children) {
1688 			if(child.hidden)
1689 				continue;
1690 			if(child.x <= x && child.y <= y
1691 				&& ((x - child.x) < child.width)
1692 				&& ((y - child.y) < child.height))
1693 			{
1694 				return child;
1695 			}
1696 		}
1697 
1698 		return null;
1699 	}
1700 
1701 	/++
1702 		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.
1703 
1704 		History:
1705 			Added July 2, 2021 (v10.2)
1706 	+/
1707 	protected void addScrollPosition(ref int x, ref int y) {};
1708 
1709 	/++
1710 		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.
1711 
1712 		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.
1713 
1714 		[paint] is not called for system widgets as the OS library draws them instead.
1715 
1716 
1717 		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.
1718 
1719 		You should also look at [WidgetPainter.visualTheme] to be theme aware.
1720 
1721 		History:
1722 			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.
1723 	+/
1724 	void paint(WidgetPainter painter) {
1725 		version(win32_widgets)
1726 			if(hwnd) {
1727 				return;
1728 			}
1729 		painter.drawThemed(&paintContent); // note this refers to the following overload
1730 	}
1731 
1732 	/++
1733 		Responsible for drawing the content as the theme engine is responsible for other elements.
1734 
1735 		$(WARNING If you override [paint], this method may never be used as it is only called from inside the default implementation of `paint`.)
1736 
1737 		Params:
1738 			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.
1739 
1740 			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.
1741 
1742 			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.
1743 
1744 		Returns:
1745 			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.
1746 
1747 		History:
1748 			Added May 15, 2021
1749 	+/
1750 	Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
1751 		return bounds;
1752 	}
1753 
1754 	deprecated("Change ScreenPainter to WidgetPainter")
1755 	final void paint(ScreenPainter) { assert(0, "Change ScreenPainter to WidgetPainter and recompile your code"); }
1756 
1757 	/// I don't actually like the name of this
1758 	/// this draws a background on it
1759 	void erase(WidgetPainter painter) {
1760 		version(win32_widgets)
1761 			if(hwnd) return; // Windows will do it. I think.
1762 
1763 		auto c = getComputedStyle().background.color;
1764 		painter.fillColor = c;
1765 		painter.outlineColor = c;
1766 
1767 		version(win32_widgets) {
1768 			HANDLE b, p;
1769 			if(c.a == 0 && parent is parentWindow) {
1770 				// I don't remember why I had this really...
1771 				b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
1772 				p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
1773 			}
1774 		}
1775 		painter.drawRectangle(Point(0, 0), width, height);
1776 		version(win32_widgets) {
1777 			if(c.a == 0 && parent is parentWindow) {
1778 				SelectObject(painter.impl.hdc, p);
1779 				SelectObject(painter.impl.hdc, b);
1780 			}
1781 		}
1782 	}
1783 
1784 	///
1785 	WidgetPainter draw() {
1786 		int x = this.x, y = this.y;
1787 		auto parent = this.parent;
1788 		while(parent) {
1789 			x += parent.x;
1790 			y += parent.y;
1791 			parent = parent.parent;
1792 		}
1793 
1794 		auto painter = parentWindow.win.draw(true);
1795 		painter.originX = x;
1796 		painter.originY = y;
1797 		painter.setClipRectangle(Point(0, 0), width, height);
1798 		return WidgetPainter(painter, this);
1799 	}
1800 
1801 	/// 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.
1802 	protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force, bool invalidate) {
1803 		if(hidden)
1804 			return;
1805 
1806 		int paintX = x;
1807 		int paintY = y;
1808 		if(this.useNativeDrawing()) {
1809 			paintX = 0;
1810 			paintY = 0;
1811 			lox = 0;
1812 			loy = 0;
1813 			containment = Rectangle(0, 0, int.max, int.max);
1814 		}
1815 
1816 		painter.originX = lox + paintX;
1817 		painter.originY = loy + paintY;
1818 
1819 		bool actuallyPainted = false;
1820 
1821 		const clip = containment.intersectionOf(Rectangle(Point(lox + paintX, loy + paintY), Size(width, height)));
1822 		if(clip == Rectangle.init) {
1823 			//import std.stdio; writeln(this, " clipped out");
1824 			return;
1825 		}
1826 
1827 		bool invalidateChildren = invalidate;
1828 
1829 		if(redrawRequested || force) {
1830 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
1831 
1832 			painter.drawingUpon = this;
1833 
1834 			erase(painter);
1835 			if(painter.visualTheme)
1836 				painter.visualTheme.doPaint(this, painter);
1837 			else
1838 				paint(painter);
1839 
1840 			if(invalidate) {
1841 				// sdpyPrintDebugString("invalidate " ~ typeid(this).name);
1842 				auto region = Rectangle(Point(clip.upperLeft.x - painter.originX, clip.upperRight.y - painter.originY), Size(clip.width, clip.height));
1843 				painter.invalidateRect(region);
1844 				// children are contained inside this, so no need to do extra work
1845 				invalidateChildren = false;
1846 			}
1847 
1848 			redrawRequested = false;
1849 			actuallyPainted = true;
1850 		}
1851 
1852 		foreach(child; children) {
1853 			version(win32_widgets)
1854 				if(child.useNativeDrawing()) continue;
1855 			child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted, invalidateChildren);
1856 		}
1857 
1858 		version(win32_widgets)
1859 		foreach(child; children) {
1860 			if(child.useNativeDrawing) {
1861 				painter = WidgetPainter(child.simpleWindowWrappingHwnd.draw(true), child);
1862 				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
1863 			}
1864 		}
1865 	}
1866 
1867 	protected bool useNativeDrawing() nothrow {
1868 		version(win32_widgets)
1869 			return hwnd !is null;
1870 		else
1871 			return false;
1872 	}
1873 
1874 	private static class RedrawEvent {}
1875 	private __gshared re = new RedrawEvent();
1876 
1877 	private bool redrawRequested;
1878 	///
1879 	final void redraw(string file = __FILE__, size_t line = __LINE__) {
1880 		redrawRequested = true;
1881 
1882 		if(this.parentWindow) {
1883 			auto sw = this.parentWindow.win;
1884 			assert(sw !is null);
1885 			if(!sw.eventQueued!RedrawEvent) {
1886 				sw.postEvent(re);
1887 				// import std.stdio; writeln("redraw requested from ", file,":",line," ", this.parentWindow.win.impl.window);
1888 			}
1889 		}
1890 	}
1891 
1892 	private SimpleWindow drawableWindow;
1893 
1894 	/++
1895 		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.
1896 
1897 		Returns:
1898 			`true` if you should do your default behavior.
1899 
1900 		History:
1901 			Added May 5, 2021
1902 
1903 		Bugs:
1904 			It does not do the static checks on gdc right now.
1905 	+/
1906 	final protected bool emit(EventType, this This, Args...)(Args args) {
1907 		version(GNU) {} else
1908 		static assert(classStaticallyEmits!(This, EventType), "The " ~ This.stringof ~ " class is not declared to emit " ~ EventType.stringof);
1909 		auto e = new EventType(this, args);
1910 		e.dispatch();
1911 		return !e.defaultPrevented;
1912 	}
1913 	/// ditto
1914 	final protected bool emit(string eventString, this This)() {
1915 		auto e = new Event(eventString, this);
1916 		e.dispatch();
1917 		return !e.defaultPrevented;
1918 	}
1919 
1920 	/++
1921 		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.
1922 
1923 		History:
1924 			Added May 5, 2021
1925 	+/
1926 	final public EventListener subscribe(EventType, this This)(void delegate(EventType) handler) {
1927 		static assert(classStaticallyEmits!(This, EventType), "The " ~ This.stringof ~ " class is not declared to emit " ~ EventType.stringof);
1928 		return addEventListener(handler);
1929 	}
1930 
1931 	/++
1932 		Gets the computed style properties from the visual theme.
1933 
1934 		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].)
1935 
1936 		History:
1937 			Added May 8, 2021
1938 	+/
1939 	final StyleInformation getComputedStyle() {
1940 		return StyleInformation(this);
1941 	}
1942 
1943 	int focusableWidgets(scope int delegate(Widget) dg) {
1944 		foreach(widget; WidgetStream(this)) {
1945 			if(widget.tabStop && !widget.hidden) {
1946 				int result = dg(widget);
1947 				if (result)
1948 					return result;
1949 			}
1950 		}
1951 		return 0;
1952 	}
1953 
1954 
1955 	// FIXME: I kinda want to hide events from implementation widgets
1956 	// so it just catches them all and stops propagation...
1957 	// i guess i can do it with a event listener on star.
1958 
1959 	mixin Emits!KeyDownEvent; ///
1960 	mixin Emits!KeyUpEvent; ///
1961 	mixin Emits!CharEvent; ///
1962 
1963 	mixin Emits!MouseDownEvent; ///
1964 	mixin Emits!MouseUpEvent; ///
1965 	mixin Emits!ClickEvent; ///
1966 	mixin Emits!DoubleClickEvent; ///
1967 	mixin Emits!MouseMoveEvent; ///
1968 	mixin Emits!MouseOverEvent; ///
1969 	mixin Emits!MouseOutEvent; ///
1970 	mixin Emits!MouseEnterEvent; ///
1971 	mixin Emits!MouseLeaveEvent; ///
1972 
1973 	mixin Emits!ResizeEvent; ///
1974 
1975 	mixin Emits!BlurEvent; ///
1976 	mixin Emits!FocusEvent; ///
1977 
1978 	mixin Emits!FocusInEvent; ///
1979 	mixin Emits!FocusOutEvent; ///
1980 }
1981 
1982 /+
1983 /++
1984 	Interface to indicate that the widget has a simple value property.
1985 
1986 	History:
1987 		Added August 26, 2021
1988 +/
1989 interface HasValue!T {
1990 	/// Getter
1991 	@property T value();
1992 	/// Setter
1993 	@property void value(T);
1994 }
1995 
1996 /++
1997 	Interface to indicate that the widget has a range of possible values for its simple value property.
1998 	This would be present on something like a slider or possibly a number picker.
1999 
2000 	History:
2001 		Added September 11, 2021
2002 +/
2003 interface HasRangeOfValues!T : HasValue!T {
2004 	/// The minimum and maximum values in the range, inclusive.
2005 	@property T minValue();
2006 	@property void minValue(T); /// ditto
2007 	@property T maxValue(); /// ditto
2008 	@property void maxValue(T); /// ditto
2009 
2010 	/// The smallest step the user interface allows. User may still type in values without this limitation.
2011 	@property void step(T);
2012 	@property T step(); /// ditto
2013 }
2014 
2015 /++
2016 	Interface to indicate that the widget has a list of possible values the user can choose from.
2017 	This would be present on something like a drop-down selector.
2018 
2019 	The value is NOT necessarily one of the items on the list. Consider the case of a free-entry
2020 	combobox.
2021 
2022 	History:
2023 		Added September 11, 2021
2024 +/
2025 interface HasListOfValues!T : HasValue!T {
2026 	@property T[] values;
2027 	@property void values(T[]);
2028 
2029 	@property int selectedIndex(); // note it may return -1!
2030 	@property void selectedIndex(int);
2031 }
2032 +/
2033 
2034 /++
2035 	History:
2036 		Added September 2021 (dub v10.4)
2037 +/
2038 class GridLayout : Layout {
2039 
2040 	// 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.
2041 
2042 	/++
2043 		If a widget is too small to fill a grid cell, the graviy tells where it "sticks" to.
2044 	+/
2045 	enum Gravity {
2046 		Center    = 0,
2047 		NorthWest = North | West,
2048 		North     = 0b10_00,
2049 		NorthEast = North | East,
2050 		West      = 0b00_10,
2051 		East      = 0b00_01,
2052 		SouthWest = South | West,
2053 		South     = 0b01_00,
2054 		SouthEast = South | East,
2055 	}
2056 
2057 	/++
2058 		The width and height are in some proportional units and can often just be 12.
2059 	+/
2060 	this(int width, int height, Widget parent) {
2061 		this.gridWidth = width;
2062 		this.gridHeight = height;
2063 		super(parent);
2064 	}
2065 
2066 	/++
2067 		Sets the position of the given child.
2068 
2069 		The units of these arguments are in the proportional grid units you set in the constructor.
2070 	+/
2071 	Widget setChildPosition(return Widget child, int x, int y, int width, int height, Gravity gravity = Gravity.Center) {
2072 		// ensure it is in bounds
2073 		// then ensure no overlaps
2074 
2075 		ChildPosition p = ChildPosition(child, x, y, width, height, gravity);
2076 
2077 		foreach(ref position; positions) {
2078 			if(position.widget is child) {
2079 				position = p;
2080 				goto set;
2081 			}
2082 		}
2083 
2084 		positions ~= p;
2085 
2086 		set:
2087 
2088 		// FIXME: should this batch?
2089 		queueRecomputeChildLayout();
2090 
2091 		return child;
2092 	}
2093 
2094 	override void addChild(Widget w, int position = int.max) {
2095 		super.addChild(w, position);
2096 		//positions ~= ChildPosition(w);
2097 		if(position != int.max) {
2098 			// FIXME: align it so they actually match.
2099 		}
2100 	}
2101 
2102 	override void widgetRemoved(size_t idx, Widget w) {
2103 		// FIXME: keep the positions array aligned
2104 		// positions[idx].widget = null;
2105 	}
2106 
2107 	override void recomputeChildLayout() {
2108 		registerMovement();
2109 		int onGrid = cast(int) positions.length;
2110 		c: foreach(child; children) {
2111 			// just snap it to the grid
2112 			if(onGrid)
2113 			foreach(position; positions)
2114 				if(position.widget is child) {
2115 					child.x = this.width * position.x / this.gridWidth;
2116 					child.y = this.height * position.y / this.gridHeight;
2117 					child.width = this.width * position.width / this.gridWidth;
2118 					child.height = this.height * position.height / this.gridHeight;
2119 
2120 					auto diff = child.width - child.maxWidth();
2121 					// FIXME: gravity?
2122 					if(diff > 0) {
2123 						child.width = child.width - diff;
2124 
2125 						if(position.gravity & Gravity.West) {
2126 							// nothing needed, already aligned
2127 						} else if(position.gravity & Gravity.East) {
2128 							child.x += diff;
2129 						} else {
2130 							child.x += diff / 2;
2131 						}
2132 					}
2133 
2134 					diff = child.height - child.maxHeight();
2135 					// FIXME: gravity?
2136 					if(diff > 0) {
2137 						child.height = child.height - diff;
2138 
2139 						if(position.gravity & Gravity.North) {
2140 							// nothing needed, already aligned
2141 						} else if(position.gravity & Gravity.South) {
2142 							child.y += diff;
2143 						} else {
2144 							child.y += diff / 2;
2145 						}
2146 					}
2147 
2148 
2149 					child.recomputeChildLayout();
2150 					onGrid--;
2151 					continue c;
2152 				}
2153 			// the position isn't given on the grid array, we'll just fill in from where the explicit ones left off.
2154 		}
2155 	}
2156 
2157 	private struct ChildPosition {
2158 		Widget widget;
2159 		int x;
2160 		int y;
2161 		int width;
2162 		int height;
2163 		Gravity gravity;
2164 	}
2165 	private ChildPosition[] positions;
2166 
2167 	int gridWidth = 12;
2168 	int gridHeight = 12;
2169 }
2170 
2171 ///
2172 abstract class ComboboxBase : Widget {
2173 	// if the user can enter arbitrary data, we want to use  2 == CBS_DROPDOWN
2174 	// or to always show the list, we want CBS_SIMPLE == 1
2175 	version(win32_widgets)
2176 		this(uint style, Widget parent) {
2177 			super(parent);
2178 			createWin32Window(this, "ComboBox"w, null, style);
2179 		}
2180 	else version(custom_widgets)
2181 		this(Widget parent) {
2182 			super(parent);
2183 
2184 			addEventListener((KeyDownEvent event) {
2185 				if(event.key == Key.Up) {
2186 					if(selection_ > -1) { // -1 means select blank
2187 						selection_--;
2188 						fireChangeEvent();
2189 					}
2190 					event.preventDefault();
2191 				}
2192 				if(event.key == Key.Down) {
2193 					if(selection_ + 1 < options.length) {
2194 						selection_++;
2195 						fireChangeEvent();
2196 					}
2197 					event.preventDefault();
2198 				}
2199 
2200 			});
2201 
2202 		}
2203 	else static assert(false);
2204 
2205 	/++
2206 		Returns the current list of options in the selection.
2207 
2208 		History:
2209 			Property accessor added March 1, 2022 (dub v10.7). Prior to that, it was private.
2210 	+/
2211 	final @property string[] options() const {
2212 		return cast(string[]) options_;
2213 	}
2214 
2215 	private string[] options_;
2216 	private int selection_ = -1;
2217 
2218 	/++
2219 		Adds an option to the end of options array.
2220 	+/
2221 	void addOption(string s) {
2222 		options_ ~= s;
2223 		version(win32_widgets)
2224 		SendMessageW(hwnd, 323 /*CB_ADDSTRING*/, 0, cast(LPARAM) toWstringzInternal(s));
2225 	}
2226 
2227 	/++
2228 		Gets the current selection as an index into the [options] array. Returns -1 if nothing is selected.
2229 	+/
2230 	int getSelection() {
2231 		return selection_;
2232 	}
2233 
2234 	/++
2235 		Returns the current selection as a string.
2236 
2237 		History:
2238 			Added November 17, 2021
2239 	+/
2240 	string getSelectionString() {
2241 		return selection_ == -1 ? null : options[selection_];
2242 	}
2243 
2244 	/++
2245 		Sets the current selection to an index in the options array, or to the given option if present.
2246 		Please note that the string version may do a linear lookup.
2247 
2248 		Returns:
2249 			the index you passed in
2250 
2251 		History:
2252 			The `string` based overload was added on March 1, 2022 (dub v10.7).
2253 
2254 			The return value was `void` prior to March 1, 2022.
2255 	+/
2256 	int setSelection(int idx) {
2257 		selection_ = idx;
2258 		version(win32_widgets)
2259 		SendMessageW(hwnd, 334 /*CB_SETCURSEL*/, idx, 0);
2260 
2261 		auto t = new SelectionChangedEvent(this, selection_, selection_ == -1 ? null : options[selection_]);
2262 		t.dispatch();
2263 
2264 		return idx;
2265 	}
2266 
2267 	/// ditto
2268 	int setSelection(string s) {
2269 		if(s !is null)
2270 		foreach(idx, item; options)
2271 			if(item == s) {
2272 				return setSelection(cast(int) idx);
2273 			}
2274 		return setSelection(-1);
2275 	}
2276 
2277 	/++
2278 		This event is fired when the selection changes. Note it inherits
2279 		from ChangeEvent!string, meaning you can use that as well, and it also
2280 		fills in [Event.intValue].
2281 	+/
2282 	static class SelectionChangedEvent : ChangeEvent!string {
2283 		this(Widget target, int iv, string sv) {
2284 			super(target, &stringValue);
2285 			this.iv = iv;
2286 			this.sv = sv;
2287 		}
2288 		immutable int iv;
2289 		immutable string sv;
2290 
2291 		override @property string stringValue() { return sv; }
2292 		override @property int intValue() { return iv; }
2293 	}
2294 
2295 	version(win32_widgets)
2296 	override void handleWmCommand(ushort cmd, ushort id) {
2297 		if(cmd == CBN_SELCHANGE) {
2298 			selection_ = cast(int) SendMessageW(hwnd, 327 /* CB_GETCURSEL */, 0, 0);
2299 			fireChangeEvent();
2300 		}
2301 	}
2302 
2303 	private void fireChangeEvent() {
2304 		if(selection_ >= options.length)
2305 			selection_ = -1;
2306 
2307 		auto t = new SelectionChangedEvent(this, selection_, selection_ == -1 ? null : options[selection_]);
2308 		t.dispatch();
2309 	}
2310 
2311 	version(win32_widgets) {
2312 		override int minHeight() { return defaultLineHeight + 6; }
2313 		override int maxHeight() { return defaultLineHeight + 6; }
2314 	} else {
2315 		override int minHeight() { return defaultLineHeight + 4; }
2316 		override int maxHeight() { return defaultLineHeight + 4; }
2317 	}
2318 
2319 	version(custom_widgets) {
2320 
2321 		// FIXME: this should scroll if there's too many elements to reasonably fit on screen
2322 
2323 		SimpleWindow dropDown;
2324 		void popup() {
2325 			auto w = width;
2326 			// FIXME: suggestedDropdownHeight see below
2327 			auto h = cast(int) this.options.length * defaultLineHeight + 8;
2328 
2329 			auto coord = this.globalCoordinates();
2330 			auto dropDown = new SimpleWindow(
2331 				w, h,
2332 				null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.dropdownMenu, WindowFlags.dontAutoShow, parentWindow ? parentWindow.win : null);
2333 
2334 			dropDown.move(coord.x, coord.y + this.height);
2335 
2336 			{
2337 				auto cs = getComputedStyle();
2338 				auto painter = dropDown.draw();
2339 				draw3dFrame(0, 0, w, h, painter, FrameStyle.risen, getComputedStyle().background.color);
2340 				auto p = Point(4, 4);
2341 				painter.outlineColor = cs.foregroundColor;
2342 				foreach(option; options) {
2343 					painter.drawText(p, option);
2344 					p.y += defaultLineHeight;
2345 				}
2346 			}
2347 
2348 			dropDown.setEventHandlers(
2349 				(MouseEvent event) {
2350 					if(event.type == MouseEventType.buttonReleased) {
2351 						dropDown.close();
2352 						auto element = (event.y - 4) / defaultLineHeight;
2353 						if(element >= 0 && element <= options.length) {
2354 							selection_ = element;
2355 
2356 							fireChangeEvent();
2357 						}
2358 					}
2359 				}
2360 			);
2361 
2362 			dropDown.visibilityChanged = (bool visible) {
2363 				if(visible) {
2364 					this.redraw();
2365 					dropDown.grabInput();
2366 				} else {
2367 					dropDown.releaseInputGrab();
2368 				}
2369 			};
2370 
2371 			dropDown.show();
2372 		}
2373 
2374 	}
2375 }
2376 
2377 /++
2378 	A drop-down list where the user must select one of the
2379 	given options. Like `<select>` in HTML.
2380 +/
2381 class DropDownSelection : ComboboxBase {
2382 	this(Widget parent) {
2383 		version(win32_widgets)
2384 			super(3 /* CBS_DROPDOWNLIST */ | WS_VSCROLL, parent);
2385 		else version(custom_widgets) {
2386 			super(parent);
2387 
2388 			addEventListener("focus", () { this.redraw; });
2389 			addEventListener("blur", () { this.redraw; });
2390 			addEventListener(EventType.change, () { this.redraw; });
2391 			addEventListener("mousedown", () { this.focus(); this.popup(); });
2392 			addEventListener((KeyDownEvent event) {
2393 				if(event.key == Key.Space)
2394 					popup();
2395 			});
2396 		} else static assert(false);
2397 	}
2398 
2399 	mixin Padding!q{2};
2400 	static class Style : Widget.Style {
2401 		override FrameStyle borderStyle() { return FrameStyle.risen; }
2402 	}
2403 	mixin OverrideStyle!Style;
2404 
2405 	version(custom_widgets)
2406 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
2407 		auto cs = getComputedStyle();
2408 
2409 		painter.drawText(bounds.upperLeft, selection_ == -1 ? "" : options[selection_]);
2410 
2411 		painter.outlineColor = cs.foregroundColor;
2412 		painter.fillColor = cs.foregroundColor;
2413 		Point[4] triangle;
2414 		enum padding = 6;
2415 		enum paddingV = 7;
2416 		enum triangleWidth = 10;
2417 		triangle[0] = Point(width - padding - triangleWidth, paddingV);
2418 		triangle[1] = Point(width - padding - triangleWidth / 2, height - paddingV);
2419 		triangle[2] = Point(width - padding - 0, paddingV);
2420 		triangle[3] = triangle[0];
2421 		painter.drawPolygon(triangle[]);
2422 
2423 		return bounds;
2424 	}
2425 
2426 	version(win32_widgets)
2427 	override void registerMovement() {
2428 		version(win32_widgets) {
2429 			if(hwnd) {
2430 				auto pos = getChildPositionRelativeToParentHwnd(this);
2431 				// the height given to this from Windows' perspective is supposed
2432 				// to include the drop down's height. so I add to it to give some
2433 				// room for that.
2434 				// FIXME: maybe make the subclass provide a suggestedDropdownHeight thing
2435 				MoveWindow(hwnd, pos[0], pos[1], width, height + 200, true);
2436 			}
2437 		}
2438 		sendResizeEvent();
2439 	}
2440 }
2441 
2442 /++
2443 	A text box with a drop down arrow listing selections.
2444 	The user can choose from the list, or type their own.
2445 +/
2446 class FreeEntrySelection : ComboboxBase {
2447 	this(Widget parent) {
2448 		version(win32_widgets)
2449 			super(2 /* CBS_DROPDOWN */, parent);
2450 		else version(custom_widgets) {
2451 			super(parent);
2452 			auto hl = new HorizontalLayout(this);
2453 			lineEdit = new LineEdit(hl);
2454 
2455 			tabStop = false;
2456 
2457 			lineEdit.addEventListener("focus", &lineEdit.selectAll);
2458 
2459 			auto btn = new class ArrowButton {
2460 				this() {
2461 					super(ArrowDirection.down, hl);
2462 				}
2463 				override int maxHeight() {
2464 					return int.max;
2465 				}
2466 			};
2467 			//btn.addDirectEventListener("focus", &lineEdit.focus);
2468 			btn.addEventListener("triggered", &this.popup);
2469 			addEventListener(EventType.change, (Event event) {
2470 				lineEdit.content = event.stringValue;
2471 				lineEdit.focus();
2472 				redraw();
2473 			});
2474 		}
2475 		else static assert(false);
2476 	}
2477 
2478 	version(custom_widgets) {
2479 		LineEdit lineEdit;
2480 	}
2481 }
2482 
2483 /++
2484 	A combination of free entry with a list below it.
2485 +/
2486 class ComboBox : ComboboxBase {
2487 	this(Widget parent) {
2488 		version(win32_widgets)
2489 			super(1 /* CBS_SIMPLE */ | CBS_NOINTEGRALHEIGHT, parent);
2490 		else version(custom_widgets) {
2491 			super(parent);
2492 			lineEdit = new LineEdit(this);
2493 			listWidget = new ListWidget(this);
2494 			listWidget.multiSelect = false;
2495 			listWidget.addEventListener(EventType.change, delegate(Widget, Event) {
2496 				string c = null;
2497 				foreach(option; listWidget.options)
2498 					if(option.selected) {
2499 						c = option.label;
2500 						break;
2501 					}
2502 				lineEdit.content = c;
2503 			});
2504 
2505 			listWidget.tabStop = false;
2506 			this.tabStop = false;
2507 			listWidget.addEventListener("focus", &lineEdit.focus);
2508 			this.addEventListener("focus", &lineEdit.focus);
2509 
2510 			addDirectEventListener(EventType.change, {
2511 				listWidget.setSelection(selection_);
2512 				if(selection_ != -1)
2513 					lineEdit.content = options[selection_];
2514 				lineEdit.focus();
2515 				redraw();
2516 			});
2517 
2518 			lineEdit.addEventListener("focus", &lineEdit.selectAll);
2519 
2520 			listWidget.addDirectEventListener(EventType.change, {
2521 				int set = -1;
2522 				foreach(idx, opt; listWidget.options)
2523 					if(opt.selected) {
2524 						set = cast(int) idx;
2525 						break;
2526 					}
2527 				if(set != selection_)
2528 					this.setSelection(set);
2529 			});
2530 		} else static assert(false);
2531 	}
2532 
2533 	override int minHeight() { return defaultLineHeight * 3; }
2534 	override int maxHeight() { return cast(int) options.length * defaultLineHeight + defaultLineHeight; }
2535 	override int heightStretchiness() { return 5; }
2536 
2537 	version(custom_widgets) {
2538 		LineEdit lineEdit;
2539 		ListWidget listWidget;
2540 
2541 		override void addOption(string s) {
2542 			listWidget.options ~= ListWidget.Option(s);
2543 			ComboboxBase.addOption(s);
2544 		}
2545 	}
2546 }
2547 
2548 /+
2549 class Spinner : Widget {
2550 	version(win32_widgets)
2551 	this(Widget parent) {
2552 		super(parent);
2553 		parentWindow = parent.parentWindow;
2554 		auto hlayout = new HorizontalLayout(this);
2555 		lineEdit = new LineEdit(hlayout);
2556 		upDownControl = new UpDownControl(hlayout);
2557 	}
2558 
2559 	LineEdit lineEdit;
2560 	UpDownControl upDownControl;
2561 }
2562 
2563 class UpDownControl : Widget {
2564 	version(win32_widgets)
2565 	this(Widget parent) {
2566 		super(parent);
2567 		parentWindow = parent.parentWindow;
2568 		createWin32Window(this, "msctls_updown32"w, null, 4/*UDS_ALIGNRIGHT*/| 2 /* UDS_SETBUDDYINT */ | 16 /* UDS_AUTOBUDDY */ | 32 /* UDS_ARROWKEYS */);
2569 	}
2570 
2571 	override int minHeight() { return defaultLineHeight; }
2572 	override int maxHeight() { return defaultLineHeight * 3/2; }
2573 
2574 	override int minWidth() { return defaultLineHeight * 3/2; }
2575 	override int maxWidth() { return defaultLineHeight * 3/2; }
2576 }
2577 +/
2578 
2579 /+
2580 class DataView : Widget {
2581 	// this is the omnibus data viewer
2582 	// the internal data layout is something like:
2583 	// string[string][] but also each node can have parents
2584 }
2585 +/
2586 
2587 
2588 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb775491(v=vs.85).aspx#PROGRESS_CLASS
2589 
2590 // http://svn.dsource.org/projects/bindings/trunk/win32/commctrl.d
2591 
2592 // FIXME: menus should prolly capture the mouse. ugh i kno.
2593 /*
2594 	TextEdit needs:
2595 
2596 	* caret manipulation
2597 	* selection control
2598 	* convenience functions for appendText, insertText, insertTextAtCaret, etc.
2599 
2600 	For example:
2601 
2602 	connect(paste, &textEdit.insertTextAtCaret);
2603 
2604 	would be nice.
2605 
2606 
2607 
2608 	I kinda want an omnibus dataview that combines list, tree,
2609 	and table - it can be switched dynamically between them.
2610 
2611 	Flattening policy: only show top level, show recursive, show grouped
2612 	List styles: plain list (e.g. <ul>), tiles (some details next to it), icons (like Windows explorer)
2613 
2614 	Single select, multi select, organization, drag+drop
2615 */
2616 
2617 //static if(UsingSimpledisplayX11)
2618 version(win32_widgets) {}
2619 else version(custom_widgets) {
2620 	enum scrollClickRepeatInterval = 50;
2621 
2622 deprecated("Get these properties off `Widget.getComputedStyle` instead. The defaults are now set in the `WidgetPainter.visualTheme`.") {
2623 	enum windowBackgroundColor = Color(212, 212, 212); // used to be 192
2624 	enum activeTabColor = lightAccentColor;
2625 	enum hoveringColor = Color(228, 228, 228);
2626 	enum buttonColor = windowBackgroundColor;
2627 	enum depressedButtonColor = darkAccentColor;
2628 	enum activeListXorColor = Color(255, 255, 127);
2629 	enum progressBarColor = Color(0, 0, 128);
2630 	enum activeMenuItemColor = Color(0, 0, 128);
2631 
2632 }}
2633 else static assert(false);
2634 deprecated("Get these properties off the `visualTheme` instead.") {
2635 	// these are used by horizontal rule so not just custom_widgets. for now at least.
2636 	enum darkAccentColor = Color(172, 172, 172);
2637 	enum lightAccentColor = Color(223, 223, 223); // used to be 223
2638 }
2639 
2640 private const(wchar)* toWstringzInternal(in char[] s) {
2641 	wchar[] str;
2642 	str.reserve(s.length + 1);
2643 	foreach(dchar ch; s)
2644 		str ~= ch;
2645 	str ~= '\0';
2646 	return str.ptr;
2647 }
2648 
2649 static if(SimpledisplayTimerAvailable)
2650 void setClickRepeat(Widget w, int interval, int delay = 250) {
2651 	Timer timer;
2652 	int delayRemaining = delay / interval;
2653 	if(delayRemaining <= 1)
2654 		delayRemaining = 2;
2655 
2656 	immutable originalDelayRemaining = delayRemaining;
2657 
2658 	w.addDirectEventListener((scope MouseDownEvent ev) {
2659 		if(ev.srcElement !is w)
2660 			return;
2661 		if(timer !is null) {
2662 			timer.destroy();
2663 			timer = null;
2664 		}
2665 		delayRemaining = originalDelayRemaining;
2666 		timer = new Timer(interval, () {
2667 			if(delayRemaining > 0)
2668 				delayRemaining--;
2669 			else {
2670 				auto ev = new Event("triggered", w);
2671 				ev.sendDirectly();
2672 			}
2673 		});
2674 	});
2675 
2676 	w.addDirectEventListener((scope MouseUpEvent ev) {
2677 		if(ev.srcElement !is w)
2678 			return;
2679 		if(timer !is null) {
2680 			timer.destroy();
2681 			timer = null;
2682 		}
2683 	});
2684 
2685 	w.addDirectEventListener((scope MouseLeaveEvent ev) {
2686 		if(ev.srcElement !is w)
2687 			return;
2688 		if(timer !is null) {
2689 			timer.destroy();
2690 			timer = null;
2691 		}
2692 	});
2693 
2694 }
2695 else
2696 void setClickRepeat(Widget w, int interval, int delay = 250) {}
2697 
2698 enum FrameStyle {
2699 	none, ///
2700 	risen, /// a 3d pop-out effect (think Windows 95 button)
2701 	sunk, /// a 3d sunken effect (think Windows 95 button as you click on it)
2702 	solid, ///
2703 	dotted, ///
2704 	fantasy, /// a style based on a popular fantasy video game
2705 }
2706 
2707 version(custom_widgets)
2708 deprecated
2709 void draw3dFrame(Widget widget, ScreenPainter painter, FrameStyle style) {
2710 	draw3dFrame(0, 0, widget.width, widget.height, painter, style, WidgetPainter.visualTheme.windowBackgroundColor);
2711 }
2712 
2713 version(custom_widgets)
2714 void draw3dFrame(Widget widget, ScreenPainter painter, FrameStyle style, Color background) {
2715 	draw3dFrame(0, 0, widget.width, widget.height, painter, style, background);
2716 }
2717 
2718 version(custom_widgets)
2719 deprecated
2720 void draw3dFrame(int x, int y, int width, int height, ScreenPainter painter, FrameStyle style) {
2721 	draw3dFrame(x, y, width, height, painter, style, WidgetPainter.visualTheme.windowBackgroundColor);
2722 }
2723 
2724 int draw3dFrame(int x, int y, int width, int height, ScreenPainter painter, FrameStyle style, Color background, Color border = Color.transparent) {
2725 	int borderWidth;
2726 	final switch(style) {
2727 		case FrameStyle.sunk, FrameStyle.risen:
2728 			// outer layer
2729 			painter.outlineColor = style == FrameStyle.sunk ? Color.white : Color.black;
2730 			borderWidth = 2;
2731 		break;
2732 		case FrameStyle.none:
2733 			painter.outlineColor = background;
2734 			borderWidth = 0;
2735 		break;
2736 		case FrameStyle.solid:
2737 			painter.pen = Pen(border, 1);
2738 			borderWidth = 1;
2739 		break;
2740 		case FrameStyle.dotted:
2741 			painter.pen = Pen(border, 1, Pen.Style.Dotted);
2742 			borderWidth = 1;
2743 		break;
2744 		case FrameStyle.fantasy:
2745 			painter.pen = Pen(border, 3);
2746 			borderWidth = 3;
2747 		break;
2748 	}
2749 
2750 	painter.fillColor = background;
2751 	painter.drawRectangle(Point(x + 0, y + 0), width, height);
2752 
2753 
2754 	if(style == FrameStyle.sunk || style == FrameStyle.risen) {
2755 		// 3d effect
2756 		auto vt = WidgetPainter.visualTheme;
2757 
2758 		painter.outlineColor = (style == FrameStyle.sunk) ? vt.darkAccentColor : vt.lightAccentColor;
2759 		painter.drawLine(Point(x + 0, y + 0), Point(x + width, y + 0));
2760 		painter.drawLine(Point(x + 0, y + 0), Point(x + 0, y + height - 1));
2761 
2762 		// inner layer
2763 		//right, bottom
2764 		painter.outlineColor = (style == FrameStyle.sunk) ? vt.lightAccentColor : vt.darkAccentColor;
2765 		painter.drawLine(Point(x + width - 2, y + 2), Point(x + width - 2, y + height - 2));
2766 		painter.drawLine(Point(x + 2, y + height - 2), Point(x + width - 2, y + height - 2));
2767 		// left, top
2768 		painter.outlineColor = (style == FrameStyle.sunk) ? Color.black : Color.white;
2769 		painter.drawLine(Point(x + 1, y + 1), Point(x + width, y + 1));
2770 		painter.drawLine(Point(x + 1, y + 1), Point(x + 1, y + height - 2));
2771 	} else if(style == FrameStyle.fantasy) {
2772 		painter.pen = Pen(Color.white, 1, Pen.Style.Solid);
2773 		painter.fillColor = Color.transparent;
2774 		painter.drawRectangle(Point(x + 1, y + 1), Point(x + width - 1, y + height - 1));
2775 	}
2776 
2777 	return borderWidth;
2778 }
2779 
2780 /++
2781 	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.
2782 
2783 	See_Also:
2784 		[MenuItem]
2785 		[ToolButton]
2786 		[Menu.addItem]
2787 +/
2788 class Action {
2789 	version(win32_widgets) {
2790 		private int id;
2791 		private static int lastId = 9000;
2792 		private static Action[int] mapping;
2793 	}
2794 
2795 	KeyEvent accelerator;
2796 
2797 	// FIXME: disable message
2798 	// and toggle thing?
2799 	// ??? and trigger arguments too ???
2800 
2801 	/++
2802 		Params:
2803 			label = the textual label
2804 			icon = icon ID. See [GenericIcons]. There is currently no way to do custom icons.
2805 			triggered = initial handler, more can be added via the [triggered] member.
2806 	+/
2807 	this(string label, ushort icon = 0, void delegate() triggered = null) {
2808 		this.label = label;
2809 		this.iconId = icon;
2810 		if(triggered !is null)
2811 			this.triggered ~= triggered;
2812 		version(win32_widgets) {
2813 			id = ++lastId;
2814 			mapping[id] = this;
2815 		}
2816 	}
2817 
2818 	private string label;
2819 	private ushort iconId;
2820 	// icon
2821 
2822 	// when it is triggered, the triggered event is fired on the window
2823 	/// The list of handlers when it is triggered.
2824 	void delegate()[] triggered;
2825 }
2826 
2827 /*
2828 	plan:
2829 		keyboard accelerators
2830 
2831 		* menus (and popups and tooltips)
2832 		* status bar
2833 		* toolbars and buttons
2834 
2835 		sortable table view
2836 
2837 		maybe notification area icons
2838 		basic clipboard
2839 
2840 		* radio box
2841 		splitter
2842 		toggle buttons (optionally mutually exclusive, like in Paint)
2843 		label, rich text display, multi line plain text (selectable)
2844 		* fieldset
2845 		* nestable grid layout
2846 		single line text input
2847 		* multi line text input
2848 		slider
2849 		spinner
2850 		list box
2851 		drop down
2852 		combo box
2853 		auto complete box
2854 		* progress bar
2855 
2856 		terminal window/widget (on unix it might even be a pty but really idk)
2857 
2858 		ok button
2859 		cancel button
2860 
2861 		keyboard hotkeys
2862 
2863 		scroll widget
2864 
2865 		event redirections and network transparency
2866 		script integration
2867 */
2868 
2869 
2870 /*
2871 	MENUS
2872 
2873 	auto bar = new MenuBar(window);
2874 	window.menuBar = bar;
2875 
2876 	auto fileMenu = bar.addItem(new Menu("&File"));
2877 	fileMenu.addItem(new MenuItem("&Exit"));
2878 
2879 
2880 	EVENTS
2881 
2882 	For controls, you should usually use "triggered" rather than "click", etc., because
2883 	triggered handles both keyboard (focus and press as well as hotkeys) and mouse activation.
2884 	This is the case on menus and pushbuttons.
2885 
2886 	"click", on the other hand, currently only fires when it is literally clicked by the mouse.
2887 */
2888 
2889 
2890 /*
2891 enum LinePreference {
2892 	AlwaysOnOwnLine, // always on its own line
2893 	PreferOwnLine, // it will always start a new line, and if max width <= line width, it will expand all the way
2894 	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
2895 }
2896 */
2897 
2898 /++
2899 	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.
2900 
2901 	---
2902 	class MyWidget : Widget {
2903 		this(Widget parent) { super(parent); }
2904 
2905 		// set paddingLeft, paddingRight, paddingTop, and paddingBottom all to `return 4;` in one go:
2906 		mixin Padding!q{4};
2907 
2908 		// set marginLeft, marginRight, marginTop, and marginBottom all to `return 8;` in one go:
2909 		mixin Margin!q{8};
2910 
2911 		// but if I specify one outside, it overrides the override, so now marginLeft is 2,
2912 		// while Top/Bottom/Right remain 8 from the mixin above.
2913 		override int marginLeft() { return 2; }
2914 	}
2915 	---
2916 
2917 
2918 	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]).
2919 
2920 	Padding is the area inside a widget where its background is drawn, but the content avoids.
2921 
2922 	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!).
2923 
2924 	* 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.
2925 +/
2926 mixin template Padding(string code) {
2927 	override int paddingLeft() { return mixin(code);}
2928 	override int paddingRight() { return mixin(code);}
2929 	override int paddingTop() { return mixin(code);}
2930 	override int paddingBottom() { return mixin(code);}
2931 }
2932 
2933 /// ditto
2934 mixin template Margin(string code) {
2935 	override int marginLeft() { return mixin(code);}
2936 	override int marginRight() { return mixin(code);}
2937 	override int marginTop() { return mixin(code);}
2938 	override int marginBottom() { return mixin(code);}
2939 }
2940 
2941 private
2942 void recomputeChildLayout(string relevantMeasure)(Widget parent) {
2943 	enum calcingV = relevantMeasure == "height";
2944 
2945 	parent.registerMovement();
2946 
2947 	if(parent.children.length == 0)
2948 		return;
2949 
2950 	auto parentStyle = parent.getComputedStyle();
2951 
2952 	enum firstThingy = relevantMeasure == "height" ? "Top" : "Left";
2953 	enum secondThingy = relevantMeasure == "height" ? "Bottom" : "Right";
2954 
2955 	enum otherFirstThingy = relevantMeasure == "height" ? "Left" : "Top";
2956 	enum otherSecondThingy = relevantMeasure == "height" ? "Right" : "Bottom";
2957 
2958 	// my own width and height should already be set by the caller of this function...
2959 	int spaceRemaining = mixin("parent." ~ relevantMeasure) -
2960 		mixin("parentStyle.padding"~firstThingy~"()") -
2961 		mixin("parentStyle.padding"~secondThingy~"()");
2962 
2963 	int stretchinessSum;
2964 	int stretchyChildSum;
2965 	int lastMargin = 0;
2966 
2967 	int shrinkinessSum;
2968 	int shrinkyChildSum;
2969 
2970 	// set initial size
2971 	foreach(child; parent.children) {
2972 
2973 		auto childStyle = child.getComputedStyle();
2974 
2975 		if(cast(StaticPosition) child)
2976 			continue;
2977 		if(child.hidden)
2978 			continue;
2979 
2980 		const iw = child.flexBasisWidth();
2981 		const ih = child.flexBasisHeight();
2982 
2983 		static if(calcingV) {
2984 			child.width = parent.width -
2985 				mixin("childStyle.margin"~otherFirstThingy~"()") -
2986 				mixin("childStyle.margin"~otherSecondThingy~"()") -
2987 				mixin("parentStyle.padding"~otherFirstThingy~"()") -
2988 				mixin("parentStyle.padding"~otherSecondThingy~"()");
2989 
2990 			if(child.width < 0)
2991 				child.width = 0;
2992 			if(child.width > childStyle.maxWidth())
2993 				child.width = childStyle.maxWidth();
2994 
2995 			if(iw > 0) {
2996 				auto totalPossible = child.width;
2997 				if(child.width > iw && child.widthStretchiness() == 0)
2998 					child.width = iw;
2999 			}
3000 
3001 			child.height = mymax(childStyle.minHeight(), ih);
3002 		} else {
3003 			// set to take all the space
3004 			child.height = parent.height -
3005 				mixin("childStyle.margin"~firstThingy~"()") -
3006 				mixin("childStyle.margin"~secondThingy~"()") -
3007 				mixin("parentStyle.padding"~firstThingy~"()") -
3008 				mixin("parentStyle.padding"~secondThingy~"()");
3009 
3010 			// then clamp it
3011 			if(child.height < 0)
3012 				child.height = 0;
3013 			if(child.height > childStyle.maxHeight())
3014 				child.height = childStyle.maxHeight();
3015 
3016 			// and if possible, respect the ideal target
3017 			if(ih > 0) {
3018 				auto totalPossible = child.height;
3019 				if(child.height > ih && child.heightStretchiness() == 0)
3020 					child.height = ih;
3021 			}
3022 
3023 			// if we have an ideal, try to respect it, otehrwise, just use the minimum
3024 			child.width = mymax(childStyle.minWidth(), iw);
3025 		}
3026 
3027 		spaceRemaining -= mixin("child." ~ relevantMeasure);
3028 
3029 		int thisMargin = mymax(lastMargin, mixin("childStyle.margin"~firstThingy~"()"));
3030 		auto margin = mixin("childStyle.margin" ~ secondThingy ~ "()");
3031 		lastMargin = margin;
3032 		spaceRemaining -= thisMargin + margin;
3033 
3034 		auto s = mixin("child." ~ relevantMeasure ~ "Stretchiness()");
3035 		stretchinessSum += s;
3036 		if(s > 0)
3037 			stretchyChildSum++;
3038 
3039 		auto s2 = mixin("child." ~ relevantMeasure ~ "Shrinkiness()");
3040 		shrinkinessSum += s2;
3041 		if(s2 > 0)
3042 			shrinkyChildSum++;
3043 	}
3044 
3045 	if(spaceRemaining < 0 && shrinkyChildSum) {
3046 		// shrink to get into the space if it is possible
3047 		auto toRemove = -spaceRemaining;
3048 		auto removalPerItem  = toRemove * shrinkinessSum / shrinkyChildSum;
3049 		auto remainder = toRemove * shrinkinessSum % shrinkyChildSum;
3050 
3051 		// FIXME: wtf why am i shrinking things with no shrinkiness?
3052 
3053 		foreach(child; parent.children) {
3054 			auto childStyle = child.getComputedStyle();
3055 			if(cast(StaticPosition) child)
3056 				continue;
3057 			if(child.hidden)
3058 				continue;
3059 			static if(calcingV) {
3060 				auto maximum = childStyle.maxHeight();
3061 			} else {
3062 				auto maximum = childStyle.maxWidth();
3063 			}
3064 
3065 			mixin("child._" ~ relevantMeasure) -= removalPerItem + remainder; // this is removing more than needed to trigger the next thing. ugh.
3066 
3067 			spaceRemaining += removalPerItem + remainder;
3068 		}
3069 	}
3070 
3071 	// stretch to fill space
3072 	while(spaceRemaining > 0 && stretchinessSum && stretchyChildSum) {
3073 		auto spacePerChild = spaceRemaining / stretchinessSum;
3074 		bool spreadEvenly;
3075 		bool giveToBiggest;
3076 		if(spacePerChild <= 0) {
3077 			spacePerChild = spaceRemaining / stretchyChildSum;
3078 			spreadEvenly = true;
3079 		}
3080 		if(spacePerChild <= 0) {
3081 			giveToBiggest = true;
3082 		}
3083 		int previousSpaceRemaining = spaceRemaining;
3084 		stretchinessSum = 0;
3085 		Widget mostStretchy;
3086 		int mostStretchyS;
3087 		foreach(child; parent.children) {
3088 			auto childStyle = child.getComputedStyle();
3089 			if(cast(StaticPosition) child)
3090 				continue;
3091 			if(child.hidden)
3092 				continue;
3093 			static if(calcingV) {
3094 				auto maximum = childStyle.maxHeight();
3095 			} else {
3096 				auto maximum = childStyle.maxWidth();
3097 			}
3098 
3099 			if(mixin("child." ~ relevantMeasure) >= maximum) {
3100 				auto adj = mixin("child." ~ relevantMeasure) - maximum;
3101 				mixin("child._" ~ relevantMeasure) -= adj;
3102 				spaceRemaining += adj;
3103 				continue;
3104 			}
3105 			auto s = mixin("child." ~ relevantMeasure ~ "Stretchiness()");
3106 			if(s <= 0)
3107 				continue;
3108 			auto spaceAdjustment = spacePerChild * (spreadEvenly ? 1 : s);
3109 			mixin("child._" ~ relevantMeasure) += spaceAdjustment;
3110 			spaceRemaining -= spaceAdjustment;
3111 			if(mixin("child." ~ relevantMeasure) > maximum) {
3112 				auto diff = mixin("child." ~ relevantMeasure) - maximum;
3113 				mixin("child._" ~ relevantMeasure) -= diff;
3114 				spaceRemaining += diff;
3115 			} else if(mixin("child._" ~ relevantMeasure) < maximum) {
3116 				stretchinessSum += mixin("child." ~ relevantMeasure ~ "Stretchiness()");
3117 				if(mostStretchy is null || s >= mostStretchyS) {
3118 					mostStretchy = child;
3119 					mostStretchyS = s;
3120 				}
3121 			}
3122 		}
3123 
3124 		if(giveToBiggest && mostStretchy !is null) {
3125 			auto child = mostStretchy;
3126 			auto childStyle = child.getComputedStyle();
3127 			int spaceAdjustment = spaceRemaining;
3128 
3129 			static if(calcingV)
3130 				auto maximum = childStyle.maxHeight();
3131 			else
3132 				auto maximum = childStyle.maxWidth();
3133 
3134 			mixin("child._" ~ relevantMeasure) += spaceAdjustment;
3135 			spaceRemaining -= spaceAdjustment;
3136 			if(mixin("child._" ~ relevantMeasure) > maximum) {
3137 				auto diff = mixin("child." ~ relevantMeasure) - maximum;
3138 				mixin("child._" ~ relevantMeasure) -= diff;
3139 				spaceRemaining += diff;
3140 			}
3141 		}
3142 
3143 		if(spaceRemaining == previousSpaceRemaining)
3144 			break; // apparently nothing more we can do
3145 
3146 	}
3147 
3148 	// position
3149 	lastMargin = 0;
3150 	int currentPos = mixin("parent.padding"~firstThingy~"()");
3151 	foreach(child; parent.children) {
3152 		auto childStyle = child.getComputedStyle();
3153 		if(cast(StaticPosition) child) {
3154 			child.recomputeChildLayout();
3155 			continue;
3156 		}
3157 		if(child.hidden)
3158 			continue;
3159 		auto margin = mixin("childStyle.margin" ~ secondThingy ~ "()");
3160 		int thisMargin = mymax(lastMargin, mixin("childStyle.margin"~firstThingy~"()"));
3161 		currentPos += thisMargin;
3162 		static if(calcingV) {
3163 			child.x = parentStyle.paddingLeft() + childStyle.marginLeft();
3164 			child.y = currentPos;
3165 		} else {
3166 			child.x = currentPos;
3167 			child.y = parentStyle.paddingTop() + childStyle.marginTop();
3168 
3169 		}
3170 		currentPos += mixin("child." ~ relevantMeasure);
3171 		currentPos += margin;
3172 		lastMargin = margin;
3173 
3174 		child.recomputeChildLayout();
3175 	}
3176 }
3177 
3178 int mymax(int a, int b) { return a > b ? a : b; }
3179 int mymax(int a, int b, int c) {
3180 	auto d = mymax(a, b);
3181 	return c > d ? c : d;
3182 }
3183 
3184 // OK so we need to make getting at the native window stuff possible in simpledisplay.d
3185 // and here, it must be integrable with the layout, the event system, and not be painted over.
3186 version(win32_widgets) {
3187 
3188 	// this function just does stuff that a parent window needs for redirection
3189 	int WindowProcedureHelper(Widget this_, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
3190 		this_.hookedWndProc(msg, wParam, lParam);
3191 
3192 		switch(msg) {
3193 
3194 			case WM_VSCROLL, WM_HSCROLL:
3195 				auto pos = HIWORD(wParam);
3196 				auto m = LOWORD(wParam);
3197 
3198 				auto scrollbarHwnd = cast(HWND) lParam;
3199 
3200 				if(auto widgetp = scrollbarHwnd in Widget.nativeMapping) {
3201 
3202 					//auto smw = cast(ScrollMessageWidget) widgetp.parent;
3203 
3204 					switch(m) {
3205 						/+
3206 						// I don't think those messages are ever actually sent normally by the widget itself,
3207 						// they are more used for the keyboard interface. methinks.
3208 						case SB_BOTTOM:
3209 							//import std.stdio; writeln("end");
3210 							auto event = new Event("scrolltoend", *widgetp);
3211 							event.dispatch();
3212 							//if(!event.defaultPrevented)
3213 						break;
3214 						case SB_TOP:
3215 							//import std.stdio; writeln("top");
3216 							auto event = new Event("scrolltobeginning", *widgetp);
3217 							event.dispatch();
3218 						break;
3219 						case SB_ENDSCROLL:
3220 							// idk
3221 						break;
3222 						+/
3223 						case SB_LINEDOWN:
3224 							(*widgetp).emitCommand!"scrolltonextline"();
3225 						return 0;
3226 						case SB_LINEUP:
3227 							(*widgetp).emitCommand!"scrolltopreviousline"();
3228 						return 0;
3229 						case SB_PAGEDOWN:
3230 							(*widgetp).emitCommand!"scrolltonextpage"();
3231 						return 0;
3232 						case SB_PAGEUP:
3233 							(*widgetp).emitCommand!"scrolltopreviouspage"();
3234 						return 0;
3235 						case SB_THUMBPOSITION:
3236 							auto ev = new ScrollToPositionEvent(*widgetp, pos);
3237 							ev.dispatch();
3238 						return 0;
3239 						case SB_THUMBTRACK:
3240 							// eh kinda lying but i like the real time update display
3241 							auto ev = new ScrollToPositionEvent(*widgetp, pos);
3242 							ev.dispatch();
3243 
3244 							// the event loop doesn't seem to carry on with a requested redraw..
3245 							// so we request it to get our dirty bit set...
3246 							// then we need to immediately actually redraw it too for instant feedback to user
3247 							SimpleWindow.processAllCustomEvents();
3248 							SimpleWindow.processAllCustomEvents();
3249 							//if(this_.parentWindow)
3250 								//this_.parentWindow.actualRedraw();
3251 
3252 							// and this ensures the WM_PAINT message is sent fairly quickly
3253 							// still seems to lag a little in large windows but meh it basically works.
3254 							if(this_.parentWindow) {
3255 								// FIXME: if painting is slow, this does still lag
3256 								// we probably will want to expose some user hook to ScrollWindowEx
3257 								// or something.
3258 								UpdateWindow(this_.parentWindow.hwnd);
3259 							}
3260 						return 0;
3261 						default:
3262 					}
3263 				}
3264 			break;
3265 
3266 			case WM_CONTEXTMENU:
3267 				auto hwndFrom = cast(HWND) wParam;
3268 
3269 				auto xPos = cast(short) LOWORD(lParam); 
3270 				auto yPos = cast(short) HIWORD(lParam); 
3271 
3272 				if(auto widgetp = hwndFrom in Widget.nativeMapping) {
3273 					POINT p;
3274 					p.x = xPos;
3275 					p.y = yPos;
3276 					ScreenToClient(hwnd, &p);
3277 					auto clientX = cast(ushort) p.x;
3278 					auto clientY = cast(ushort) p.y;
3279 
3280 					auto wap = widgetAtPoint(*widgetp, clientX, clientY);
3281 
3282 					if(wap.widget.showContextMenu(wap.x, wap.y, xPos, yPos)) {
3283 						return 0;
3284 					}
3285 				}
3286 			break;
3287 
3288 			case WM_DRAWITEM:
3289 				auto dis = cast(DRAWITEMSTRUCT*) lParam;
3290 				if(auto widgetp = dis.hwndItem in Widget.nativeMapping) {
3291 					return (*widgetp).handleWmDrawItem(dis);
3292 				}
3293 			break;
3294 
3295 			case WM_NOTIFY:
3296 				auto hdr = cast(NMHDR*) lParam;
3297 				auto hwndFrom = hdr.hwndFrom;
3298 				auto code = hdr.code;
3299 
3300 				if(auto widgetp = hwndFrom in Widget.nativeMapping) {
3301 					return (*widgetp).handleWmNotify(hdr, code, mustReturn);
3302 				}
3303 			break;
3304 			case WM_COMMAND:
3305 				auto handle = cast(HWND) lParam;
3306 				auto cmd = HIWORD(wParam);
3307 				return processWmCommand(hwnd, handle, cmd, LOWORD(wParam));
3308 
3309 			default:
3310 				// pass it on
3311 		}
3312 		return 0;
3313 	}
3314 
3315 
3316 
3317 	extern(Windows)
3318 	private
3319 	// this is called by native child windows, whereas the other hook is done by simpledisplay windows
3320 	// but can i merge them?!
3321 	LRESULT HookedWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
3322 		//import std.stdio; try { writeln(iMessage); } catch(Exception e) {};
3323 
3324 		if(auto te = hWnd in Widget.nativeMapping) {
3325 			try {
3326 
3327 				te.hookedWndProc(iMessage, wParam, lParam);
3328 
3329 				int mustReturn;
3330 				auto ret = WindowProcedureHelper(*te, hWnd, iMessage, wParam, lParam, mustReturn);
3331 				if(mustReturn)
3332 					return ret;
3333 
3334 				if(iMessage == WM_SETFOCUS) {
3335 					auto lol = *te;
3336 					while(lol !is null && lol.implicitlyCreated)
3337 						lol = lol.parent;
3338 					lol.focus();
3339 					//(*te).parentWindow.focusedWidget = lol;
3340 				}
3341 
3342 
3343 				if(iMessage == WM_CTLCOLORBTN || iMessage == WM_CTLCOLORSTATIC) {
3344 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
3345 					return cast(typeof(return)) GetSysColorBrush(COLOR_3DFACE); // this is the window background color...
3346 						//GetStockObject(NULL_BRUSH);
3347 				}
3348 
3349 				auto pos = getChildPositionRelativeToParentOrigin(*te);
3350 				lastDefaultPrevented = false;
3351 				// try {import std.stdio; writeln(typeid(*te)); } catch(Exception e) {}
3352 				if(SimpleWindow.triggerEvents(hWnd, iMessage, wParam, lParam, pos[0], pos[1], (*te).parentWindow.win) || !lastDefaultPrevented)
3353 					return CallWindowProcW((*te).originalWindowProcedure, hWnd, iMessage, wParam, lParam);
3354 				else {
3355 					// it was something we recognized, should only call the window procedure if the default was not prevented
3356 				}
3357 			} catch(Exception e) {
3358 				assert(0, e.toString());
3359 			}
3360 			return 0;
3361 		}
3362 		assert(0, "shouldn't be receiving messages for this window....");
3363 		//import std.conv;
3364 		//assert(0, to!string(hWnd) ~ " :: " ~ to!string(TextEdit.nativeMapping)); // not supposed to happen
3365 	}
3366 
3367 	extern(Windows)
3368 	private
3369 	// see for info https://jeffpar.github.io/kbarchive/kb/079/Q79982/
3370 	LRESULT HookedWndProcBSGROUPBOX_HACK(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
3371 		if(iMessage == WM_ERASEBKGND) {
3372 			auto dc = GetDC(hWnd);
3373 			auto b = SelectObject(dc, GetSysColorBrush(COLOR_3DFACE));
3374 			auto p = SelectObject(dc, GetStockObject(NULL_PEN));
3375 			RECT r;
3376 			GetWindowRect(hWnd, &r);
3377 			// since the pen is null, to fill the whole space, we need the +1 on both.
3378 			gdi.Rectangle(dc, 0, 0, r.right - r.left + 1, r.bottom - r.top + 1);
3379 			SelectObject(dc, p);
3380 			SelectObject(dc, b);
3381 			ReleaseDC(hWnd, dc);
3382 			InvalidateRect(hWnd, null, false); // redraw the border
3383 			return 1;
3384 		}
3385 		return HookedWndProc(hWnd, iMessage, wParam, lParam);
3386 	}
3387 
3388 	/++
3389 		Calls MS Windows' CreateWindowExW function to create a native backing for the given widget. It will create
3390 		needed mappings, window procedure hooks, and other private member variables needed to tie it into the rest
3391 		of minigui's expectations.
3392 
3393 		This should be called in your widget's constructor AFTER you call `super(parent);`. The parent window
3394 		member MUST already be initialized for this function to succeed, which is done by [Widget]'s base constructor.
3395 
3396 		It assumes `className` is zero-terminated. It should come from a `"wide string literal"w`.
3397 
3398 		To check if you can use this, use `static if(UsingWin32Widgets)`.
3399 	+/
3400 	void createWin32Window(Widget p, const(wchar)[] className, string windowText, DWORD style, DWORD extStyle = 0) {
3401 		assert(p.parentWindow !is null);
3402 		assert(p.parentWindow.win.impl.hwnd !is null);
3403 
3404 		auto bsgroupbox = style == BS_GROUPBOX;
3405 
3406 		HWND phwnd;
3407 
3408 		auto wtf = p.parent;
3409 		while(wtf) {
3410 			if(wtf.hwnd !is null) {
3411 				phwnd = wtf.hwnd;
3412 				break;
3413 			}
3414 			wtf = wtf.parent;
3415 		}
3416 
3417 		if(phwnd is null)
3418 			phwnd = p.parentWindow.win.impl.hwnd;
3419 
3420 		assert(phwnd !is null);
3421 
3422 		WCharzBuffer wt = WCharzBuffer(windowText);
3423 
3424 		style |= WS_VISIBLE | WS_CHILD;
3425 		//if(className != WC_TABCONTROL)
3426 			style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
3427 		p.hwnd = CreateWindowExW(extStyle, className.ptr, wt.ptr, style,
3428 				CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
3429 				phwnd, null, cast(HINSTANCE) GetModuleHandle(null), null);
3430 
3431 		assert(p.hwnd !is null);
3432 
3433 
3434 		static HFONT font;
3435 		if(font is null) {
3436 			NONCLIENTMETRICS params;
3437 			params.cbSize = params.sizeof;
3438 			if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
3439 				font = CreateFontIndirect(&params.lfMessageFont);
3440 			}
3441 		}
3442 
3443 		if(font)
3444 			SendMessage(p.hwnd, WM_SETFONT, cast(uint) font, true);
3445 
3446 		p.simpleWindowWrappingHwnd = new SimpleWindow(p.hwnd);
3447 		p.simpleWindowWrappingHwnd.beingOpenKeepsAppOpen = false;
3448 		Widget.nativeMapping[p.hwnd] = p;
3449 
3450 		if(bsgroupbox)
3451 		p.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(p.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProcBSGROUPBOX_HACK);
3452 		else
3453 		p.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(p.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc);
3454 
3455 		EnumChildWindows(p.hwnd, &childHandler, cast(LPARAM) cast(void*) p);
3456 
3457 		p.registerMovement();
3458 	}
3459 }
3460 
3461 version(win32_widgets)
3462 private
3463 extern(Windows) BOOL childHandler(HWND hwnd, LPARAM lparam) {
3464 	if(hwnd is null || hwnd in Widget.nativeMapping)
3465 		return true;
3466 	auto parent = cast(Widget) cast(void*) lparam;
3467 	Widget p = new Widget(null);
3468 	p._parent = parent;
3469 	p.parentWindow = parent.parentWindow;
3470 	p.hwnd = hwnd;
3471 	p.implicitlyCreated = true;
3472 	Widget.nativeMapping[p.hwnd] = p;
3473 	p.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(p.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc);
3474 	return true;
3475 }
3476 
3477 /++
3478 	Encapsulates the simpledisplay [ScreenPainter] for use on a [Widget], with [VisualTheme] and invalidated area awareness.
3479 +/
3480 struct WidgetPainter {
3481 	this(ScreenPainter screenPainter, Widget drawingUpon) {
3482 		this.drawingUpon = drawingUpon;
3483 		this.screenPainter = screenPainter;
3484 		if(auto font = visualTheme.defaultFontCached)
3485 			this.screenPainter.setFont(font);
3486 	}
3487 
3488 	///
3489 	ScreenPainter screenPainter;
3490 	/// Forward to the screen painter for other methods
3491 	alias screenPainter this;
3492 
3493 	private Widget drawingUpon;
3494 
3495 	/++
3496 		This is the list of rectangles that actually need to be redrawn.
3497 
3498 		Not actually implemented yet.
3499 	+/
3500 	Rectangle[] invalidatedRectangles;
3501 
3502 	private static BaseVisualTheme _visualTheme;
3503 
3504 	/++
3505 		Functions to access the visual theme and helpers to easily use it.
3506 
3507 		These are aware of the current widget's computed style out of the theme.
3508 	+/
3509 	static @property BaseVisualTheme visualTheme() {
3510 		if(_visualTheme is null)
3511 			_visualTheme = new DefaultVisualTheme();
3512 		return _visualTheme;
3513 	}
3514 
3515 	/// ditto
3516 	static @property void visualTheme(BaseVisualTheme theme) {
3517 		_visualTheme = theme;
3518 	}
3519 
3520 	/// ditto
3521 	Color themeForeground() {
3522 		return drawingUpon.getComputedStyle().foregroundColor();
3523 	}
3524 
3525 	/// ditto
3526 	Color themeBackground() {
3527 		return drawingUpon.getComputedStyle().background.color;
3528 	}
3529 
3530 	int isDarkTheme() {
3531 		return 0; // unspecified, yes, no as enum. FIXME
3532 	}
3533 
3534 	/++
3535 		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.
3536 
3537 		It gives your draw delegate a [Rectangle] representing the coordinates inside your border and padding.
3538 
3539 		If you change teh clip rectangle, you should change it back before you return.
3540 
3541 
3542 		The sequence it uses is:
3543 			background
3544 			content (delegated to you)
3545 			border
3546 			focused outline
3547 			selected overlay
3548 
3549 		Example code:
3550 
3551 		---
3552 		void paint(WidgetPainter painter) {
3553 			painter.drawThemed((bounds) {
3554 				return bounds; // if the selection overlay should be contained, you can return it here.
3555 			});
3556 		}
3557 		---
3558 	+/
3559 	void drawThemed(scope Rectangle delegate(const Rectangle bounds) drawBody) {
3560 		drawThemed((WidgetPainter painter, const Rectangle bounds) {
3561 			return drawBody(bounds);
3562 		});
3563 	}
3564 	// this overload is actually mroe for setting the delegate to a virtual function
3565 	void drawThemed(scope Rectangle delegate(WidgetPainter painter, const Rectangle bounds) drawBody) {
3566 		Rectangle rect = Rectangle(0, 0, drawingUpon.width, drawingUpon.height);
3567 
3568 		auto cs = drawingUpon.getComputedStyle();
3569 
3570 		auto bg = cs.background.color;
3571 
3572 		auto borderWidth = draw3dFrame(0, 0, drawingUpon.width, drawingUpon.height, this, cs.borderStyle, bg, cs.borderColor);
3573 
3574 		rect.left += borderWidth;
3575 		rect.right -= borderWidth;
3576 		rect.top += borderWidth;
3577 		rect.bottom -= borderWidth;
3578 
3579 		auto insideBorderRect = rect;
3580 
3581 		rect.left += cs.paddingLeft;
3582 		rect.right -= cs.paddingRight;
3583 		rect.top += cs.paddingTop;
3584 		rect.bottom += cs.paddingBottom;
3585 
3586 		this.outlineColor = this.themeForeground;
3587 		this.fillColor = bg;
3588 
3589 		auto widgetFont = cs.fontCached;
3590 		if(widgetFont !is null)
3591 			this.setFont(widgetFont);
3592 
3593 		rect = drawBody(this, rect);
3594 
3595 		if(widgetFont !is null) {
3596 			if(auto vtFont = visualTheme.defaultFontCached)
3597 				this.setFont(vtFont);
3598 			else
3599 				this.setFont(null);
3600 		}
3601 
3602 		if(auto os = cs.outlineStyle()) {
3603 			this.pen = Pen(cs.outlineColor(), 1, os == FrameStyle.dotted ? Pen.Style.Dotted : Pen.Style.Solid);
3604 			this.fillColor = Color.transparent;
3605 			this.drawRectangle(insideBorderRect);
3606 		}
3607 	}
3608 
3609 	/++
3610 		First, draw the background.
3611 		Then draw your content.
3612 		Next, draw the border.
3613 		And the focused indicator.
3614 		And the is-selected box.
3615 
3616 		If it is focused i can draw the outline too...
3617 
3618 		If selected i can even do the xor action but that's at the end.
3619 	+/
3620 	void drawThemeBackground() {
3621 
3622 	}
3623 
3624 	void drawThemeBorder() {
3625 
3626 	}
3627 
3628 	// all this stuff is a dangerous experiment....
3629 	static class ScriptableVersion {
3630 		ScreenPainterImplementation* p;
3631 		int originX, originY;
3632 
3633 		@scriptable:
3634 		void drawRectangle(int x, int y, int width, int height) {
3635 			p.drawRectangle(x + originX, y + originY, width, height);
3636 		}
3637 		void drawLine(int x1, int y1, int x2, int y2) {
3638 			p.drawLine(x1 + originX, y1 + originY, x2 + originX, y2 + originY);
3639 		}
3640 		void drawText(int x, int y, string text) {
3641 			p.drawText(x + originX, y + originY, 100000, 100000, text, 0);
3642 		}
3643 		void setOutlineColor(int r, int g, int b) {
3644 			p.pen = Pen(Color(r,g,b), 1);
3645 		}
3646 		void setFillColor(int r, int g, int b) {
3647 			p.fillColor = Color(r,g,b);
3648 		}
3649 	}
3650 
3651 	ScriptableVersion toArsdJsvar() {
3652 		auto sv = new ScriptableVersion;
3653 		sv.p = this.screenPainter.impl;
3654 		sv.originX = this.screenPainter.originX;
3655 		sv.originY = this.screenPainter.originY;
3656 		return sv;
3657 	}
3658 
3659 	static WidgetPainter fromJsVar(T)(T t) {
3660 		return WidgetPainter.init;
3661 	}
3662 	// done..........
3663 }
3664 
3665 
3666 struct Style {
3667 	static struct helper(string m, T) {
3668 		enum method = m;
3669 		T v;
3670 
3671 		mixin template MethodOverride(typeof(this) v) {
3672 			mixin("override typeof(v.v) "~v.method~"() { return v.v; }");
3673 		}
3674 	}
3675 
3676 	static auto opDispatch(string method, T)(T value) {
3677 		return helper!(method, T)(value);
3678 	}
3679 }
3680 
3681 /++
3682 	Implementation detail of the [ControlledBy] UDA.
3683 
3684 	History:
3685 		Added Oct 28, 2020
3686 +/
3687 struct ControlledBy_(T, Args...) {
3688 	Args args;
3689 
3690 	static if(Args.length)
3691 	this(Args args) {
3692 		this.args = args;
3693 	}
3694 
3695 	private T construct(Widget parent) {
3696 		return new T(args, parent);
3697 	}
3698 }
3699 
3700 /++
3701 	User-defined attribute you can add to struct members contrlled by [addDataControllerWidget] or [dialog] to tell which widget you want created for them.
3702 
3703 	History:
3704 		Added Oct 28, 2020
3705 +/
3706 auto ControlledBy(T, Args...)(Args args) {
3707 	return ControlledBy_!(T, Args)(args);
3708 }
3709 
3710 struct ContainerMeta {
3711 	string name;
3712 	ContainerMeta[] children;
3713 	Widget function(Widget parent) factory;
3714 
3715 	Widget instantiate(Widget parent) {
3716 		auto n = factory(parent);
3717 		n.name = name;
3718 		foreach(child; children)
3719 			child.instantiate(n);
3720 		return n;
3721 	}
3722 }
3723 
3724 /++
3725 	This is a helper for [addDataControllerWidget]. You can use it as a UDA on the type. See
3726 	http://dpldocs.info/this-week-in-d/Blog.Posted_2020_11_02.html for more information.
3727 
3728 	Please note that as of May 28, 2021, a dmd bug prevents this from compiling on module-level
3729 	structures. It works fine on structs declared inside functions though.
3730 
3731 	See: https://issues.dlang.org/show_bug.cgi?id=21984
3732 +/
3733 template Container(CArgs...) {
3734 	static if(CArgs.length && is(CArgs[0] : Widget)) {
3735 		private alias Super = CArgs[0];
3736 		private alias CArgs2 = CArgs[1 .. $];
3737 	} else {
3738 		private alias Super = Layout;
3739 		private alias CArgs2 = CArgs;
3740 	}
3741 
3742 	class Container : Super {
3743 		this(Widget parent) { super(parent); }
3744 
3745 		// just to partially support old gdc versions
3746 		version(GNU) {
3747 			static if(CArgs2.length >= 1) { enum tmp0 = CArgs2[0]; mixin typeof(tmp0).MethodOverride!(CArgs2[0]); }
3748 			static if(CArgs2.length >= 2) { enum tmp1 = CArgs2[1]; mixin typeof(tmp1).MethodOverride!(CArgs2[1]); }
3749 			static if(CArgs2.length >= 3) { enum tmp2 = CArgs2[2]; mixin typeof(tmp2).MethodOverride!(CArgs2[2]); }
3750 			static if(CArgs2.length > 3) static assert(0, "only a few overrides like this supported on your compiler version at this time");
3751 		} else mixin(q{
3752 			static foreach(Arg; CArgs2) {
3753 				mixin Arg.MethodOverride!(Arg);
3754 			}
3755 		});
3756 
3757 		static ContainerMeta opCall(string name, ContainerMeta[] children...) {
3758 			return ContainerMeta(
3759 				name,
3760 				children.dup,
3761 				function (Widget parent) { return new typeof(this)(parent); }
3762 			);
3763 		}
3764 
3765 		static ContainerMeta opCall(ContainerMeta[] children...) {
3766 			return opCall(null, children);
3767 		}
3768 	}
3769 }
3770 
3771 /++
3772 	The data controller widget is created by reflecting over the given
3773 	data type. You can use [ControlledBy] as a UDA on a struct or
3774 	just let it create things automatically.
3775 
3776 	Unlike [dialog], this uses real-time updating of the data and
3777 	you add it to another window yourself.
3778 
3779 	---
3780 		struct Test {
3781 			int x;
3782 			int y;
3783 		}
3784 
3785 		auto window = new Window();
3786 		auto dcw = new DataControllerWidget!Test(new Test, window);
3787 	---
3788 
3789 	The way it works is any public members are given a widget based
3790 	on their data type, and public methods trigger an action button
3791 	if no relevant parameters or a dialog action if it does have
3792 	parameters, similar to the [menu] facility.
3793 
3794 	If you change data programmatically, without going through the
3795 	DataControllerWidget methods, you will have to tell it something
3796 	has changed and it needs to redraw. This is done with the `invalidate`
3797 	method.
3798 
3799 	History:
3800 		Added Oct 28, 2020
3801 +/
3802 /// Group: generating_from_code
3803 class DataControllerWidget(T) : WidgetContainer {
3804 	static if(is(T == class) || is(T : const E[], E))
3805 		private alias Tref = T;
3806 	else
3807 		private alias Tref = T*;
3808 
3809 	Tref datum;
3810 
3811 	/++
3812 		See_also: [addDataControllerWidget]
3813 	+/
3814 	this(Tref datum, Widget parent) {
3815 		this.datum = datum;
3816 
3817 		Widget cp = this;
3818 
3819 		super(parent);
3820 
3821 		foreach(attr; __traits(getAttributes, T))
3822 			static if(is(typeof(attr) == ContainerMeta)) {
3823 				cp = attr.instantiate(this);
3824 			}
3825 
3826 		auto def = this.getByName("default");
3827 		if(def !is null)
3828 			cp = def;
3829 
3830 		Widget helper(string name) {
3831 			auto maybe = this.getByName(name);
3832 			if(maybe is null)
3833 				return cp;
3834 			return maybe;
3835 
3836 		}
3837 
3838 		foreach(member; __traits(allMembers, T))
3839 		static if(member != "this") // wtf https://issues.dlang.org/show_bug.cgi?id=22011
3840 		static if(is(typeof(__traits(getMember, this.datum, member))))
3841 		static if(__traits(getProtection, __traits(getMember, this.datum, member)) == "public") {
3842 			void delegate() update;
3843 
3844 			auto w = widgetFor!(__traits(getMember, T, member))(&__traits(getMember, this.datum, member), helper(member), update);
3845 
3846 			if(update)
3847 				updaters ~= update;
3848 
3849 			static if(is(typeof(__traits(getMember, this.datum, member)) == function)) {
3850 				w.addEventListener("triggered", delegate() {
3851 					makeAutomaticHandler!(__traits(getMember, this.datum, member))(&__traits(getMember, this.datum, member))();
3852 					notifyDataUpdated();
3853 				});
3854 			} else static if(is(typeof(w.isChecked) == bool)) {
3855 				w.addEventListener(EventType.change, (Event ev) {
3856 					__traits(getMember, this.datum, member) = w.isChecked;
3857 				});
3858 			} else static if(is(typeof(w.value) == string) || is(typeof(w.content) == string)) {
3859 				w.addEventListener("change", (Event e) { genericSetValue(&__traits(getMember, this.datum, member), e.stringValue); } );
3860 			} else static if(is(typeof(w.value) == int)) {
3861 				w.addEventListener("change", (Event e) { genericSetValue(&__traits(getMember, this.datum, member), e.intValue); } );
3862 			} else static if(is(typeof(w) == DropDownSelection)) {
3863 				// special case for this to kinda support enums and such. coudl be better though
3864 				w.addEventListener("change", (Event e) { genericSetValue(&__traits(getMember, this.datum, member), e.intValue); } );
3865 			} else {
3866 				static assert(0, "unsupported type " ~ typeof(__traits(getMember, this.datum, member)).stringof ~ " " ~ typeof(w).stringof);
3867 			}
3868 		}
3869 	}
3870 
3871 	/++
3872 		If you modify the data in the structure directly, you need to call this to update the UI and propagate any change messages.
3873 
3874 		History:
3875 			Added May 28, 2021
3876 	+/
3877 	void notifyDataUpdated() {
3878 		foreach(updater; updaters)
3879 			updater();
3880 
3881 		this.emit!(ChangeEvent!void)(delegate{});
3882 	}
3883 
3884 	private Widget[string] memberWidgets;
3885 	private void delegate()[] updaters;
3886 
3887 	mixin Emits!(ChangeEvent!void);
3888 }
3889 
3890 private int saturatedSum(int[] values...) {
3891 	int sum;
3892 	foreach(value; values) {
3893 		if(value == int.max)
3894 			return int.max;
3895 		sum += value;
3896 	}
3897 	return sum;
3898 }
3899 
3900 void genericSetValue(T, W)(T* where, W what) {
3901 	import std.conv;
3902 	*where = to!T(what);
3903 	//*where = cast(T) stringToLong(what);
3904 }
3905 
3906 /++
3907 	Creates a widget for the value `tt`, which is pointed to at runtime by `valptr`, with the given parent.
3908 
3909 	The `update` delegate can be called if you change `*valptr` to reflect those changes in the widget.
3910 
3911 	Note that this creates the widget but does not attach any event handlers to it.
3912 +/
3913 private static auto widgetFor(alias tt, P)(P valptr, Widget parent, out void delegate() update) {
3914 
3915 	string displayName = __traits(identifier, tt).beautify;
3916 
3917 	static if(controlledByCount!tt == 1) {
3918 		foreach(i, attr; __traits(getAttributes, tt)) {
3919 			static if(is(typeof(attr) == ControlledBy_!(T, Args), T, Args...)) {
3920 				auto w = attr.construct(parent);
3921 				static if(__traits(compiles, w.setPosition(*valptr)))
3922 					update = () { w.setPosition(*valptr); };
3923 				else static if(__traits(compiles, w.setValue(*valptr)))
3924 					update = () { w.setValue(*valptr); };
3925 
3926 				if(update)
3927 					update();
3928 				return w;
3929 			}
3930 		}
3931 	} else static if(controlledByCount!tt == 0) {
3932 		static if(is(typeof(tt) == enum)) {
3933 			// FIXME: update
3934 			auto dds = new DropDownSelection(parent);
3935 			foreach(idx, option; __traits(allMembers, typeof(tt))) {
3936 				dds.addOption(option);
3937 				if(__traits(getMember, typeof(tt), option) == *valptr)
3938 					dds.setSelection(cast(int) idx);
3939 			}
3940 			return dds;
3941 		} else static if(is(typeof(tt) == bool)) {
3942 			auto box = new Checkbox(displayName, parent);
3943 			update = () { box.isChecked = *valptr; };
3944 			update();
3945 			return box;
3946 		} else static if(is(typeof(tt) : const long)) {
3947 			auto le = new LabeledLineEdit(displayName, parent);
3948 			update = () { le.content = toInternal!string(*valptr); };
3949 			update();
3950 			return le;
3951 		} else static if(is(typeof(tt) : const string)) {
3952 			auto le = new LabeledLineEdit(displayName, parent);
3953 			update = () { le.content = *valptr; };
3954 			update();
3955 			return le;
3956 		} else static if(is(typeof(tt) == function)) {
3957 			auto w = new Button(displayName, parent);
3958 			return w;
3959 		}
3960 	} else static assert(0, "multiple controllers not yet supported");
3961 }
3962 
3963 private template controlledByCount(alias tt) {
3964 	static int helper() {
3965 		int count;
3966 		foreach(i, attr; __traits(getAttributes, tt))
3967 			static if(is(typeof(attr) == ControlledBy_!(T, Args), T, Args...))
3968 				count++;
3969 		return count;
3970 	}
3971 
3972 	enum controlledByCount = helper;
3973 }
3974 
3975 /++
3976 	Intended for UFCS action like `window.addDataControllerWidget(new MyObject());`
3977 
3978 	If you provide a `redrawOnChange` widget, it will automatically register a change event handler that calls that widget's redraw method.
3979 
3980 	History:
3981 		The `redrawOnChange` parameter was added on May 28, 2021.
3982 +/
3983 DataControllerWidget!T addDataControllerWidget(T)(Widget parent, T t, Widget redrawOnChange = null) if(is(T == class)) {
3984 	auto dcw = new DataControllerWidget!T(t, parent);
3985 	initializeDataControllerWidget(dcw, redrawOnChange);
3986 	return dcw;
3987 }
3988 
3989 /// ditto
3990 DataControllerWidget!T addDataControllerWidget(T)(Widget parent, T* t, Widget redrawOnChange = null) if(is(T == struct)) {
3991 	auto dcw = new DataControllerWidget!T(t, parent);
3992 	initializeDataControllerWidget(dcw, redrawOnChange);
3993 	return dcw;
3994 }
3995 
3996 private void initializeDataControllerWidget(Widget w, Widget redrawOnChange) {
3997 	if(redrawOnChange !is null)
3998 		w.addEventListener("change", delegate() { redrawOnChange.redraw(); });
3999 }
4000 
4001 /++
4002 	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.
4003 
4004 	History:
4005 		Finalized on June 3, 2021 for the dub v10.0 release
4006 +/
4007 struct StyleInformation {
4008 	private Widget w;
4009 	private BaseVisualTheme visualTheme;
4010 
4011 	private this(Widget w) {
4012 		this.w = w;
4013 		this.visualTheme = WidgetPainter.visualTheme;
4014 	}
4015 
4016 	/++
4017 		Forwards to [Widget.Style]
4018 
4019 		Bugs:
4020 			It is supposed to fall back to the [VisualTheme] if
4021 			the style doesn't override the default, but that is
4022 			not generally implemented. Many of them may end up
4023 			being explicit overloads instead of the generic
4024 			opDispatch fallback, like [font] is now.
4025 	+/
4026 	public @property opDispatch(string name)() {
4027 		typeof(__traits(getMember, Widget.Style.init, name)()) prop;
4028 		w.useStyleProperties((scope Widget.Style props) {
4029 		//visualTheme.useStyleProperties(w, (props) {
4030 			prop = __traits(getMember, props, name);
4031 		});
4032 		return prop;
4033 	}
4034 
4035 	/++
4036 		Returns the cached font object associated with the widget,
4037 		if overridden by the [Widget.Style|Style], or the [VisualTheme] if not.
4038 
4039 		History:
4040 			Prior to March 21, 2022 (dub v10.7), `font` went through
4041 			[opDispatch], which did not use the cache. You can now call it
4042 			repeatedly without guilt.
4043 	+/
4044 	public @property OperatingSystemFont font() {
4045 		OperatingSystemFont prop;
4046 		w.useStyleProperties((scope Widget.Style props) {
4047 			prop = props.fontCached;
4048 		});
4049 		if(prop is null)
4050 			prop = visualTheme.defaultFontCached;
4051 		return prop;
4052 	}
4053 
4054 	@property {
4055 		// Layout helpers. Currently just forwarding since I haven't made up my mind on a better way.
4056 		/** */ int paddingLeft() { return w.paddingLeft(); }
4057 		/** */ int paddingRight() { return w.paddingRight(); }
4058 		/** */ int paddingTop() { return w.paddingTop(); }
4059 		/** */ int paddingBottom() { return w.paddingBottom(); }
4060 
4061 		/** */ int marginLeft() { return w.marginLeft(); }
4062 		/** */ int marginRight() { return w.marginRight(); }
4063 		/** */ int marginTop() { return w.marginTop(); }
4064 		/** */ int marginBottom() { return w.marginBottom(); }
4065 
4066 		/** */ int maxHeight() { return w.maxHeight(); }
4067 		/** */ int minHeight() { return w.minHeight(); }
4068 
4069 		/** */ int maxWidth() { return w.maxWidth(); }
4070 		/** */ int minWidth() { return w.minWidth(); }
4071 
4072 		/** */ int flexBasisWidth() { return w.flexBasisWidth(); }
4073 		/** */ int flexBasisHeight() { return w.flexBasisHeight(); }
4074 
4075 		/** */ int heightStretchiness() { return w.heightStretchiness(); }
4076 		/** */ int widthStretchiness() { return w.widthStretchiness(); }
4077 
4078 		/** */ int heightShrinkiness() { return w.heightShrinkiness(); }
4079 		/** */ int widthShrinkiness() { return w.widthShrinkiness(); }
4080 
4081 		// Global helpers some of these are unstable.
4082 		static:
4083 		/** */ Color windowBackgroundColor() { return WidgetPainter.visualTheme.windowBackgroundColor(); }
4084 		/** */ Color widgetBackgroundColor() { return WidgetPainter.visualTheme.widgetBackgroundColor(); }
4085 		/** */ Color lightAccentColor() { return WidgetPainter.visualTheme.lightAccentColor(); }
4086 		/** */ Color darkAccentColor() { return WidgetPainter.visualTheme.darkAccentColor(); }
4087 
4088 		/** */ Color activeTabColor() { return lightAccentColor; }
4089 		/** */ Color buttonColor() { return windowBackgroundColor; }
4090 		/** */ Color depressedButtonColor() { return darkAccentColor; }
4091 		/** */ Color hoveringColor() { return Color(228, 228, 228); }
4092 		/** */ Color activeListXorColor() {
4093 			auto c = WidgetPainter.visualTheme.selectionColor();
4094 			return Color(c.r ^ 255, c.g ^ 255, c.b ^ 255, c.a);
4095 		}
4096 		/** */ Color progressBarColor() { return WidgetPainter.visualTheme.selectionColor(); }
4097 		/** */ Color activeMenuItemColor() { return WidgetPainter.visualTheme.selectionColor(); }
4098 	}
4099 
4100 
4101 
4102 	/+
4103 
4104 	private static auto extractStyleProperty(string name)(Widget w) {
4105 		typeof(__traits(getMember, Widget.Style.init, name)()) prop;
4106 		w.useStyleProperties((props) {
4107 			prop = __traits(getMember, props, name);
4108 		});
4109 		return prop;
4110 	}
4111 
4112 	// FIXME: clear this upon a X server disconnect
4113 	private static OperatingSystemFont[string] fontCache;
4114 
4115 	T getProperty(T)(string name, lazy T default_) {
4116 		if(visualTheme !is null) {
4117 			auto str = visualTheme.getPropertyString(w, name);
4118 			if(str is null)
4119 				return default_;
4120 			static if(is(T == Color))
4121 				return Color.fromString(str);
4122 			else static if(is(T == Measurement))
4123 				return Measurement(cast(int) toInternal!int(str));
4124 			else static if(is(T == WidgetBackground))
4125 				return WidgetBackground.fromString(str);
4126 			else static if(is(T == OperatingSystemFont)) {
4127 				if(auto f = str in fontCache)
4128 					return *f;
4129 				else
4130 					return fontCache[str] = new OperatingSystemFont(str);
4131 			} else static if(is(T == FrameStyle)) {
4132 				switch(str) {
4133 					default:
4134 						return FrameStyle.none;
4135 					foreach(style; __traits(allMembers, FrameStyle))
4136 					case style:
4137 						return __traits(getMember, FrameStyle, style);
4138 				}
4139 			} else static assert(0);
4140 		} else
4141 			return default_;
4142 	}
4143 
4144 	static struct Measurement {
4145 		int value;
4146 		alias value this;
4147 	}
4148 
4149 	@property:
4150 
4151 	int paddingLeft() { return getProperty("padding-left", Measurement(w.paddingLeft())); }
4152 	int paddingRight() { return getProperty("padding-right", Measurement(w.paddingRight())); }
4153 	int paddingTop() { return getProperty("padding-top", Measurement(w.paddingTop())); }
4154 	int paddingBottom() { return getProperty("padding-bottom", Measurement(w.paddingBottom())); }
4155 
4156 	int marginLeft() { return getProperty("margin-left", Measurement(w.marginLeft())); }
4157 	int marginRight() { return getProperty("margin-right", Measurement(w.marginRight())); }
4158 	int marginTop() { return getProperty("margin-top", Measurement(w.marginTop())); }
4159 	int marginBottom() { return getProperty("margin-bottom", Measurement(w.marginBottom())); }
4160 
4161 	int maxHeight() { return getProperty("max-height", Measurement(w.maxHeight())); }
4162 	int minHeight() { return getProperty("min-height", Measurement(w.minHeight())); }
4163 
4164 	int maxWidth() { return getProperty("max-width", Measurement(w.maxWidth())); }
4165 	int minWidth() { return getProperty("min-width", Measurement(w.minWidth())); }
4166 
4167 
4168 	WidgetBackground background() { return getProperty("background", extractStyleProperty!"background"(w)); }
4169 	Color foregroundColor() { return getProperty("foreground-color", extractStyleProperty!"foregroundColor"(w)); }
4170 
4171 	OperatingSystemFont font() { return getProperty("font", extractStyleProperty!"fontCached"(w)); }
4172 
4173 	FrameStyle borderStyle() { return getProperty("border-style", extractStyleProperty!"borderStyle"(w)); }
4174 	Color borderColor() { return getProperty("border-color", extractStyleProperty!"borderColor"(w)); }
4175 
4176 	FrameStyle outlineStyle() { return getProperty("outline-style", extractStyleProperty!"outlineStyle"(w)); }
4177 	Color outlineColor() { return getProperty("outline-color", extractStyleProperty!"outlineColor"(w)); }
4178 
4179 
4180 	Color windowBackgroundColor() { return WidgetPainter.visualTheme.windowBackgroundColor(); }
4181 	Color widgetBackgroundColor() { return WidgetPainter.visualTheme.widgetBackgroundColor(); }
4182 	Color lightAccentColor() { return WidgetPainter.visualTheme.lightAccentColor(); }
4183 	Color darkAccentColor() { return WidgetPainter.visualTheme.darkAccentColor(); }
4184 
4185 	Color activeTabColor() { return lightAccentColor; }
4186 	Color buttonColor() { return windowBackgroundColor; }
4187 	Color depressedButtonColor() { return darkAccentColor; }
4188 	Color hoveringColor() { return Color(228, 228, 228); }
4189 	Color activeListXorColor() {
4190 		auto c = WidgetPainter.visualTheme.selectionColor();
4191 		return Color(c.r ^ 255, c.g ^ 255, c.b ^ 255, c.a);
4192 	}
4193 	Color progressBarColor() { return WidgetPainter.visualTheme.selectionColor(); }
4194 	Color activeMenuItemColor() { return WidgetPainter.visualTheme.selectionColor(); }
4195 	+/
4196 }
4197 
4198 
4199 
4200 // pragma(msg, __traits(classInstanceSize, Widget));
4201 
4202 /*private*/ template EventString(E) {
4203 	static if(is(typeof(E.EventString)))
4204 		enum EventString = E.EventString;
4205 	else
4206 		enum EventString = E.mangleof; // FIXME fqn? or something more user friendly
4207 }
4208 
4209 /*private*/ template EventStringIdentifier(E) {
4210 	string helper() {
4211 		auto es = EventString!E;
4212 		char[] id = new char[](es.length * 2);
4213 		size_t idx;
4214 		foreach(char ch; es) {
4215 			id[idx++] = cast(char)('a' + (ch >> 4));
4216 			id[idx++] = cast(char)('a' + (ch & 0x0f));
4217 		}
4218 		return cast(string) id;
4219 	}
4220 
4221 	enum EventStringIdentifier = helper();
4222 }
4223 
4224 
4225 template classStaticallyEmits(This, EventType) {
4226 	static if(is(This Base == super))
4227 		static if(is(Base : Widget))
4228 			enum baseEmits = classStaticallyEmits!(Base, EventType);
4229 		else
4230 			enum baseEmits = false;
4231 	else
4232 		enum baseEmits = false;
4233 
4234 	enum thisEmits = is(typeof(__traits(getMember, This, "emits_" ~ EventStringIdentifier!EventType)) == EventType[0]);
4235 
4236 	enum classStaticallyEmits = thisEmits || baseEmits;
4237 }
4238 
4239 /++
4240 	A helper to make widgets out of other native windows.
4241 
4242 	History:
4243 		Factored out of OpenGlWidget on November 5, 2021
4244 +/
4245 class NestedChildWindowWidget : Widget {
4246 	SimpleWindow win;
4247 
4248 	/++
4249 		Used on X to send focus to the appropriate child window when requested by the window manager.
4250 
4251 		Normally returns its own nested window. Can also return another child or null to revert to the parent
4252 		if you override it in a child class.
4253 
4254 		History:
4255 			Added April 2, 2022 (dub v10.8)
4256 	+/
4257 	SimpleWindow focusableWindow() {
4258 		return win;
4259 	}
4260 
4261 	///
4262 	// win = new SimpleWindow(640, 480, null, OpenGlOptions.yes, Resizability.automaticallyScaleIfPossible, WindowTypes.nestedChild, WindowFlags.normal, getParentWindow(parent));
4263 	this(SimpleWindow win, Widget parent) {
4264 		this.parentWindow = parent.parentWindow;
4265 		this.win = win;
4266 
4267 		super(parent);
4268 		windowsetup(win);
4269 	}
4270 
4271 	static protected SimpleWindow getParentWindow(Widget parent) {
4272 		assert(parent !is null);
4273 		SimpleWindow pwin = parent.parentWindow.win;
4274 
4275 		version(win32_widgets) {
4276 			HWND phwnd;
4277 			auto wtf = parent;
4278 			while(wtf) {
4279 				if(wtf.hwnd) {
4280 					phwnd = wtf.hwnd;
4281 					break;
4282 				}
4283 				wtf = wtf.parent;
4284 			}
4285 			// kinda a hack here just because the ctor below just needs a SimpleWindow wrapper....
4286 			if(phwnd)
4287 				pwin = new SimpleWindow(phwnd);
4288 		}
4289 
4290 		return pwin;
4291 	}
4292 
4293 	/++
4294 		Called upon the nested window being destroyed.
4295 		Remember the window has already been destroyed at
4296 		this point, so don't use the native handle for anything.
4297 
4298 		History:
4299 			Added April 3, 2022 (dub v10.8)
4300 	+/
4301 	protected void dispose() {
4302 
4303 	}
4304 
4305 	protected void windowsetup(SimpleWindow w) {
4306 		/*
4307 		win.onFocusChange = (bool getting) {
4308 			if(getting)
4309 				this.focus();
4310 		};
4311 		*/
4312 
4313 		/+
4314 		win.onFocusChange = (bool getting) {
4315 			if(getting) {
4316 				this.parentWindow.focusedWidget = this;
4317 				this.emit!FocusEvent();
4318 				this.emit!FocusInEvent();
4319 			} else {
4320 				this.emit!BlurEvent();
4321 				this.emit!FocusOutEvent();
4322 			}
4323 		};
4324 		+/
4325 
4326 		win.onDestroyed = () {
4327 			this.dispose();
4328 		};
4329 
4330 		version(win32_widgets) {
4331 			Widget.nativeMapping[win.hwnd] = this;
4332 			this.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(win.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc);
4333 		} else {
4334 			win.setEventHandlers(
4335 				(MouseEvent e) {
4336 					Widget p = this;
4337 					while(p ! is parentWindow) {
4338 						e.x += p.x;
4339 						e.y += p.y;
4340 						p = p.parent;
4341 					}
4342 					parentWindow.dispatchMouseEvent(e);
4343 				},
4344 				(KeyEvent e) {
4345 					//import std.stdio; writefln("%s %x   %s", cast(void*) win, cast(uint) e.key, e.key);
4346 					parentWindow.dispatchKeyEvent(e);
4347 				},
4348 				(dchar e) {
4349 					parentWindow.dispatchCharEvent(e);
4350 				},
4351 			);
4352 		}
4353 
4354 	}
4355 
4356 	override void showing(bool s, bool recalc) {
4357 		auto cur = hidden;
4358 		win.hidden = !s;
4359 		if(cur != s && s)
4360 			redraw();
4361 	}
4362 
4363 	/// OpenGL widgets cannot have child widgets. Do not call this.
4364 	/* @disable */ final override void addChild(Widget, int) {
4365 		throw new Error("cannot add children to OpenGL widgets");
4366 	}
4367 
4368 	/// When an opengl widget is laid out, it will adjust the glViewport for you automatically.
4369 	/// Keep in mind that events like mouse coordinates are still relative to your size.
4370 	override void registerMovement() {
4371 		//import std.stdio; writefln("%d %d %d %d", x,y,width,height);
4372 		version(win32_widgets)
4373 			auto pos = getChildPositionRelativeToParentHwnd(this);
4374 		else
4375 			auto pos = getChildPositionRelativeToParentOrigin(this);
4376 		win.moveResize(pos[0], pos[1], width, height);
4377 
4378 		registerMovementAdditionalWork();
4379 		sendResizeEvent();
4380 	}
4381 
4382 	abstract void registerMovementAdditionalWork();
4383 }
4384 
4385 /++
4386 	Nests an opengl capable window inside this window as a widget.
4387 
4388 	You may also just want to create an additional [SimpleWindow] with
4389 	[OpenGlOptions.yes] yourself.
4390 
4391 	An OpenGL widget cannot have child widgets. It will throw if you try.
4392 +/
4393 static if(OpenGlEnabled)
4394 class OpenGlWidget : NestedChildWindowWidget {
4395 
4396 	override void registerMovementAdditionalWork() {
4397 		win.setAsCurrentOpenGlContext();
4398 	}
4399 
4400 	///
4401 	this(Widget parent) {
4402 		auto win = new SimpleWindow(640, 480, null, OpenGlOptions.yes, Resizability.automaticallyScaleIfPossible, WindowTypes.nestedChild, WindowFlags.normal, getParentWindow(parent));
4403 		super(win, parent);
4404 	}
4405 
4406 	override void paint(WidgetPainter painter) {
4407 		win.setAsCurrentOpenGlContext();
4408 		glViewport(0, 0, this.width, this.height);
4409 		win.redrawOpenGlSceneNow();
4410 	}
4411 
4412 	void redrawOpenGlScene(void delegate() dg) {
4413 		win.redrawOpenGlScene = dg;
4414 	}
4415 }
4416 
4417 /++
4418 	This demo shows how to draw text in an opengl scene.
4419 +/
4420 unittest {
4421 	import arsd.minigui;
4422 	import arsd.ttf;
4423 
4424 	void main() {
4425 		auto window = new Window();
4426 
4427 		auto widget = new OpenGlWidget(window);
4428 
4429 		// old means non-shader code so compatible with glBegin etc.
4430 		// tbh I haven't implemented new one in font yet...
4431 		// anyway, declaring here, will construct soon.
4432 		OpenGlLimitedFont!(OpenGlFontGLVersion.old) glfont;
4433 
4434 		// this is a little bit awkward, calling some methods through
4435 		// the underlying SimpleWindow `win` method, and you can't do this
4436 		// on a nanovega widget due to conflicts so I should probably fix
4437 		// the api to be a bit easier. But here it will work.
4438 		//
4439 		// Alternatively, you could load the font on the first draw, inside
4440 		// the redrawOpenGlScene, and keep a flag so you don't do it every
4441 		// time. That'd be a bit easier since the lib sets up the context
4442 		// by then guaranteed.
4443 		//
4444 		// But still, I wanna show this.
4445 		widget.win.visibleForTheFirstTime = delegate {
4446 			// must set the opengl context
4447 			widget.win.setAsCurrentOpenGlContext();
4448 
4449 			// if you were doing a OpenGL 3+ shader, this
4450 			// gets especially important to do in order. With
4451 			// old-style opengl, I think you can even do it
4452 			// in main(), but meh, let's show it more correctly.
4453 
4454 			// Anyway, now it is time to load the font from the
4455 			// OS (you can alternatively load one from a .ttf file
4456 			// you bundle with the application), then load the
4457 			// font into texture for drawing.
4458 
4459 			auto osfont = new OperatingSystemFont("DejaVu Sans", 18);
4460 
4461 			assert(!osfont.isNull()); // make sure it actually loaded
4462 
4463 			// using typeof to avoid repeating the long name lol
4464 			glfont = new typeof(glfont)(
4465 				// get the raw data from the font for loading in here
4466 				// since it doesn't use the OS function to draw the
4467 				// text, we gotta treat it more as a file than as
4468 				// a drawing api.
4469 				osfont.getTtfBytes(),
4470 				18, // need to respecify size since opengl world is different coordinate system
4471 
4472 				// these last two numbers are why it is called
4473 				// "Limited" font. It only loads the characters
4474 				// in the given range, since the texture atlas
4475 				// it references is all a big image generated ahead
4476 				// of time. You could maybe do the whole thing but
4477 				// idk how much memory that is.
4478 				//
4479 				// But here, 0-128 represents the ASCII range, so
4480 				// good enough for most English things, numeric labels,
4481 				// etc.
4482 				0,
4483 				128
4484 			);
4485 		};
4486 
4487 		widget.redrawOpenGlScene = () {
4488 			// now we can use the glfont's drawString function
4489 
4490 			// first some opengl setup. You can do this in one place
4491 			// on window first visible too in many cases, just showing
4492 			// here cuz it is easier for me.
4493 
4494 			// gonna need some alpha blending or it just looks awful
4495 			glEnable(GL_BLEND);
4496 			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
4497 			glClearColor(0,0,0,0);
4498 			glDepthFunc(GL_LEQUAL);
4499 
4500 			// Also need to enable 2d textures, since it draws the
4501 			// font characters as images baked in
4502 			glMatrixMode(GL_MODELVIEW);
4503 			glLoadIdentity();
4504 			glDisable(GL_DEPTH_TEST);
4505 			glEnable(GL_TEXTURE_2D);
4506 
4507 			// the orthographic matrix is best for 2d things like text
4508 			// so let's set that up. This matrix makes the coordinates
4509 			// in the opengl scene be one-to-one with the actual pixels
4510 			// on screen. (Not necessarily best, you may wish to scale
4511 			// things, but it does help keep fonts looking normal.)
4512 			glMatrixMode(GL_PROJECTION);
4513 			glLoadIdentity();
4514 			glOrtho(0, widget.width, widget.height, 0, 0, 1);
4515 
4516 			// you can do other glScale, glRotate, glTranslate, etc
4517 			// to the matrix here of course if you want.
4518 
4519 			// note the x,y coordinates here are for the text baseline
4520 			// NOT the upper-left corner. The baseline is like the line
4521 			// in the notebook you write on. Most the letters are actually
4522 			// above it, but some, like p and q, dip a bit below it.
4523 			//
4524 			// So if you're used to the upper left coordinate like the
4525 			// rest of simpledisplay/minigui usually do, do the
4526 			// y + glfont.ascent to bring it down a little. So this
4527 			// example puts the string in the upper left of the window.
4528 			glfont.drawString(0, 0 + glfont.ascent, "Hello!!", Color.green);
4529 
4530 			// re color btw: the function sets a solid color internally,
4531 			// but you actually COULD do your own thing for rainbow effects
4532 			// and the sort if you wanted too, by pulling its guts out.
4533 			// Just view its source for an idea of how it actually draws:
4534 			// http://arsd-official.dpldocs.info/source/arsd.ttf.d.html#L332
4535 
4536 			// it gets a bit complicated with the character positioning,
4537 			// but the opengl parts are fairly simple: bind a texture,
4538 			// set the color, draw a quad for each letter.
4539 
4540 
4541 			// the last optional argument there btw is a bounding box
4542 			// it will/ use to word wrap and return an object you can
4543 			// use to implement scrolling or pagination; it tells how
4544 			// much of the string didn't fit in the box. But for simple
4545 			// labels we can just ignore that.
4546 
4547 
4548 			// I'd suggest drawing text as the last step, after you
4549 			// do your other drawing. You might use the push/pop matrix
4550 			// stuff to keep your place. You, in theory, should be able
4551 			// to do text in a 3d space but I've never actually tried
4552 			// that....
4553 		};
4554 
4555 		window.loop();
4556 	}
4557 }
4558 
4559 version(custom_widgets)
4560 	private alias ListWidgetBase = ScrollableWidget;
4561 else
4562 	private alias ListWidgetBase = Widget;
4563 
4564 /++
4565 	A list widget contains a list of strings that the user can examine and select.
4566 
4567 
4568 	In the future, items in the list may be possible to be more than just strings.
4569 
4570 	See_Also:
4571 		[TableView]
4572 +/
4573 class ListWidget : ListWidgetBase {
4574 	/// 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.
4575 	mixin Emits!(ChangeEvent!void);
4576 
4577 	static struct Option {
4578 		string label;
4579 		bool selected;
4580 		void* tag;
4581 	}
4582 
4583 	/++
4584 		Sets the current selection to the `y`th item in the list. Will emit [ChangeEvent] when complete.
4585 	+/
4586 	void setSelection(int y) {
4587 		if(!multiSelect)
4588 			foreach(ref opt; options)
4589 				opt.selected = false;
4590 		if(y >= 0 && y < options.length)
4591 			options[y].selected = !options[y].selected;
4592 
4593 		this.emit!(ChangeEvent!void)(delegate {});
4594 
4595 		version(custom_widgets)
4596 			redraw();
4597 	}
4598 
4599 	/++
4600 		Gets the index of the selected item. In case of multi select, the index of the first selected item is returned.
4601 		Returns -1 if nothing is selected.
4602 	+/
4603 	int getSelection()
4604 	{
4605 		foreach(i, opt; options) {
4606 			if (opt.selected)
4607 				return cast(int) i;
4608 		}
4609 		return -1;
4610 	}
4611 
4612 	version(custom_widgets)
4613 	override void defaultEventHandler_click(ClickEvent event) {
4614 		this.focus();
4615 		if(event.button == MouseButton.left) {
4616 			auto y = (event.clientY - 4) / defaultLineHeight;
4617 			if(y >= 0 && y < options.length) {
4618 				setSelection(y);
4619 			}
4620 		}
4621 		super.defaultEventHandler_click(event);
4622 	}
4623 
4624 	this(Widget parent) {
4625 		tabStop = false;
4626 		super(parent);
4627 		version(win32_widgets)
4628 			createWin32Window(this, WC_LISTBOX, "", 
4629 				0|WS_CHILD|WS_VISIBLE|LBS_NOTIFY, 0);
4630 	}
4631 
4632 	version(win32_widgets)
4633 	override void handleWmCommand(ushort code, ushort id) {
4634 		switch(code) {
4635 			case LBN_SELCHANGE:
4636 				auto sel = SendMessageW(hwnd, LB_GETCURSEL, 0, 0);
4637 				setSelection(cast(int) sel);
4638 			break;
4639 			default:
4640 		}
4641 	}
4642 
4643 
4644 	version(custom_widgets)
4645 	override void paintFrameAndBackground(WidgetPainter painter) {
4646 		draw3dFrame(this, painter, FrameStyle.sunk, Color.white);
4647 	}
4648 
4649 	version(custom_widgets)
4650 	override void paint(WidgetPainter painter) {
4651 		auto cs = getComputedStyle();
4652 		auto pos = Point(4, 4);
4653 		foreach(idx, option; options) {
4654 			painter.fillColor = Color.white;
4655 			painter.outlineColor = Color.white;
4656 			painter.drawRectangle(pos, width - 8, defaultLineHeight);
4657 			painter.outlineColor = cs.foregroundColor;
4658 			painter.drawText(pos, option.label);
4659 			if(option.selected) {
4660 				painter.rasterOp = RasterOp.xor;
4661 				painter.outlineColor = Color.white;
4662 				painter.fillColor = cs.activeListXorColor;
4663 				painter.drawRectangle(pos, width - 8, defaultLineHeight);
4664 				painter.rasterOp = RasterOp.normal;
4665 			}
4666 			pos.y += defaultLineHeight;
4667 		}
4668 	}
4669 
4670 	static class Style : Widget.Style {
4671 		override WidgetBackground background() {
4672 			return WidgetBackground(WidgetPainter.visualTheme.widgetBackgroundColor);
4673 		}
4674 	}
4675 	mixin OverrideStyle!Style;
4676 	//mixin Padding!q{2};
4677 
4678 	void addOption(string text, void* tag = null) {
4679 		options ~= Option(text, false, tag);
4680 		version(win32_widgets) {
4681 			WCharzBuffer buffer = WCharzBuffer(text);
4682 			SendMessageW(hwnd, LB_ADDSTRING, 0, cast(LPARAM) buffer.ptr);
4683 		}
4684 		version(custom_widgets) {
4685 			setContentSize(width, cast(int) (options.length * defaultLineHeight));
4686 			redraw();
4687 		}
4688 	}
4689 
4690 	void clear() {
4691 		options = null;
4692 		version(win32_widgets) {
4693 			while(SendMessageW(hwnd, LB_DELETESTRING, 0, 0) > 0)
4694 				{}
4695 
4696 		} else version(custom_widgets) {
4697 			scrollTo(Point(0, 0));
4698 			redraw();
4699 		}
4700 	}
4701 
4702 	Option[] options;
4703 	version(win32_widgets)
4704 		enum multiSelect = false; /// not implemented yet
4705 	else
4706 		bool multiSelect;
4707 
4708 	override int heightStretchiness() { return 6; }
4709 }
4710 
4711 
4712 
4713 /// For [ScrollableWidget], determines when to show the scroll bar to the user.
4714 enum ScrollBarShowPolicy {
4715 	automatic, /// automatically show the scroll bar if it is necessary
4716 	never, /// never show the scroll bar (scrolling must be done programmatically)
4717 	always /// always show the scroll bar, even if it is disabled
4718 }
4719 
4720 /++
4721 	A widget that tries (with, at best, limited success) to offer scrolling that is transparent to the inner.
4722 
4723 	It isn't very good and will very likely be removed. Try [ScrollMessageWidget] or [ScrollableContainerWidget] instead for new code.
4724 +/
4725 // FIXME ScrollBarShowPolicy
4726 // FIXME: use the ScrollMessageWidget in here now that it exists
4727 class ScrollableWidget : Widget {
4728 	// FIXME: make line size configurable
4729 	// FIXME: add keyboard controls
4730 	version(win32_widgets) {
4731 		override int hookedWndProc(UINT msg, WPARAM wParam, LPARAM lParam) {
4732 			if(msg == WM_VSCROLL || msg == WM_HSCROLL) {
4733 				auto pos = HIWORD(wParam);
4734 				auto m = LOWORD(wParam);
4735 
4736 				// FIXME: I can reintroduce the
4737 				// scroll bars now by using this
4738 				// in the top-level window handler
4739 				// to forward comamnds
4740 				auto scrollbarHwnd = lParam;
4741 				switch(m) {
4742 					case SB_BOTTOM:
4743 						if(msg == WM_HSCROLL)
4744 							horizontalScrollTo(contentWidth_);
4745 						else
4746 							verticalScrollTo(contentHeight_);
4747 					break;
4748 					case SB_TOP:
4749 						if(msg == WM_HSCROLL)
4750 							horizontalScrollTo(0);
4751 						else
4752 							verticalScrollTo(0);
4753 					break;
4754 					case SB_ENDSCROLL:
4755 						// idk
4756 					break;
4757 					case SB_LINEDOWN:
4758 						if(msg == WM_HSCROLL)
4759 							horizontalScroll(scaleWithDpi(16));
4760 						else
4761 							verticalScroll(scaleWithDpi(16));
4762 					break;
4763 					case SB_LINEUP:
4764 						if(msg == WM_HSCROLL)
4765 							horizontalScroll(scaleWithDpi(-16));
4766 						else
4767 							verticalScroll(scaleWithDpi(-16));
4768 					break;
4769 					case SB_PAGEDOWN:
4770 						if(msg == WM_HSCROLL)
4771 							horizontalScroll(scaleWithDpi(100));
4772 						else
4773 							verticalScroll(scaleWithDpi(100));
4774 					break;
4775 					case SB_PAGEUP:
4776 						if(msg == WM_HSCROLL)
4777 							horizontalScroll(scaleWithDpi(-100));
4778 						else
4779 							verticalScroll(scaleWithDpi(-100));
4780 					break;
4781 					case SB_THUMBPOSITION:
4782 					case SB_THUMBTRACK:
4783 						if(msg == WM_HSCROLL)
4784 							horizontalScrollTo(pos);
4785 						else
4786 							verticalScrollTo(pos);
4787 
4788 						if(m == SB_THUMBTRACK) {
4789 							// the event loop doesn't seem to carry on with a requested redraw..
4790 							// so we request it to get our dirty bit set...
4791 							redraw();
4792 
4793 							// then we need to immediately actually redraw it too for instant feedback to user
4794 
4795 							SimpleWindow.processAllCustomEvents();
4796 							//if(parentWindow)
4797 								//parentWindow.actualRedraw();
4798 						}
4799 					break;
4800 					default:
4801 				}
4802 			}
4803 			return super.hookedWndProc(msg, wParam, lParam);
4804 		}
4805 	}
4806 	///
4807 	this(Widget parent) {
4808 		this.parentWindow = parent.parentWindow;
4809 
4810 		version(win32_widgets) {
4811 			createWin32Window(this, Win32Class!"arsd_minigui_ScrollableWidget"w, "", 
4812 				0|WS_CHILD|WS_VISIBLE|WS_HSCROLL|WS_VSCROLL, 0);
4813 			super(parent);
4814 		} else version(custom_widgets) {
4815 			outerContainer = new InternalScrollableContainerWidget(this, parent);
4816 			super(outerContainer);
4817 		} else static assert(0);
4818 	}
4819 
4820 	version(custom_widgets)
4821 		InternalScrollableContainerWidget outerContainer;
4822 
4823 	override void defaultEventHandler_click(ClickEvent event) {
4824 		if(event.button == MouseButton.wheelUp)
4825 			verticalScroll(scaleWithDpi(-16));
4826 		if(event.button == MouseButton.wheelDown)
4827 			verticalScroll(scaleWithDpi(16));
4828 		super.defaultEventHandler_click(event);
4829 	}
4830 
4831 	override void defaultEventHandler_keydown(KeyDownEvent event) {
4832 		switch(event.key) {
4833 			case Key.Left:
4834 				horizontalScroll(scaleWithDpi(-16));
4835 			break;
4836 			case Key.Right:
4837 				horizontalScroll(scaleWithDpi(16));
4838 			break;
4839 			case Key.Up:
4840 				verticalScroll(scaleWithDpi(-16));
4841 			break;
4842 			case Key.Down:
4843 				verticalScroll(scaleWithDpi(16));
4844 			break;
4845 			case Key.Home:
4846 				verticalScrollTo(0);
4847 			break;
4848 			case Key.End:
4849 				verticalScrollTo(contentHeight);
4850 			break;
4851 			case Key.PageUp:
4852 				verticalScroll(scaleWithDpi(-160));
4853 			break;
4854 			case Key.PageDown:
4855 				verticalScroll(scaleWithDpi(160));
4856 			break;
4857 			default:
4858 		}
4859 		super.defaultEventHandler_keydown(event);
4860 	}
4861 
4862 
4863 	version(win32_widgets)
4864 	override void recomputeChildLayout() {
4865 		super.recomputeChildLayout();
4866 		SCROLLINFO info;
4867 		info.cbSize = info.sizeof;
4868 		info.nPage = viewportHeight;
4869 		info.fMask = SIF_PAGE | SIF_RANGE;
4870 		info.nMin = 0;
4871 		info.nMax = contentHeight_;
4872 		SetScrollInfo(hwnd, SB_VERT, &info, true);
4873 
4874 		info.cbSize = info.sizeof;
4875 		info.nPage = viewportWidth;
4876 		info.fMask = SIF_PAGE | SIF_RANGE;
4877 		info.nMin = 0;
4878 		info.nMax = contentWidth_;
4879 		SetScrollInfo(hwnd, SB_HORZ, &info, true);
4880 	}
4881 
4882 	/*
4883 		Scrolling
4884 		------------
4885 
4886 		You are assigned a width and a height by the layout engine, which
4887 		is your viewport box. However, you may draw more than that by setting
4888 		a contentWidth and contentHeight.
4889 
4890 		If these can be contained by the viewport, no scrollbar is displayed.
4891 		If they cannot fit though, it will automatically show scroll as necessary.
4892 
4893 		If contentWidth == 0, no horizontal scrolling is performed. If contentHeight
4894 		is zero, no vertical scrolling is performed.
4895 
4896 		If scrolling is necessary, the lib will automatically work with the bars.
4897 		When you redraw, the origin and clipping info in the painter is set so if
4898 		you just draw everything, it will work, but you can be more efficient by checking
4899 		the viewportWidth, viewportHeight, and scrollOrigin members.
4900 	*/
4901 
4902 	///
4903 	final @property int viewportWidth() {
4904 		return width - (showingVerticalScroll ? scaleWithDpi(16) : 0);
4905 	}
4906 	///
4907 	final @property int viewportHeight() {
4908 		return height - (showingHorizontalScroll ? scaleWithDpi(16) : 0);
4909 	}
4910 
4911 	// FIXME property
4912 	Point scrollOrigin_;
4913 
4914 	///
4915 	final const(Point) scrollOrigin() {
4916 		return scrollOrigin_;
4917 	}
4918 
4919 	// the user sets these two
4920 	private int contentWidth_ = 0;
4921 	private int contentHeight_ = 0;
4922 
4923 	///
4924 	int contentWidth() { return contentWidth_; }
4925 	///
4926 	int contentHeight() { return contentHeight_; }
4927 
4928 	///
4929 	void setContentSize(int width, int height) {
4930 		contentWidth_ = width;
4931 		contentHeight_ = height;
4932 
4933 		version(custom_widgets) {
4934 			if(showingVerticalScroll || showingHorizontalScroll) {
4935 				outerContainer.recomputeChildLayout();
4936 			}
4937 
4938 			if(showingVerticalScroll())
4939 				outerContainer.verticalScrollBar.redraw();
4940 			if(showingHorizontalScroll())
4941 				outerContainer.horizontalScrollBar.redraw();
4942 		} else version(win32_widgets) {
4943 			recomputeChildLayout();
4944 		} else static assert(0);
4945 	}
4946 
4947 	///
4948 	void verticalScroll(int delta) {
4949 		verticalScrollTo(scrollOrigin.y + delta);
4950 	}
4951 	///
4952 	void verticalScrollTo(int pos) {
4953 		scrollOrigin_.y = pos;
4954 		if(pos == int.max || (scrollOrigin_.y + viewportHeight > contentHeight))
4955 			scrollOrigin_.y = contentHeight - viewportHeight;
4956 
4957 		if(scrollOrigin_.y < 0)
4958 			scrollOrigin_.y = 0;
4959 
4960 		version(win32_widgets) {
4961 			SCROLLINFO info;
4962 			info.cbSize = info.sizeof;
4963 			info.fMask = SIF_POS;
4964 			info.nPos = scrollOrigin_.y;
4965 			SetScrollInfo(hwnd, SB_VERT, &info, true);
4966 		} else version(custom_widgets) {
4967 			outerContainer.verticalScrollBar.setPosition(scrollOrigin_.y);
4968 		} else static assert(0);
4969 
4970 		redraw();
4971 	}
4972 
4973 	///
4974 	void horizontalScroll(int delta) {
4975 		horizontalScrollTo(scrollOrigin.x + delta);
4976 	}
4977 	///
4978 	void horizontalScrollTo(int pos) {
4979 		scrollOrigin_.x = pos;
4980 		if(pos == int.max || (scrollOrigin_.x + viewportWidth > contentWidth))
4981 			scrollOrigin_.x = contentWidth - viewportWidth;
4982 
4983 		if(scrollOrigin_.x < 0)
4984 			scrollOrigin_.x = 0;
4985 
4986 		version(win32_widgets) {
4987 			SCROLLINFO info;
4988 			info.cbSize = info.sizeof;
4989 			info.fMask = SIF_POS;
4990 			info.nPos = scrollOrigin_.x;
4991 			SetScrollInfo(hwnd, SB_HORZ, &info, true);
4992 		} else version(custom_widgets) {
4993 			outerContainer.horizontalScrollBar.setPosition(scrollOrigin_.x);
4994 		} else static assert(0);
4995 
4996 		redraw();
4997 	}
4998 	///
4999 	void scrollTo(Point p) {
5000 		verticalScrollTo(p.y);
5001 		horizontalScrollTo(p.x);
5002 	}
5003 
5004 	///
5005 	void ensureVisibleInScroll(Point p) {
5006 		auto rect = viewportRectangle();
5007 		if(rect.contains(p))
5008 			return;
5009 		if(p.x < rect.left)
5010 			horizontalScroll(p.x - rect.left);
5011 		else if(p.x > rect.right)
5012 			horizontalScroll(p.x - rect.right);
5013 
5014 		if(p.y < rect.top)
5015 			verticalScroll(p.y - rect.top);
5016 		else if(p.y > rect.bottom)
5017 			verticalScroll(p.y - rect.bottom);
5018 	}
5019 
5020 	///
5021 	void ensureVisibleInScroll(Rectangle rect) {
5022 		ensureVisibleInScroll(rect.upperLeft);
5023 		ensureVisibleInScroll(rect.lowerRight);
5024 	}
5025 
5026 	///
5027 	Rectangle viewportRectangle() {
5028 		return Rectangle(scrollOrigin, Size(viewportWidth, viewportHeight));
5029 	}
5030 
5031 	///
5032 	bool showingHorizontalScroll() {
5033 		return contentWidth > width;
5034 	}
5035 	///
5036 	bool showingVerticalScroll() {
5037 		return contentHeight > height;
5038 	}
5039 
5040 	/// This is called before the ordinary paint delegate,
5041 	/// giving you a chance to draw the window frame, etc,
5042 	/// before the scroll clip takes effect
5043 	void paintFrameAndBackground(WidgetPainter painter) {
5044 		version(win32_widgets) {
5045 			auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
5046 			auto p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
5047 			// since the pen is null, to fill the whole space, we need the +1 on both.
5048 			gdi.Rectangle(painter.impl.hdc, 0, 0, this.width + 1, this.height + 1);
5049 			SelectObject(painter.impl.hdc, p);
5050 			SelectObject(painter.impl.hdc, b);
5051 		}
5052 
5053 	}
5054 
5055 	// make space for the scroll bar, and that's it.
5056 	final override int paddingRight() { return scaleWithDpi(16); }
5057 	final override int paddingBottom() { return scaleWithDpi(16); }
5058 
5059 	/*
5060 		END SCROLLING
5061 	*/
5062 
5063 	override WidgetPainter draw() {
5064 		int x = this.x, y = this.y;
5065 		auto parent = this.parent;
5066 		while(parent) {
5067 			x += parent.x;
5068 			y += parent.y;
5069 			parent = parent.parent;
5070 		}
5071 
5072 		//version(win32_widgets) {
5073 			//auto painter = simpleWindowWrappingHwnd ? simpleWindowWrappingHwnd.draw(true) : parentWindow.win.draw(true);
5074 		//} else {
5075 			auto painter = parentWindow.win.draw(true);
5076 		//}
5077 		painter.originX = x;
5078 		painter.originY = y;
5079 
5080 		painter.originX = painter.originX - scrollOrigin.x;
5081 		painter.originY = painter.originY - scrollOrigin.y;
5082 		painter.setClipRectangle(scrollOrigin, viewportWidth(), viewportHeight());
5083 
5084 		return WidgetPainter(painter, this);
5085 	}
5086 
5087 	mixin ScrollableChildren;
5088 }
5089 
5090 // you need to have a Point scrollOrigin in the class somewhere
5091 // and a paintFrameAndBackground
5092 private mixin template ScrollableChildren() {
5093 	override protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force, bool invalidate) {
5094 		if(hidden)
5095 			return;
5096 
5097 		//version(win32_widgets)
5098 			//painter = simpleWindowWrappingHwnd ? simpleWindowWrappingHwnd.draw(true) : parentWindow.win.draw(true);
5099 
5100 		painter.originX = lox + x;
5101 		painter.originY = loy + y;
5102 
5103 		bool actuallyPainted = false;
5104 
5105 		const clip = containment.intersectionOf(Rectangle(Point(lox + x, loy + y), Size(width, height)));
5106 		if(clip == Rectangle.init)
5107 			return;
5108 
5109 		if(force || redrawRequested) {
5110 			//painter.setClipRectangle(scrollOrigin, width, height);
5111 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
5112 			paintFrameAndBackground(painter);
5113 		}
5114 
5115 		painter.originX = painter.originX - scrollOrigin.x;
5116 		painter.originY = painter.originY - scrollOrigin.y;
5117 		if(force || redrawRequested) {
5118 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY) + Point(2, 2) /* border */, clip.width - 4, clip.height - 4);
5119 			//painter.setClipRectangle(scrollOrigin + Point(2, 2) /* border */, width - 4, height - 4);
5120 
5121 			//erase(painter); // we paintFrameAndBackground above so no need
5122 			if(painter.visualTheme)
5123 				painter.visualTheme.doPaint(this, painter);
5124 			else
5125 				paint(painter);
5126 
5127 			if(invalidate) {
5128 				painter.invalidateRect(Rectangle(Point(clip.upperLeft.x - painter.originX, clip.upperRight.y - painter.originY), Size(clip.width, clip.height)));
5129 				// children are contained inside this, so no need to do extra work
5130 				invalidate = false;
5131 			}
5132 
5133 
5134 			actuallyPainted = true;
5135 			redrawRequested = false;
5136 		}
5137 		foreach(child; children) {
5138 			if(cast(FixedPosition) child)
5139 				child.privatePaint(painter, painter.originX + scrollOrigin.x, painter.originY + scrollOrigin.y, clip, actuallyPainted, invalidate);
5140 			else
5141 				child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted, invalidate);
5142 		}
5143 	}
5144 }
5145 
5146 private class InternalScrollableContainerInsideWidget : ContainerWidget {
5147 	ScrollableContainerWidget scw;
5148 
5149 	this(ScrollableContainerWidget parent) {
5150 		scw = parent;
5151 		super(parent);
5152 	}
5153 
5154 	version(custom_widgets)
5155 	override protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force, bool invalidate) {
5156 		if(hidden)
5157 			return;
5158 
5159 		bool actuallyPainted = false;
5160 
5161 		auto scrollOrigin = Point(scw.scrollX_, scw.scrollY_);
5162 
5163 		const clip = containment.intersectionOf(Rectangle(Point(lox + x, loy + y), Size(width + scw.scrollX_, height + scw.scrollY_)));
5164 		if(clip == Rectangle.init)
5165 			return;
5166 
5167 		painter.originX = lox + x - scrollOrigin.x;
5168 		painter.originY = loy + y - scrollOrigin.y;
5169 		if(force || redrawRequested) {
5170 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
5171 
5172 			erase(painter);
5173 			if(painter.visualTheme)
5174 				painter.visualTheme.doPaint(this, painter);
5175 			else
5176 				paint(painter);
5177 
5178 			if(invalidate) {
5179 				painter.invalidateRect(Rectangle(Point(clip.upperLeft.x - painter.originX, clip.upperRight.y - painter.originY), Size(clip.width, clip.height)));
5180 				// children are contained inside this, so no need to do extra work
5181 				invalidate = false;
5182 			}
5183 
5184 			actuallyPainted = true;
5185 			redrawRequested = false;
5186 		}
5187 		foreach(child; children) {
5188 			if(cast(FixedPosition) child)
5189 				child.privatePaint(painter, painter.originX + scrollOrigin.x, painter.originY + scrollOrigin.y, clip, actuallyPainted, invalidate);
5190 			else
5191 				child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted, invalidate);
5192 		}
5193 	}
5194 
5195 	version(custom_widgets)
5196 	override protected void addScrollPosition(ref int x, ref int y) {
5197 		x += scw.scrollX_;
5198 		y += scw.scrollY_;
5199 	}
5200 }
5201 
5202 /++
5203 	A widget meant to contain other widgets that may need to scroll.
5204 
5205 	Currently buggy.
5206 
5207 	History:
5208 		Added July 1, 2021 (dub v10.2)
5209 
5210 		On January 3, 2022, I tried to use it in a few other cases
5211 		and found it only worked well in the original test case. Since
5212 		it still sucks, I think I'm going to rewrite it again.
5213 +/
5214 class ScrollableContainerWidget : ContainerWidget {
5215 	///
5216 	this(Widget parent) {
5217 		super(parent);
5218 
5219 		container = new InternalScrollableContainerInsideWidget(this);
5220 		hsb = new HorizontalScrollbar(this);
5221 		vsb = new VerticalScrollbar(this);
5222 
5223 		tabStop = false;
5224 		container.tabStop = false;
5225 		magic = true;
5226 
5227 
5228 		vsb.addEventListener("scrolltonextline", () {
5229 			scrollBy(0, scaleWithDpi(16));
5230 		});
5231 		vsb.addEventListener("scrolltopreviousline", () {
5232 			scrollBy(0,scaleWithDpi( -16));
5233 		});
5234 		vsb.addEventListener("scrolltonextpage", () {
5235 			scrollBy(0, container.height);
5236 		});
5237 		vsb.addEventListener("scrolltopreviouspage", () {
5238 			scrollBy(0, -container.height);
5239 		});
5240 		vsb.addEventListener((scope ScrollToPositionEvent spe) {
5241 			scrollTo(scrollX_, spe.value);
5242 		});
5243 
5244 		this.addEventListener(delegate (scope ClickEvent e) {
5245 			if(e.button == MouseButton.wheelUp) {
5246 				if(!e.defaultPrevented)
5247 					scrollBy(0, scaleWithDpi(-16));
5248 				e.stopPropagation();
5249 			} else if(e.button == MouseButton.wheelDown) {
5250 				if(!e.defaultPrevented)
5251 					scrollBy(0, scaleWithDpi(16));
5252 				e.stopPropagation();
5253 			}
5254 		});
5255 	}
5256 
5257 	/+
5258 	override void defaultEventHandler_click(ClickEvent e) {
5259 	}
5260 	+/
5261 
5262 	override void removeAllChildren() {
5263 		container.removeAllChildren();
5264 	}
5265 
5266 	void scrollTo(int x, int y) {
5267 		scrollBy(x - scrollX_, y - scrollY_);
5268 	}
5269 
5270 	void scrollBy(int x, int y) {
5271 		auto ox = scrollX_;
5272 		auto oy = scrollY_;
5273 
5274 		auto nx = ox + x;
5275 		auto ny = oy + y;
5276 
5277 		if(nx < 0)
5278 			nx = 0;
5279 		if(ny < 0)
5280 			ny = 0;
5281 
5282 		auto maxX = hsb.max - container.width;
5283 		if(maxX < 0) maxX = 0;
5284 		auto maxY = vsb.max - container.height;
5285 		if(maxY < 0) maxY = 0;
5286 
5287 		if(nx > maxX)
5288 			nx = maxX;
5289 		if(ny > maxY)
5290 			ny = maxY;
5291 
5292 		auto dx = nx - ox;
5293 		auto dy = ny - oy;
5294 
5295 		if(dx || dy) {
5296 			version(win32_widgets)
5297 				ScrollWindowEx(container.hwnd, -dx, -dy, null, null, null, null, SW_SCROLLCHILDREN | SW_INVALIDATE | SW_ERASE);
5298 			else {
5299 				redraw();
5300 			}
5301 
5302 			hsb.setPosition = nx;
5303 			vsb.setPosition = ny;
5304 
5305 			scrollX_ = nx;
5306 			scrollY_ = ny;
5307 		}
5308 	}
5309 
5310 	private int scrollX_;
5311 	private int scrollY_;
5312 
5313 	void setTotalArea(int width, int height) {
5314 		hsb.setMax(width);
5315 		vsb.setMax(height);
5316 	}
5317 
5318 	///
5319 	void setViewableArea(int width, int height) {
5320 		hsb.setViewableArea(width);
5321 		vsb.setViewableArea(height);
5322 	}
5323 
5324 	private bool magic;
5325 	override void addChild(Widget w, int position = int.max) {
5326 		if(magic)
5327 			container.addChild(w, position);
5328 		else
5329 			super.addChild(w, position);
5330 	}
5331 
5332 	override void recomputeChildLayout() {
5333 		if(hsb is null || vsb is null || container is null) return;
5334 
5335 		/+
5336 		import std.stdio; writeln(x, " ", y , " ", width, " ", height);
5337 		writeln(this.ContainerWidget.minWidth(), "x", this.ContainerWidget.minHeight());
5338 		+/
5339 
5340 		registerMovement();
5341 
5342 		hsb.height = scaleWithDpi(16); // FIXME? are tese 16s sane?
5343 		hsb.x = 0;
5344 		hsb.y = this.height - hsb.height;
5345 		hsb.width = this.width - scaleWithDpi(16);
5346 		hsb.recomputeChildLayout();
5347 
5348 		vsb.width = scaleWithDpi(16); // FIXME?
5349 		vsb.x = this.width - vsb.width;
5350 		vsb.y = 0;
5351 		vsb.height = this.height - scaleWithDpi(16);
5352 		vsb.recomputeChildLayout();
5353 
5354 		container.x = 0;
5355 		container.y = 0;
5356 		container.width = this.width - vsb.width;
5357 		container.height = this.height - hsb.height;
5358 		container.recomputeChildLayout();
5359 
5360 		scrollX_ = 0;
5361 		scrollY_ = 0;
5362 
5363 		hsb.setPosition(0);
5364 		vsb.setPosition(0);
5365 
5366 		int mw, mh;
5367 		Widget c = container;
5368 		// FIXME: hack here to handle a layout inside...
5369 		if(c.children.length == 1 && cast(Layout) c.children[0])
5370 			c = c.children[0];
5371 		foreach(child; c.children) {
5372 			auto w = child.x + child.width;
5373 			auto h = child.y + child.height;
5374 
5375 			if(w > mw) mw = w;
5376 			if(h > mh) mh = h;
5377 		}
5378 
5379 		setTotalArea(mw, mh);
5380 		setViewableArea(width, height);
5381 	}
5382 
5383 	override int minHeight() { return scaleWithDpi(64); }
5384 
5385 	HorizontalScrollbar hsb;
5386 	VerticalScrollbar vsb;
5387 	ContainerWidget container;
5388 }
5389 
5390 
5391 version(custom_widgets)
5392 private class InternalScrollableContainerWidget : Widget {
5393 
5394 	ScrollableWidget sw;
5395 
5396 	VerticalScrollbar verticalScrollBar;
5397 	HorizontalScrollbar horizontalScrollBar;
5398 
5399 	this(ScrollableWidget sw, Widget parent) {
5400 		this.sw = sw;
5401 
5402 		this.tabStop = false;
5403 
5404 		horizontalScrollBar = new HorizontalScrollbar(this);
5405 		verticalScrollBar = new VerticalScrollbar(this);
5406 
5407 		horizontalScrollBar.showing_ = false;
5408 		verticalScrollBar.showing_ = false;
5409 
5410 		horizontalScrollBar.addEventListener("scrolltonextline", {
5411 			horizontalScrollBar.setPosition(horizontalScrollBar.position + 1);
5412 			sw.horizontalScrollTo(horizontalScrollBar.position);
5413 		});
5414 		horizontalScrollBar.addEventListener("scrolltopreviousline", {
5415 			horizontalScrollBar.setPosition(horizontalScrollBar.position - 1);
5416 			sw.horizontalScrollTo(horizontalScrollBar.position);
5417 		});
5418 		verticalScrollBar.addEventListener("scrolltonextline", {
5419 			verticalScrollBar.setPosition(verticalScrollBar.position + 1);
5420 			sw.verticalScrollTo(verticalScrollBar.position);
5421 		});
5422 		verticalScrollBar.addEventListener("scrolltopreviousline", {
5423 			verticalScrollBar.setPosition(verticalScrollBar.position - 1);
5424 			sw.verticalScrollTo(verticalScrollBar.position);
5425 		});
5426 		horizontalScrollBar.addEventListener("scrolltonextpage", {
5427 			horizontalScrollBar.setPosition(horizontalScrollBar.position + horizontalScrollBar.step_);
5428 			sw.horizontalScrollTo(horizontalScrollBar.position);
5429 		});
5430 		horizontalScrollBar.addEventListener("scrolltopreviouspage", {
5431 			horizontalScrollBar.setPosition(horizontalScrollBar.position - horizontalScrollBar.step_);
5432 			sw.horizontalScrollTo(horizontalScrollBar.position);
5433 		});
5434 		verticalScrollBar.addEventListener("scrolltonextpage", {
5435 			verticalScrollBar.setPosition(verticalScrollBar.position + verticalScrollBar.step_);
5436 			sw.verticalScrollTo(verticalScrollBar.position);
5437 		});
5438 		verticalScrollBar.addEventListener("scrolltopreviouspage", {
5439 			verticalScrollBar.setPosition(verticalScrollBar.position - verticalScrollBar.step_);
5440 			sw.verticalScrollTo(verticalScrollBar.position);
5441 		});
5442 		horizontalScrollBar.addEventListener("scrolltoposition", (Event event) {
5443 			horizontalScrollBar.setPosition(event.intValue);
5444 			sw.horizontalScrollTo(horizontalScrollBar.position);
5445 		});
5446 		verticalScrollBar.addEventListener("scrolltoposition", (Event event) {
5447 			verticalScrollBar.setPosition(event.intValue);
5448 			sw.verticalScrollTo(verticalScrollBar.position);
5449 		});
5450 		horizontalScrollBar.addEventListener("scrolltrack", (Event event) {
5451 			horizontalScrollBar.setPosition(event.intValue);
5452 			sw.horizontalScrollTo(horizontalScrollBar.position);
5453 		});
5454 		verticalScrollBar.addEventListener("scrolltrack", (Event event) {
5455 			verticalScrollBar.setPosition(event.intValue);
5456 		});
5457 
5458 		super(parent);
5459 	}
5460 
5461 	// this is supposed to be basically invisible...
5462 	override int minWidth() { return sw.minWidth; }
5463 	override int minHeight() { return sw.minHeight; }
5464 	override int maxWidth() { return sw.maxWidth; }
5465 	override int maxHeight() { return sw.maxHeight; }
5466 	override int widthStretchiness() { return sw.widthStretchiness; }
5467 	override int heightStretchiness() { return sw.heightStretchiness; }
5468 	override int marginLeft() { return sw.marginLeft; }
5469 	override int marginRight() { return sw.marginRight; }
5470 	override int marginTop() { return sw.marginTop; }
5471 	override int marginBottom() { return sw.marginBottom; }
5472 	override int paddingLeft() { return sw.paddingLeft; }
5473 	override int paddingRight() { return sw.paddingRight; }
5474 	override int paddingTop() { return sw.paddingTop; }
5475 	override int paddingBottom() { return sw.paddingBottom; }
5476 	override void focus() { sw.focus(); }
5477 
5478 
5479 	override void recomputeChildLayout() {
5480 		// The stupid thing needs to calculate if a scroll bar is needed...
5481 		recomputeChildLayoutHelper();
5482 		// then running it again will position things correctly if the bar is NOT needed
5483 		recomputeChildLayoutHelper();
5484 
5485 		// this sucks but meh it barely works
5486 	}
5487 
5488 	private void recomputeChildLayoutHelper() {
5489 		if(sw is null) return;
5490 
5491 		bool both = sw.showingVerticalScroll && sw.showingHorizontalScroll;
5492 		if(horizontalScrollBar && verticalScrollBar) {
5493 			horizontalScrollBar.width = this.width - (both ? verticalScrollBar.minWidth() : 0);
5494 			horizontalScrollBar.height = horizontalScrollBar.minHeight();
5495 			horizontalScrollBar.x = 0;
5496 			horizontalScrollBar.y = this.height - horizontalScrollBar.minHeight();
5497 
5498 			verticalScrollBar.width = verticalScrollBar.minWidth();
5499 			verticalScrollBar.height = this.height - (both ? horizontalScrollBar.minHeight() : 0) - 2 - 2;
5500 			verticalScrollBar.x = this.width - verticalScrollBar.minWidth();
5501 			verticalScrollBar.y = 0 + 2;
5502 
5503 			sw.x = 0;
5504 			sw.y = 0;
5505 			sw.width = this.width - (verticalScrollBar.showing ? verticalScrollBar.width : 0);
5506 			sw.height = this.height - (horizontalScrollBar.showing ? horizontalScrollBar.height : 0);
5507 
5508 			if(sw.contentWidth_ <= this.width)
5509 				sw.scrollOrigin_.x = 0;
5510 			if(sw.contentHeight_ <= this.height)
5511 				sw.scrollOrigin_.y = 0;
5512 
5513 			horizontalScrollBar.recomputeChildLayout();
5514 			verticalScrollBar.recomputeChildLayout();
5515 			sw.recomputeChildLayout();
5516 		}
5517 
5518 		if(sw.contentWidth_ <= this.width)
5519 			sw.scrollOrigin_.x = 0;
5520 		if(sw.contentHeight_ <= this.height)
5521 			sw.scrollOrigin_.y = 0;
5522 
5523 		if(sw.showingHorizontalScroll())
5524 			horizontalScrollBar.showing(true, false);
5525 		else
5526 			horizontalScrollBar.showing(false, false);
5527 		if(sw.showingVerticalScroll())
5528 			verticalScrollBar.showing(true, false);
5529 		else
5530 			verticalScrollBar.showing(false, false);
5531 
5532 		verticalScrollBar.setViewableArea(sw.viewportHeight());
5533 		verticalScrollBar.setMax(sw.contentHeight);
5534 		verticalScrollBar.setPosition(sw.scrollOrigin.y);
5535 
5536 		horizontalScrollBar.setViewableArea(sw.viewportWidth());
5537 		horizontalScrollBar.setMax(sw.contentWidth);
5538 		horizontalScrollBar.setPosition(sw.scrollOrigin.x);
5539 	}
5540 }
5541 
5542 /*
5543 class ScrollableClientWidget : Widget {
5544 	this(Widget parent) {
5545 		super(parent);
5546 	}
5547 	override void paint(WidgetPainter p) {
5548 		parent.paint(p);
5549 	}
5550 }
5551 */
5552 
5553 /++
5554 	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.
5555 +/
5556 abstract class Slider : Widget {
5557 	this(int min, int max, int step, Widget parent) {
5558 		min_ = min;
5559 		max_ = max;
5560 		step_ = step;
5561 		page_ = step;
5562 		super(parent);
5563 	}
5564 
5565 	private int min_;
5566 	private int max_;
5567 	private int step_;
5568 	private int position_;
5569 	private int page_;
5570 
5571 	// selection start and selection end
5572 	// tics
5573 	// tooltip?
5574 	// some way to see and just type the value
5575 	// win32 buddy controls are labels
5576 
5577 	///
5578 	void setMin(int a) {
5579 		min_ = a;
5580 		version(custom_widgets)
5581 			redraw();
5582 		version(win32_widgets)
5583 			SendMessage(hwnd, TBM_SETRANGEMIN, true, a);
5584 	}
5585 	///
5586 	int min() {
5587 		return min_;
5588 	}
5589 	///
5590 	void setMax(int a) {
5591 		max_ = a;
5592 		version(custom_widgets)
5593 			redraw();
5594 		version(win32_widgets)
5595 			SendMessage(hwnd, TBM_SETRANGEMAX, true, a);
5596 	}
5597 	///
5598 	int max() {
5599 		return max_;
5600 	}
5601 	///
5602 	void setPosition(int a) {
5603 		if(a > max)
5604 			a = max;
5605 		if(a < min)
5606 			a = min;
5607 		position_ = a;
5608 		version(custom_widgets)
5609 			setPositionCustom(a);
5610 
5611 		version(win32_widgets)
5612 			setPositionWindows(a);
5613 	}
5614 	version(win32_widgets) {
5615 		protected abstract void setPositionWindows(int a);
5616 	}
5617 
5618 	protected abstract int win32direction();
5619 
5620 	/++
5621 		Alias for [position] for better compatibility with generic code.
5622 
5623 		History:
5624 			Added October 5, 2021
5625 	+/
5626 	@property int value() {
5627 		return position;
5628 	}
5629 
5630 	///
5631 	int position() {
5632 		return position_;
5633 	}
5634 	///
5635 	void setStep(int a) {
5636 		step_ = a;
5637 		version(win32_widgets)
5638 			SendMessage(hwnd, TBM_SETLINESIZE, 0, a);
5639 	}
5640 	///
5641 	int step() {
5642 		return step_;
5643 	}
5644 	///
5645 	void setPageSize(int a) {
5646 		page_ = a;
5647 		version(win32_widgets)
5648 			SendMessage(hwnd, TBM_SETPAGESIZE, 0, a);
5649 	}
5650 	///
5651 	int pageSize() {
5652 		return page_;
5653 	}
5654 
5655 	private void notify() {
5656 		auto event = new ChangeEvent!int(this, &this.position);
5657 		event.dispatch();
5658 	}
5659 
5660 	version(win32_widgets)
5661 	void win32Setup(int style) {
5662 		createWin32Window(this, TRACKBAR_CLASS, "", 
5663 			0|WS_CHILD|WS_VISIBLE|style|TBS_TOOLTIPS, 0);
5664 
5665 		// the trackbar sends the same messages as scroll, which
5666 		// our other layer sends as these... just gonna translate
5667 		// here
5668 		this.addDirectEventListener("scrolltoposition", (Event event) {
5669 			event.stopPropagation();
5670 			this.setPosition(this.win32direction > 0 ? event.intValue : max - event.intValue);
5671 			notify();
5672 		});
5673 		this.addDirectEventListener("scrolltonextline", (Event event) {
5674 			event.stopPropagation();
5675 			this.setPosition(this.position + this.step_ * this.win32direction);
5676 			notify();
5677 		});
5678 		this.addDirectEventListener("scrolltopreviousline", (Event event) {
5679 			event.stopPropagation();
5680 			this.setPosition(this.position - this.step_ * this.win32direction);
5681 			notify();
5682 		});
5683 		this.addDirectEventListener("scrolltonextpage", (Event event) {
5684 			event.stopPropagation();
5685 			this.setPosition(this.position + this.page_ * this.win32direction);
5686 			notify();
5687 		});
5688 		this.addDirectEventListener("scrolltopreviouspage", (Event event) {
5689 			event.stopPropagation();
5690 			this.setPosition(this.position - this.page_ * this.win32direction);
5691 			notify();
5692 		});
5693 
5694 		setMin(min_);
5695 		setMax(max_);
5696 		setStep(step_);
5697 		setPageSize(page_);
5698 	}
5699 
5700 	version(custom_widgets) {
5701 		protected MouseTrackingWidget thumb;
5702 
5703 		protected abstract void setPositionCustom(int a);
5704 
5705 		override void defaultEventHandler_keydown(KeyDownEvent event) {
5706 			switch(event.key) {
5707 				case Key.Up:
5708 				case Key.Right:
5709 					setPosition(position() - step() * win32direction);
5710 					changed();
5711 				break;
5712 				case Key.Down:
5713 				case Key.Left:
5714 					setPosition(position() + step() * win32direction);
5715 					changed();
5716 				break;
5717 				case Key.Home:
5718 					setPosition(win32direction > 0 ? min() : max());
5719 					changed();
5720 				break;
5721 				case Key.End:
5722 					setPosition(win32direction > 0 ? max() : min());
5723 					changed();
5724 				break;
5725 				case Key.PageUp:
5726 					setPosition(position() - pageSize() * win32direction);
5727 					changed();
5728 				break;
5729 				case Key.PageDown:
5730 					setPosition(position() + pageSize() * win32direction);
5731 					changed();
5732 				break;
5733 				default:
5734 			}
5735 			super.defaultEventHandler_keydown(event);
5736 		}
5737 
5738 		protected void changed() {
5739 			auto ev = new ChangeEvent!int(this, &position);
5740 			ev.dispatch();
5741 		}
5742 	}
5743 }
5744 
5745 /++
5746 
5747 +/
5748 class VerticalSlider : Slider {
5749 	this(int min, int max, int step, Widget parent) {
5750 		version(custom_widgets)
5751 			initialize();
5752 
5753 		super(min, max, step, parent);
5754 
5755 		version(win32_widgets)
5756 			win32Setup(TBS_VERT | 0x0200 /* TBS_REVERSED */);
5757 	}
5758 
5759 	protected override int win32direction() {
5760 		return -1;
5761 	}
5762 
5763 	version(win32_widgets)
5764 	protected override void setPositionWindows(int a) {
5765 		// the windows thing makes the top 0 and i don't like that.
5766 		SendMessage(hwnd, TBM_SETPOS, true, max - a);
5767 	}
5768 
5769 	version(custom_widgets)
5770 	private void initialize() {
5771 		thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.vertical, this);
5772 
5773 		thumb.tabStop = false;
5774 
5775 		thumb.thumbWidth = width;
5776 		thumb.thumbHeight = scaleWithDpi(16);
5777 
5778 		thumb.addEventListener(EventType.change, () {
5779 			auto sx = thumb.positionY * max() / (thumb.height - scaleWithDpi(16));
5780 			sx = max - sx;
5781 			//informProgramThatUserChangedPosition(sx);
5782 
5783 			position_ = sx;
5784 
5785 			changed();
5786 		});
5787 	}
5788 
5789 	version(custom_widgets)
5790 	override void recomputeChildLayout() {
5791 		thumb.thumbWidth = this.width;
5792 		super.recomputeChildLayout();
5793 		setPositionCustom(position_);
5794 	}
5795 
5796 	version(custom_widgets)
5797 	protected override void setPositionCustom(int a) {
5798 		if(max())
5799 			thumb.positionY = (max - a) * (thumb.height - scaleWithDpi(16)) / max();
5800 		redraw();
5801 	}
5802 }
5803 
5804 /++
5805 
5806 +/
5807 class HorizontalSlider : Slider {
5808 	this(int min, int max, int step, Widget parent) {
5809 		version(custom_widgets)
5810 			initialize();
5811 
5812 		super(min, max, step, parent);
5813 
5814 		version(win32_widgets)
5815 			win32Setup(TBS_HORZ);
5816 	}
5817 
5818 	version(win32_widgets)
5819 	protected override void setPositionWindows(int a) {
5820 		SendMessage(hwnd, TBM_SETPOS, true, a);
5821 	}
5822 
5823 	protected override int win32direction() {
5824 		return 1;
5825 	}
5826 
5827 	version(custom_widgets)
5828 	private void initialize() {
5829 		thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.horizontal, this);
5830 
5831 		thumb.tabStop = false;
5832 
5833 		thumb.thumbWidth = scaleWithDpi(16);
5834 		thumb.thumbHeight = height;
5835 
5836 		thumb.addEventListener(EventType.change, () {
5837 			auto sx = thumb.positionX * max() / (thumb.width - scaleWithDpi(16));
5838 			//informProgramThatUserChangedPosition(sx);
5839 
5840 			position_ = sx;
5841 
5842 			changed();
5843 		});
5844 	}
5845 
5846 	version(custom_widgets)
5847 	override void recomputeChildLayout() {
5848 		thumb.thumbHeight = this.height;
5849 		super.recomputeChildLayout();
5850 		setPositionCustom(position_);
5851 	}
5852 
5853 	version(custom_widgets)
5854 	protected override void setPositionCustom(int a) {
5855 		if(max())
5856 			thumb.positionX = a * (thumb.width - scaleWithDpi(16)) / max();
5857 		redraw();
5858 	}
5859 }
5860 
5861 
5862 ///
5863 abstract class ScrollbarBase : Widget {
5864 	///
5865 	this(Widget parent) {
5866 		super(parent);
5867 		tabStop = false;
5868 		step_ = scaleWithDpi(16);
5869 	}
5870 
5871 	private int viewableArea_;
5872 	private int max_;
5873 	private int step_;// = 16;
5874 	private int position_;
5875 
5876 	///
5877 	bool atEnd() {
5878 		return position_ + viewableArea_ >= max_;
5879 	}
5880 
5881 	///
5882 	bool atStart() {
5883 		return position_ == 0;
5884 	}
5885 
5886 	///
5887 	void setViewableArea(int a) {
5888 		viewableArea_ = a;
5889 		version(custom_widgets)
5890 			redraw();
5891 	}
5892 	///
5893 	void setMax(int a) {
5894 		max_ = a;
5895 		version(custom_widgets)
5896 			redraw();
5897 	}
5898 	///
5899 	int max() {
5900 		return max_;
5901 	}
5902 	///
5903 	void setPosition(int a) {
5904 		if(a == int.max)
5905 			a = max;
5906 		position_ = max ? a : 0;
5907 		if(position_ + viewableArea_ > max)
5908 			position_ = max - viewableArea_;
5909 		if(position_ < 0)
5910 			position_ = 0;
5911 		version(custom_widgets)
5912 			redraw();
5913 	}
5914 	///
5915 	int position() {
5916 		return position_;
5917 	}
5918 	///
5919 	void setStep(int a) {
5920 		step_ = a;
5921 	}
5922 	///
5923 	int step() {
5924 		return step_;
5925 	}
5926 
5927 	// FIXME: remove this.... maybe
5928 	/+
5929 	protected void informProgramThatUserChangedPosition(int n) {
5930 		position_ = n;
5931 		auto evt = new Event(EventType.change, this);
5932 		evt.intValue = n;
5933 		evt.dispatch();
5934 	}
5935 	+/
5936 
5937 	version(custom_widgets) {
5938 		abstract protected int getBarDim();
5939 		int thumbSize() {
5940 			if(viewableArea_ >= max_)
5941 				return getBarDim();
5942 
5943 			int res;
5944 			if(max_) {
5945 				res = getBarDim() * viewableArea_ / max_;
5946 			}
5947 			if(res < 6)
5948 				res = 6;
5949 
5950 			return res;
5951 		}
5952 
5953 		int thumbPosition() {
5954 			/*
5955 				viewableArea_ is the viewport height/width
5956 				position_ is where we are
5957 			*/
5958 			if(max_) {
5959 				if(position_ + viewableArea_ >= max_)
5960 					return getBarDim - thumbSize;
5961 				return getBarDim * position_ / max_;
5962 			}
5963 			return 0;
5964 		}
5965 	}
5966 }
5967 
5968 //public import mgt;
5969 
5970 /++
5971 	A mouse tracking widget is one that follows the mouse when dragged inside it.
5972 
5973 	Concrete subclasses may include a scrollbar thumb and a volume control.
5974 +/
5975 //version(custom_widgets)
5976 class MouseTrackingWidget : Widget {
5977 
5978 	///
5979 	int positionX() { return positionX_; }
5980 	///
5981 	int positionY() { return positionY_; }
5982 
5983 	///
5984 	void positionX(int p) { positionX_ = p; }
5985 	///
5986 	void positionY(int p) { positionY_ = p; }
5987 
5988 	private int positionX_;
5989 	private int positionY_;
5990 
5991 	///
5992 	enum Orientation {
5993 		horizontal, ///
5994 		vertical, ///
5995 		twoDimensional, ///
5996 	}
5997 
5998 	private int thumbWidth_;
5999 	private int thumbHeight_;
6000 
6001 	///
6002 	int thumbWidth() { return thumbWidth_; }
6003 	///
6004 	int thumbHeight() { return thumbHeight_; }
6005 	///
6006 	int thumbWidth(int a) { return thumbWidth_ = a; }
6007 	///
6008 	int thumbHeight(int a) { return thumbHeight_ = a; }
6009 
6010 	private bool dragging;
6011 	private bool hovering;
6012 	private int startMouseX, startMouseY;
6013 
6014 	///
6015 	this(Orientation orientation, Widget parent) {
6016 		super(parent);
6017 
6018 		//assert(parentWindow !is null);
6019 
6020 		addEventListener((MouseDownEvent event) {
6021 			if(event.clientX >= positionX && event.clientX < positionX + thumbWidth && event.clientY >= positionY && event.clientY < positionY + thumbHeight) {
6022 				dragging = true;
6023 				startMouseX = event.clientX - positionX;
6024 				startMouseY = event.clientY - positionY;
6025 				parentWindow.captureMouse(this);
6026 			} else {
6027 				if(orientation == Orientation.horizontal || orientation == Orientation.twoDimensional)
6028 					positionX = event.clientX - thumbWidth / 2;
6029 				if(orientation == Orientation.vertical || orientation == Orientation.twoDimensional)
6030 					positionY = event.clientY - thumbHeight / 2;
6031 
6032 				if(positionX + thumbWidth > this.width)
6033 					positionX = this.width - thumbWidth;
6034 				if(positionY + thumbHeight > this.height)
6035 					positionY = this.height - thumbHeight;
6036 
6037 				if(positionX < 0)
6038 					positionX = 0;
6039 				if(positionY < 0)
6040 					positionY = 0;
6041 
6042 
6043 				// this.emit!(ChangeEvent!void)();
6044 				auto evt = new Event(EventType.change, this);
6045 				evt.sendDirectly();
6046 
6047 				redraw();
6048 
6049 			}
6050 		});
6051 
6052 		addEventListener(EventType.mouseup, (Event event) {
6053 			dragging = false;
6054 			parentWindow.releaseMouseCapture();
6055 		});
6056 
6057 		addEventListener(EventType.mouseout, (Event event) {
6058 			if(!hovering)
6059 				return;
6060 			hovering = false;
6061 			redraw();
6062 		});
6063 
6064 		int lpx, lpy;
6065 
6066 		addEventListener((MouseMoveEvent event) {
6067 			auto oh = hovering;
6068 			if(event.clientX >= positionX && event.clientX < positionX + thumbWidth && event.clientY >= positionY && event.clientY < positionY + thumbHeight) {
6069 				hovering = true;
6070 			} else {
6071 				hovering = false;
6072 			}
6073 			if(!dragging) {
6074 				if(hovering != oh)
6075 					redraw();
6076 				return;
6077 			}
6078 
6079 			if(orientation == Orientation.horizontal || orientation == Orientation.twoDimensional)
6080 				positionX = event.clientX - startMouseX; // FIXME: click could be in the middle of it
6081 			if(orientation == Orientation.vertical || orientation == Orientation.twoDimensional)
6082 				positionY = event.clientY - startMouseY;
6083 
6084 			if(positionX + thumbWidth > this.width)
6085 				positionX = this.width - thumbWidth;
6086 			if(positionY + thumbHeight > this.height)
6087 				positionY = this.height - thumbHeight;
6088 
6089 			if(positionX < 0)
6090 				positionX = 0;
6091 			if(positionY < 0)
6092 				positionY = 0;
6093 
6094 			if(positionX != lpx || positionY != lpy) {
6095 				auto evt = new Event(EventType.change, this);
6096 				evt.sendDirectly();
6097 
6098 				lpx = positionX;
6099 				lpy = positionY;
6100 			}
6101 
6102 			redraw();
6103 		});
6104 	}
6105 
6106 	version(custom_widgets)
6107 	override void paint(WidgetPainter painter) {
6108 		auto cs = getComputedStyle();
6109 		auto c = darken(cs.windowBackgroundColor, 0.2);
6110 		painter.outlineColor = c;
6111 		painter.fillColor = c;
6112 		painter.drawRectangle(Point(0, 0), this.width, this.height);
6113 
6114 		auto color = hovering ? cs.hoveringColor : cs.windowBackgroundColor;
6115 		draw3dFrame(positionX, positionY, thumbWidth, thumbHeight, painter, FrameStyle.risen, color);
6116 	}
6117 }
6118 
6119 //version(custom_widgets)
6120 //private
6121 class HorizontalScrollbar : ScrollbarBase {
6122 
6123 	version(custom_widgets) {
6124 		private MouseTrackingWidget thumb;
6125 
6126 		override int getBarDim() {
6127 			return thumb.width;
6128 		}
6129 	}
6130 
6131 	override void setViewableArea(int a) {
6132 		super.setViewableArea(a);
6133 
6134 		version(win32_widgets) {
6135 			SCROLLINFO info;
6136 			info.cbSize = info.sizeof;
6137 			info.nPage = a + 1;
6138 			info.fMask = SIF_PAGE;
6139 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6140 		} else version(custom_widgets) {
6141 			thumb.positionX = thumbPosition;
6142 			thumb.thumbWidth = thumbSize;
6143 			thumb.redraw();
6144 		} else static assert(0);
6145 
6146 	}
6147 
6148 	override void setMax(int a) {
6149 		super.setMax(a);
6150 		version(win32_widgets) {
6151 			SCROLLINFO info;
6152 			info.cbSize = info.sizeof;
6153 			info.nMin = 0;
6154 			info.nMax = max;
6155 			info.fMask = SIF_RANGE;
6156 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6157 		} else version(custom_widgets) {
6158 			thumb.positionX = thumbPosition;
6159 			thumb.thumbWidth = thumbSize;
6160 			thumb.redraw();
6161 		}
6162 	}
6163 
6164 	override void setPosition(int a) {
6165 		super.setPosition(a);
6166 		version(win32_widgets) {
6167 			SCROLLINFO info;
6168 			info.cbSize = info.sizeof;
6169 			info.fMask = SIF_POS;
6170 			info.nPos = position;
6171 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6172 		} else version(custom_widgets) {
6173 			thumb.positionX = thumbPosition();
6174 			thumb.thumbWidth = thumbSize;
6175 			thumb.redraw();
6176 		} else static assert(0);
6177 	}
6178 
6179 	this(Widget parent) {
6180 		super(parent);
6181 
6182 		version(win32_widgets) {
6183 			createWin32Window(this, "Scrollbar"w, "", 
6184 				0|WS_CHILD|WS_VISIBLE|SBS_HORZ|SBS_BOTTOMALIGN, 0);
6185 		} else version(custom_widgets) {
6186 			auto vl = new HorizontalLayout(this);
6187 			auto leftButton = new ArrowButton(ArrowDirection.left, vl);
6188 			leftButton.setClickRepeat(scrollClickRepeatInterval);
6189 			thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.horizontal, vl);
6190 			auto rightButton = new ArrowButton(ArrowDirection.right, vl);
6191 			rightButton.setClickRepeat(scrollClickRepeatInterval);
6192 
6193 			leftButton.tabStop = false;
6194 			rightButton.tabStop = false;
6195 			thumb.tabStop = false;
6196 
6197 			leftButton.addEventListener(EventType.triggered, () {
6198 				this.emitCommand!"scrolltopreviousline"();
6199 				//informProgramThatUserChangedPosition(position - step());
6200 			});
6201 			rightButton.addEventListener(EventType.triggered, () {
6202 				this.emitCommand!"scrolltonextline"();
6203 				//informProgramThatUserChangedPosition(position + step());
6204 			});
6205 
6206 			thumb.thumbWidth = this.minWidth;
6207 			thumb.thumbHeight = scaleWithDpi(16);
6208 
6209 			thumb.addEventListener(EventType.change, () {
6210 				auto sx = thumb.positionX * max() / thumb.width;
6211 				//informProgramThatUserChangedPosition(sx);
6212 
6213 				auto ev = new ScrollToPositionEvent(this, sx);
6214 				ev.dispatch();
6215 			});
6216 		}
6217 	}
6218 
6219 	override int minHeight() { return scaleWithDpi(16); }
6220 	override int maxHeight() { return scaleWithDpi(16); }
6221 	override int minWidth() { return scaleWithDpi(48); }
6222 }
6223 
6224 class ScrollToPositionEvent : Event {
6225 	enum EventString = "scrolltoposition";
6226 
6227 	this(Widget target, int value) {
6228 		this.value = value;
6229 		super(EventString, target);
6230 	}
6231 
6232 	immutable int value;
6233 
6234 	override @property int intValue() {
6235 		return value;
6236 	}
6237 }
6238 
6239 //version(custom_widgets)
6240 //private
6241 class VerticalScrollbar : ScrollbarBase {
6242 
6243 	version(custom_widgets) {
6244 		override int getBarDim() {
6245 			return thumb.height;
6246 		}
6247 
6248 		private MouseTrackingWidget thumb;
6249 	}
6250 
6251 	override void setViewableArea(int a) {
6252 		super.setViewableArea(a);
6253 
6254 		version(win32_widgets) {
6255 			SCROLLINFO info;
6256 			info.cbSize = info.sizeof;
6257 			info.nPage = a + 1;
6258 			info.fMask = SIF_PAGE;
6259 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6260 		} else version(custom_widgets) {
6261 			thumb.positionY = thumbPosition;
6262 			thumb.thumbHeight = thumbSize;
6263 			thumb.redraw();
6264 		} else static assert(0);
6265 
6266 	}
6267 
6268 	override void setMax(int a) {
6269 		super.setMax(a);
6270 		version(win32_widgets) {
6271 			SCROLLINFO info;
6272 			info.cbSize = info.sizeof;
6273 			info.nMin = 0;
6274 			info.nMax = max;
6275 			info.fMask = SIF_RANGE;
6276 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6277 		} else version(custom_widgets) {
6278 			thumb.positionY = thumbPosition;
6279 			thumb.thumbHeight = thumbSize;
6280 			thumb.redraw();
6281 		}
6282 	}
6283 
6284 	override void setPosition(int a) {
6285 		super.setPosition(a);
6286 		version(win32_widgets) {
6287 			SCROLLINFO info;
6288 			info.cbSize = info.sizeof;
6289 			info.fMask = SIF_POS;
6290 			info.nPos = position;
6291 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6292 		} else version(custom_widgets) {
6293 			thumb.positionY = thumbPosition;
6294 			thumb.thumbHeight = thumbSize;
6295 			thumb.redraw();
6296 		} else static assert(0);
6297 	}
6298 
6299 	this(Widget parent) {
6300 		super(parent);
6301 
6302 		version(win32_widgets) {
6303 			createWin32Window(this, "Scrollbar"w, "", 
6304 				0|WS_CHILD|WS_VISIBLE|SBS_VERT|SBS_RIGHTALIGN, 0);
6305 		} else version(custom_widgets) {
6306 			auto vl = new VerticalLayout(this);
6307 			auto upButton = new ArrowButton(ArrowDirection.up, vl);
6308 			upButton.setClickRepeat(scrollClickRepeatInterval);
6309 			thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.vertical, vl);
6310 			auto downButton = new ArrowButton(ArrowDirection.down, vl);
6311 			downButton.setClickRepeat(scrollClickRepeatInterval);
6312 
6313 			upButton.addEventListener(EventType.triggered, () {
6314 				this.emitCommand!"scrolltopreviousline"();
6315 				//informProgramThatUserChangedPosition(position - step());
6316 			});
6317 			downButton.addEventListener(EventType.triggered, () {
6318 				this.emitCommand!"scrolltonextline"();
6319 				//informProgramThatUserChangedPosition(position + step());
6320 			});
6321 
6322 			thumb.thumbWidth = this.minWidth;
6323 			thumb.thumbHeight = scaleWithDpi(16);
6324 
6325 			thumb.addEventListener(EventType.change, () {
6326 				auto sy = thumb.positionY * max() / thumb.height;
6327 
6328 				auto ev = new ScrollToPositionEvent(this, sy);
6329 				ev.dispatch();
6330 
6331 				//informProgramThatUserChangedPosition(sy);
6332 			});
6333 
6334 			upButton.tabStop = false;
6335 			downButton.tabStop = false;
6336 			thumb.tabStop = false;
6337 		}
6338 	}
6339 
6340 	override int minWidth() { return scaleWithDpi(16); }
6341 	override int maxWidth() { return scaleWithDpi(16); }
6342 	override int minHeight() { return scaleWithDpi(48); }
6343 }
6344 
6345 
6346 /++
6347 	EXPERIMENTAL
6348 
6349 	A widget specialized for being a container for other widgets.
6350 
6351 	History:
6352 		Added May 29, 2021. Not stabilized at this time.
6353 +/
6354 class WidgetContainer : Widget {
6355 	this(Widget parent) {
6356 		tabStop = false;
6357 		super(parent);
6358 	}
6359 
6360 	override int maxHeight() {
6361 		if(this.children.length == 1) {
6362 			return saturatedSum(this.children[0].maxHeight, this.children[0].marginTop, this.children[0].marginBottom);
6363 		} else {
6364 			return int.max;
6365 		}
6366 	}
6367 
6368 	override int maxWidth() {
6369 		if(this.children.length == 1) {
6370 			return saturatedSum(this.children[0].maxWidth, this.children[0].marginLeft, this.children[0].marginRight);
6371 		} else {
6372 			return int.max;
6373 		}
6374 	}
6375 
6376 	/+
6377 
6378 	override int minHeight() {
6379 		int largest = 0;
6380 		int margins = 0;
6381 		int lastMargin = 0;
6382 		foreach(child; children) {
6383 			auto mh = child.minHeight();
6384 			if(mh > largest)
6385 				largest = mh;
6386 			margins += mymax(lastMargin, child.marginTop());
6387 			lastMargin = child.marginBottom();
6388 		}
6389 		return largest + margins;
6390 	}
6391 
6392 	override int maxHeight() {
6393 		int largest = 0;
6394 		int margins = 0;
6395 		int lastMargin = 0;
6396 		foreach(child; children) {
6397 			auto mh = child.maxHeight();
6398 			if(mh == int.max)
6399 				return int.max;
6400 			if(mh > largest)
6401 				largest = mh;
6402 			margins += mymax(lastMargin, child.marginTop());
6403 			lastMargin = child.marginBottom();
6404 		}
6405 		return largest + margins;
6406 	}
6407 
6408 	override int minWidth() {
6409 		int min;
6410 		foreach(child; children) {
6411 			auto cm = child.minWidth;
6412 			if(cm > min)
6413 				min = cm;
6414 		}
6415 		return min + paddingLeft + paddingRight;
6416 	}
6417 
6418 	override int minHeight() {
6419 		int min;
6420 		foreach(child; children) {
6421 			auto cm = child.minHeight;
6422 			if(cm > min)
6423 				min = cm;
6424 		}
6425 		return min + paddingTop + paddingBottom;
6426 	}
6427 
6428 	override int maxHeight() {
6429 		int largest = 0;
6430 		int margins = 0;
6431 		int lastMargin = 0;
6432 		foreach(child; children) {
6433 			auto mh = child.maxHeight();
6434 			if(mh == int.max)
6435 				return int.max;
6436 			if(mh > largest)
6437 				largest = mh;
6438 			margins += mymax(lastMargin, child.marginTop());
6439 			lastMargin = child.marginBottom();
6440 		}
6441 		return largest + margins;
6442 	}
6443 
6444 	override int heightStretchiness() {
6445 		int max;
6446 		foreach(child; children) {
6447 			auto c = child.heightStretchiness;
6448 			if(c > max)
6449 				max = c;
6450 		}
6451 		return max;
6452 	}
6453 
6454 	override int marginTop() {
6455 		if(this.children.length)
6456 			return this.children[0].marginTop;
6457 		return 0;
6458 	}
6459 	+/
6460 }
6461 
6462 ///
6463 abstract class Layout : Widget {
6464 	this(Widget parent) {
6465 		tabStop = false;
6466 		super(parent);
6467 	}
6468 }
6469 
6470 /++
6471 	Makes all children minimum width and height, placing them down
6472 	left to right, top to bottom.
6473 
6474 	Useful if you want to make a list of buttons that automatically
6475 	wrap to a new line when necessary.
6476 +/
6477 class InlineBlockLayout : Layout {
6478 	///
6479 	this(Widget parent) { super(parent); }
6480 
6481 	override void recomputeChildLayout() {
6482 		registerMovement();
6483 
6484 		int x = this.paddingLeft, y = this.paddingTop;
6485 
6486 		int lineHeight;
6487 		int previousMargin = 0;
6488 		int previousMarginBottom = 0;
6489 
6490 		foreach(child; children) {
6491 			if(child.hidden)
6492 				continue;
6493 			if(cast(FixedPosition) child) {
6494 				child.recomputeChildLayout();
6495 				continue;
6496 			}
6497 			child.width = child.flexBasisWidth();
6498 			if(child.width == 0)
6499 				child.width = child.minWidth();
6500 			if(child.width == 0)
6501 				child.width = 32;
6502 
6503 			child.height = child.flexBasisHeight();
6504 			if(child.height == 0)
6505 				child.height = child.minHeight();
6506 			if(child.height == 0)
6507 				child.height = 32;
6508 
6509 			if(x + child.width + paddingRight > this.width) {
6510 				x = this.paddingLeft;
6511 				y += lineHeight;
6512 				lineHeight = 0;
6513 				previousMargin = 0;
6514 				previousMarginBottom = 0;
6515 			}
6516 
6517 			auto margin = child.marginLeft;
6518 			if(previousMargin > margin)
6519 				margin = previousMargin;
6520 
6521 			x += margin;
6522 
6523 			child.x = x;
6524 			child.y = y;
6525 
6526 			int marginTopApplied;
6527 			if(child.marginTop > previousMarginBottom) {
6528 				child.y += child.marginTop;
6529 				marginTopApplied = child.marginTop;
6530 			}
6531 
6532 			x += child.width;
6533 			previousMargin = child.marginRight;
6534 
6535 			if(child.marginBottom > previousMarginBottom)
6536 				previousMarginBottom = child.marginBottom;
6537 
6538 			auto h = child.height + previousMarginBottom + marginTopApplied;
6539 			if(h > lineHeight)
6540 				lineHeight = h;
6541 
6542 			child.recomputeChildLayout();
6543 		}
6544 
6545 	}
6546 
6547 	override int minWidth() {
6548 		int min;
6549 		foreach(child; children) {
6550 			auto cm = child.minWidth;
6551 			if(cm > min)
6552 				min = cm;
6553 		}
6554 		return min + paddingLeft + paddingRight;
6555 	}
6556 
6557 	override int minHeight() {
6558 		int min;
6559 		foreach(child; children) {
6560 			auto cm = child.minHeight;
6561 			if(cm > min)
6562 				min = cm;
6563 		}
6564 		return min + paddingTop + paddingBottom;
6565 	}
6566 }
6567 
6568 /++
6569 	A TabMessageWidget is a clickable row of tabs followed by a content area, very similar
6570 	to the [TabWidget]. The difference is the TabMessageWidget only sends messages, whereas
6571 	the [TabWidget] will automatically change pages of child widgets.
6572 
6573 	This allows you to react to it however you see fit rather than having to
6574 	be tied to just the new sets of child widgets.
6575 
6576 	It sends the message in the form of `this.emitCommand!"changetab"();`.
6577 
6578 	History:
6579 		Added December 24, 2021 (dub v10.5)
6580 +/
6581 class TabMessageWidget : Widget {
6582 
6583 	protected void tabIndexClicked(int item) {
6584 		this.emitCommand!"changetab"();
6585 	}
6586 
6587 	/++
6588 		Adds the a new tab to the control with the given title.
6589 
6590 		Returns:
6591 			The index of the newly added tab. You will need to know
6592 			this index to refer to it later and to know which tab to
6593 			change to when you get a changetab message.
6594 	+/
6595 	int addTab(string title, int pos = int.max) {
6596 		version(win32_widgets) {
6597 			TCITEM item;
6598 			item.mask = TCIF_TEXT;
6599 			WCharzBuffer buf = WCharzBuffer(title);
6600 			item.pszText = buf.ptr;
6601 			return cast(int) SendMessage(hwnd, TCM_INSERTITEM, pos, cast(LPARAM) &item);
6602 		} else version(custom_widgets) {
6603 			if(pos >= tabs.length) {
6604 				tabs ~= title;
6605 				redraw();
6606 				return cast(int) tabs.length - 1;
6607 			} else if(pos <= 0) {
6608 				tabs = title ~ tabs;
6609 				redraw();
6610 				return 0;
6611 			} else {
6612 				tabs = tabs[0 .. pos] ~ title ~ title[pos .. $];
6613 				redraw();
6614 				return pos;
6615 			}
6616 		}
6617 	}
6618 
6619 	override void addChild(Widget child, int pos = int.max) {
6620 		if(container)
6621 			container.addChild(child, pos);
6622 		else
6623 			super.addChild(child, pos);
6624 	}
6625 
6626 	protected Widget makeContainer() {
6627 		return new Widget(this);
6628 	}
6629 
6630 	private Widget container;
6631 
6632 	override void recomputeChildLayout() {
6633 		version(win32_widgets) {
6634 			this.registerMovement();
6635 
6636 			RECT rect;
6637 			GetWindowRect(hwnd, &rect);
6638 
6639 			auto left = rect.left;
6640 			auto top = rect.top;
6641 
6642 			TabCtrl_AdjustRect(hwnd, false, &rect);
6643 			foreach(child; children) {
6644 				if(!child.showing) continue;
6645 				child.x = rect.left - left;
6646 				child.y = rect.top - top;
6647 				child.width = rect.right - rect.left;
6648 				child.height = rect.bottom - rect.top;
6649 				child.recomputeChildLayout();
6650 			}
6651 		} else version(custom_widgets) {
6652 			this.registerMovement();
6653 			foreach(child; children) {
6654 				if(!child.showing) continue;
6655 				child.x = 2;
6656 				child.y = tabBarHeight + 2; // for the border
6657 				child.width = width - 4; // for the border
6658 				child.height = height - tabBarHeight - 2 - 2; // for the border
6659 				child.recomputeChildLayout();
6660 			}
6661 		} else static assert(0);
6662 	}
6663 
6664 	version(custom_widgets)
6665 		string[] tabs;
6666 
6667 	this(Widget parent) {
6668 		super(parent);
6669 
6670 		tabStop = false;
6671 
6672 		version(win32_widgets) {
6673 			createWin32Window(this, WC_TABCONTROL, "", 0);
6674 		} else version(custom_widgets) {
6675 			addEventListener((ClickEvent event) {
6676 				if(event.target !is this && this.container !is null && event.target !is this.container) return;
6677 				if(event.clientY < tabBarHeight) {
6678 					auto t = (event.clientX / tabWidth);
6679 					if(t >= 0 && t < tabs.length) {
6680 						currentTab_ = t;
6681 						tabIndexClicked(t);
6682 						redraw();
6683 					}
6684 				}
6685 			});
6686 		} else static assert(0);
6687 
6688 		this.container = makeContainer();
6689 	}
6690 
6691 	override int marginTop() { return 4; }
6692 	override int paddingBottom() { return 4; }
6693 
6694 	override int minHeight() {
6695 		int max = 0;
6696 		foreach(child; children)
6697 			max = mymax(child.minHeight, max);
6698 
6699 
6700 		version(win32_widgets) {
6701 			RECT rect;
6702 			rect.right = this.width;
6703 			rect.bottom = max;
6704 			TabCtrl_AdjustRect(hwnd, true, &rect);
6705 
6706 			max = rect.bottom;
6707 		} else {
6708 			max += defaultLineHeight + 4;
6709 		}
6710 
6711 
6712 		return max;
6713 	}
6714 
6715 	version(win32_widgets)
6716 	override int handleWmNotify(NMHDR* hdr, int code, out int mustReturn) {
6717 		switch(code) {
6718 			case TCN_SELCHANGE:
6719 				auto sel = TabCtrl_GetCurSel(hwnd);
6720 				tabIndexClicked(sel);
6721 			break;
6722 			default:
6723 		}
6724 		return 0;
6725 	}
6726 
6727 	version(custom_widgets) {
6728 		private int currentTab_;
6729 		private int tabBarHeight() { return defaultLineHeight; }
6730 		int tabWidth = 80;
6731 	}
6732 
6733 	version(win32_widgets)
6734 	override void paint(WidgetPainter painter) {}
6735 
6736 	version(custom_widgets)
6737 	override void paint(WidgetPainter painter) {
6738 		auto cs = getComputedStyle();
6739 
6740 		draw3dFrame(0, tabBarHeight - 2, width, height - tabBarHeight + 2, painter, FrameStyle.risen, cs.background.color);
6741 
6742 		int posX = 0;
6743 		foreach(idx, title; tabs) {
6744 			auto isCurrent = idx == getCurrentTab();
6745 
6746 			painter.setClipRectangle(Point(posX, 0), tabWidth, tabBarHeight);
6747 
6748 			draw3dFrame(posX, 0, tabWidth, tabBarHeight, painter, isCurrent ? FrameStyle.risen : FrameStyle.sunk, isCurrent ? cs.windowBackgroundColor : darken(cs.windowBackgroundColor, 0.1));
6749 			painter.outlineColor = cs.foregroundColor;
6750 			painter.drawText(Point(posX + 4, 2), title, Point(posX + tabWidth, tabBarHeight - 2), TextAlignment.VerticalCenter);
6751 
6752 			if(isCurrent) {
6753 				painter.outlineColor = cs.windowBackgroundColor;
6754 				painter.fillColor = Color.transparent;
6755 				painter.drawLine(Point(posX + 2, tabBarHeight - 1), Point(posX + tabWidth, tabBarHeight - 1));
6756 				painter.drawLine(Point(posX + 2, tabBarHeight - 2), Point(posX + tabWidth, tabBarHeight - 2));
6757 
6758 				painter.outlineColor = Color.white;
6759 				painter.drawPixel(Point(posX + 1, tabBarHeight - 1));
6760 				painter.drawPixel(Point(posX + 1, tabBarHeight - 2));
6761 				painter.outlineColor = cs.activeTabColor;
6762 				painter.drawPixel(Point(posX, tabBarHeight - 1));
6763 			}
6764 
6765 			posX += tabWidth - 2;
6766 		}
6767 	}
6768 
6769 	///
6770 	@scriptable
6771 	void setCurrentTab(int item) {
6772 		version(win32_widgets)
6773 			TabCtrl_SetCurSel(hwnd, item);
6774 		else version(custom_widgets)
6775 			currentTab_ = item;
6776 		else static assert(0);
6777 
6778 		tabIndexClicked(item);
6779 	}
6780 
6781 	///
6782 	@scriptable
6783 	int getCurrentTab() {
6784 		version(win32_widgets)
6785 			return TabCtrl_GetCurSel(hwnd);
6786 		else version(custom_widgets)
6787 			return currentTab_; // FIXME
6788 		else static assert(0);
6789 	}
6790 
6791 	///
6792 	@scriptable
6793 	void removeTab(int item) {
6794 		if(item && item == getCurrentTab())
6795 			setCurrentTab(item - 1);
6796 
6797 		version(win32_widgets) {
6798 			TabCtrl_DeleteItem(hwnd, item);
6799 		}
6800 
6801 		for(int a = item; a < children.length - 1; a++)
6802 			this._children[a] = this._children[a + 1];
6803 		this._children = this._children[0 .. $-1];
6804 	}
6805 
6806 }
6807 
6808 
6809 /++
6810 	A tab widget is a set of clickable tab buttons followed by a content area.
6811 
6812 
6813 	Tabs can change existing content or can be new pages.
6814 
6815 	When the user picks a different tab, a `change` message is generated.
6816 +/
6817 class TabWidget : TabMessageWidget {
6818 	this(Widget parent) {
6819 		super(parent);
6820 	}
6821 
6822 	override protected Widget makeContainer() {
6823 		return null;
6824 	}
6825 
6826 	override void addChild(Widget child, int pos = int.max) {
6827 		if(auto twp = cast(TabWidgetPage) child) {
6828 			Widget.addChild(child, pos);
6829 			if(pos == int.max)
6830 				pos = cast(int) this.children.length - 1;
6831 
6832 			super.addTab(twp.title, pos); // need to bypass the override here which would get into a loop...
6833 
6834 			if(pos != getCurrentTab) {
6835 				child.showing = false;
6836 			}
6837 		} else {
6838 			assert(0, "Don't add children directly to a tab widget, instead add them to a page (see addPage)");
6839 		}
6840 	}
6841 
6842 	// FIXME: add tab icons at some point, Windows supports them
6843 	/++
6844 		Adds a page and its associated tab with the given label to the widget.
6845 
6846 		Returns:
6847 			The added page object, to which you can add other widgets.
6848 	+/
6849 	@scriptable
6850 	TabWidgetPage addPage(string title) {
6851 		return new TabWidgetPage(title, this);
6852 	}
6853 
6854 	/++
6855 		Gets the page at the given tab index, or `null` if the index is bad.
6856 
6857 		History:
6858 			Added December 24, 2021.
6859 	+/
6860 	TabWidgetPage getPage(int index) {
6861 		if(index < this.children.length)
6862 			return null;
6863 		return cast(TabWidgetPage) this.children[index];
6864 	}
6865 
6866 	/++
6867 		While you can still use the addTab from the parent class,
6868 		*strongly* recommend you use [addPage] insteaad.
6869 
6870 		History:
6871 			Added December 24, 2021 to fulful the interface
6872 			requirement that came from adding [TabMessageWidget].
6873 
6874 			You should not use it though since the [addPage] function
6875 			is much easier to use here.
6876 	+/
6877 	override int addTab(string title, int pos = int.max) {
6878 		auto p = addPage(title);
6879 		foreach(idx, child; this.children)
6880 			if(child is p)
6881 				return cast(int) idx;
6882 		return -1;
6883 	}
6884 
6885 	protected override void tabIndexClicked(int item) {
6886 		foreach(idx, child; children) {
6887 			child.showing(false, false); // batch the recalculates for the end
6888 		}
6889 
6890 		foreach(idx, child; children) {
6891 			if(idx == item) {
6892 				child.showing(true, false);
6893 				if(parentWindow) {
6894 					auto f = parentWindow.getFirstFocusable(child);
6895 					if(f)
6896 						f.focus();
6897 				}
6898 				recomputeChildLayout();
6899 			}
6900 		}
6901 
6902 		version(win32_widgets) {
6903 			InvalidateRect(hwnd, null, true);
6904 		} else version(custom_widgets) {
6905 			this.redraw();
6906 		}
6907 	}
6908 
6909 }
6910 
6911 /++
6912 	A page widget is basically a tab widget with hidden tabs. It is also sometimes called a "StackWidget".
6913 
6914 	You add [TabWidgetPage]s to it.
6915 +/
6916 class PageWidget : Widget {
6917 	this(Widget parent) {
6918 		super(parent);
6919 	}
6920 
6921 	override int minHeight() {
6922 		int max = 0;
6923 		foreach(child; children)
6924 			max = mymax(child.minHeight, max);
6925 
6926 		return max;
6927 	}
6928 
6929 
6930 	override void addChild(Widget child, int pos = int.max) {
6931 		if(auto twp = cast(TabWidgetPage) child) {
6932 			super.addChild(child, pos);
6933 			if(pos == int.max)
6934 				pos = cast(int) this.children.length - 1;
6935 
6936 			if(pos != getCurrentTab) {
6937 				child.showing = false;
6938 			}
6939 		} else {
6940 			assert(0, "Don't add children directly to a page widget, instead add them to a page (see addPage)");
6941 		}
6942 	}
6943 
6944 	override void recomputeChildLayout() {
6945 		this.registerMovement();
6946 		foreach(child; children) {
6947 			child.x = 0;
6948 			child.y = 0;
6949 			child.width = width;
6950 			child.height = height;
6951 			child.recomputeChildLayout();
6952 		}
6953 	}
6954 
6955 	private int currentTab_;
6956 
6957 	///
6958 	@scriptable
6959 	void setCurrentTab(int item) {
6960 		currentTab_ = item;
6961 
6962 		showOnly(item);
6963 	}
6964 
6965 	///
6966 	@scriptable
6967 	int getCurrentTab() {
6968 		return currentTab_;
6969 	}
6970 
6971 	///
6972 	@scriptable
6973 	void removeTab(int item) {
6974 		if(item && item == getCurrentTab())
6975 			setCurrentTab(item - 1);
6976 
6977 		for(int a = item; a < children.length - 1; a++)
6978 			this._children[a] = this._children[a + 1];
6979 		this._children = this._children[0 .. $-1];
6980 	}
6981 
6982 	///
6983 	@scriptable
6984 	TabWidgetPage addPage(string title) {
6985 		return new TabWidgetPage(title, this);
6986 	}
6987 
6988 	private void showOnly(int item) {
6989 		foreach(idx, child; children)
6990 			if(idx == item) {
6991 				child.show();
6992 				child.recomputeChildLayout();
6993 			} else {
6994 				child.hide();
6995 			}
6996 	}
6997 
6998 }
6999 
7000 /++
7001 
7002 +/
7003 class TabWidgetPage : Widget {
7004 	string title;
7005 	this(string title, Widget parent) {
7006 		this.title = title;
7007 		this.tabStop = false;
7008 		super(parent);
7009 
7010 		///*
7011 		version(win32_widgets) {
7012 			createWin32Window(this, Win32Class!"arsd_minigui_TabWidgetPage"w, "", 0);
7013 		}
7014 		//*/
7015 	}
7016 
7017 	override int minHeight() {
7018 		int sum = 0;
7019 		foreach(child; children)
7020 			sum += child.minHeight();
7021 		return sum;
7022 	}
7023 }
7024 
7025 version(none)
7026 /++
7027 	A collapsable sidebar is a container that shows if its assigned width is greater than its minimum and otherwise shows as a button.
7028 
7029 	I think I need to modify the layout algorithms to support this.
7030 +/
7031 class CollapsableSidebar : Widget {
7032 
7033 }
7034 
7035 /// Stacks the widgets vertically, taking all the available width for each child.
7036 class VerticalLayout : Layout {
7037 	// most of this is intentionally blank - widget's default is vertical layout right now
7038 	///
7039 	this(Widget parent) { super(parent); }
7040 
7041 	/++
7042 		Sets a max width for the layout so you don't have to subclass. The max width
7043 		is in device-independent pixels, meaning pixels at 96 dpi that are auto-scaled.
7044 
7045 		History:
7046 			Added November 29, 2021 (dub v10.5)
7047 	+/
7048 	this(int maxWidth, Widget parent) {
7049 		this.mw = maxWidth;
7050 		super(parent);
7051 	}
7052 
7053 	private int mw = int.max;
7054 
7055 	override int maxWidth() { return scaleWithDpi(mw); }
7056 }
7057 
7058 /// Stacks the widgets horizontally, taking all the available height for each child.
7059 class HorizontalLayout : Layout {
7060 	///
7061 	this(Widget parent) { super(parent); }
7062 
7063 	/++
7064 		Sets a max height for the layout so you don't have to subclass. The max height
7065 		is in device-independent pixels, meaning pixels at 96 dpi that are auto-scaled.
7066 
7067 		History:
7068 			Added November 29, 2021 (dub v10.5)
7069 	+/
7070 	this(int maxHeight, Widget parent) {
7071 		this.mh = maxHeight;
7072 		super(parent);
7073 	}
7074 
7075 	private int mh = 0;
7076 
7077 
7078 
7079 	override void recomputeChildLayout() {
7080 		.recomputeChildLayout!"width"(this);
7081 	}
7082 
7083 	override int minHeight() {
7084 		int largest = 0;
7085 		int margins = 0;
7086 		int lastMargin = 0;
7087 		foreach(child; children) {
7088 			auto mh = child.minHeight();
7089 			if(mh > largest)
7090 				largest = mh;
7091 			margins += mymax(lastMargin, child.marginTop());
7092 			lastMargin = child.marginBottom();
7093 		}
7094 		return largest + margins;
7095 	}
7096 
7097 	override int maxHeight() {
7098 		if(mh != 0)
7099 			return mymax(minHeight, scaleWithDpi(mh));
7100 
7101 		int largest = 0;
7102 		int margins = 0;
7103 		int lastMargin = 0;
7104 		foreach(child; children) {
7105 			auto mh = child.maxHeight();
7106 			if(mh == int.max)
7107 				return int.max;
7108 			if(mh > largest)
7109 				largest = mh;
7110 			margins += mymax(lastMargin, child.marginTop());
7111 			lastMargin = child.marginBottom();
7112 		}
7113 		return largest + margins;
7114 	}
7115 
7116 	override int heightStretchiness() {
7117 		int max;
7118 		foreach(child; children) {
7119 			auto c = child.heightStretchiness;
7120 			if(c > max)
7121 				max = c;
7122 		}
7123 		return max;
7124 	}
7125 
7126 }
7127 
7128 version(win32_widgets)
7129 private
7130 extern(Windows)
7131 LRESULT DoubleBufferWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) nothrow {
7132 	Widget* pwin = hwnd in Widget.nativeMapping;
7133 	if(pwin is null)
7134 		return DefWindowProc(hwnd, message, wparam, lparam);
7135 	SimpleWindow win = pwin.simpleWindowWrappingHwnd;
7136 	if(win is null)
7137 		return DefWindowProc(hwnd, message, wparam, lparam);
7138 
7139 	switch(message) {
7140 		case WM_SIZE:
7141 			auto width = LOWORD(lparam);
7142 			auto height = HIWORD(lparam);
7143 
7144 			// FIXME: could this be more efficient? it never relinquishes a large bitmap
7145 			if(width > win.bmpWidth || height > win.bmpHeight) {
7146 				auto hdc = GetDC(hwnd);
7147 				auto oldBuffer = win.buffer;
7148 				win.buffer = CreateCompatibleBitmap(hdc, width, height);
7149 
7150 				auto hdcBmp = CreateCompatibleDC(hdc);
7151 
7152 				auto oldBmp = SelectObject(hdcBmp, win.buffer);
7153 
7154 				if(oldBuffer) {
7155 					auto hdcOldBmp = CreateCompatibleDC(hdc);
7156 					auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer);
7157 
7158 					BitBlt(hdcBmp, 0, 0, win.bmpWidth, win.bmpHeight, hdcOldBmp, 0, 0, SRCCOPY);
7159 
7160 					SelectObject(hdcOldBmp, oldOldBmp);
7161 					DeleteDC(hdcOldBmp);
7162 				}
7163 
7164 				auto brush = GetSysColorBrush(COLOR_3DFACE);
7165 				RECT r;
7166 				r.left = win.bmpWidth;
7167 				r.top = 0;
7168 				r.right = width;
7169 				r.bottom = height;
7170 				FillRect(hdcBmp, &r, brush);
7171 
7172 				r.left = 0;
7173 				r.top = win.bmpHeight;
7174 				r.right = width;
7175 				r.bottom = height;
7176 				FillRect(hdcBmp, &r, brush);
7177 
7178 				SelectObject(hdcBmp, oldBmp);
7179 				DeleteDC(hdcBmp);
7180 				ReleaseDC(hwnd, hdc);
7181 
7182 				if(oldBuffer)
7183 					DeleteObject(oldBuffer);
7184 
7185 				win.bmpWidth = width;
7186 				win.bmpHeight = height;
7187 			}
7188 		break;
7189 		case WM_PAINT:
7190 			if(win.buffer is null)
7191 				goto default;
7192 
7193 			BITMAP bm;
7194 			PAINTSTRUCT ps;
7195 
7196 			HDC hdc = BeginPaint(hwnd, &ps);
7197 
7198 			HDC hdcMem = CreateCompatibleDC(hdc);
7199 			HBITMAP hbmOld = SelectObject(hdcMem, win.buffer);
7200 
7201 			GetObject(win.buffer, bm.sizeof, &bm);
7202 
7203 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
7204 
7205 			SelectObject(hdcMem, hbmOld);
7206 			DeleteDC(hdcMem);
7207 			EndPaint(hwnd, &ps);
7208 		break;
7209 		default:
7210 			return DefWindowProc(hwnd, message, wparam, lparam);
7211 	}
7212 
7213 	return 0;
7214 }
7215 
7216 private wstring Win32Class(wstring name)() {
7217 	static bool classRegistered;
7218 	if(!classRegistered) {
7219 		HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
7220 		WNDCLASSEX wc;
7221 		wc.cbSize = wc.sizeof;
7222 		wc.hInstance = hInstance;
7223 		wc.hbrBackground = cast(HBRUSH) (COLOR_3DFACE+1); // GetStockObject(WHITE_BRUSH);
7224 		wc.lpfnWndProc = &DoubleBufferWndProc;
7225 		wc.lpszClassName = name.ptr;
7226 		if(!RegisterClassExW(&wc))
7227 			throw new Exception("RegisterClass ");// ~ to!string(GetLastError()));
7228 		classRegistered = true;
7229 	}
7230 
7231 		return name;
7232 }
7233 
7234 /+
7235 version(win32_widgets)
7236 extern(Windows)
7237 private
7238 LRESULT CustomDrawWindowProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
7239 	switch(iMessage) {
7240 		case WM_PAINT:
7241 			if(auto te = hWnd in Widget.nativeMapping) {
7242 				try {
7243 					//te.redraw();
7244 					import std.stdio; writeln(te, " drawing");
7245 				} catch(Exception) {}
7246 			}
7247 			return DefWindowProc(hWnd, iMessage, wParam, lParam);
7248 		default:
7249 			return DefWindowProc(hWnd, iMessage, wParam, lParam);
7250 	}
7251 }
7252 +/
7253 
7254 
7255 /++
7256 	A widget specifically designed to hold other widgets.
7257 
7258 	History:
7259 		Added July 1, 2021
7260 +/
7261 class ContainerWidget : Widget {
7262 	this(Widget parent) {
7263 		super(parent);
7264 		this.tabStop = false;
7265 
7266 		version(win32_widgets) {
7267 			createWin32Window(this, Win32Class!"arsd_minigui_ContainerWidget"w, "", 0);
7268 		}
7269 	}
7270 }
7271 
7272 /++
7273 	A widget that takes your widget, puts scroll bars around it, and sends
7274 	messages to it when the user scrolls. Unlike [ScrollableWidget], it makes
7275 	no effort to automatically scroll or clip its child widgets - it just sends
7276 	the messages.
7277 
7278 
7279 	A ScrollMessageWidget notifies you with a [ScrollEvent] that it has changed.
7280 	The scroll coordinates are all given in a unit you interpret as you wish. One
7281 	of these units is moved on each press of the arrow buttons and represents the
7282 	smallest amount the user can scroll. The intention is for this to be one line,
7283 	one item in a list, one row in a table, etc. Whatever makes sense for your widget
7284 	in each direction that the user might be interested in.
7285 
7286 	You can set a "page size" with the [step] property. (Yes, I regret the name...)
7287 	This is the amount it jumps when the user pressed page up and page down, or clicks
7288 	in the exposed part of the scroll bar.
7289 
7290 	You should add child content to the ScrollMessageWidget. However, it is important to
7291 	note that the coordinates are always independent of the scroll position! It is YOUR
7292 	responsibility to do any necessary transforms, clipping, etc., while drawing the
7293 	content and interpreting mouse events if they are supposed to change with the scroll.
7294 	This is in contrast to the (likely to be deprecated) [ScrollableWidget], which tries
7295 	to maintain the illusion that there's an infinite space. The [ScrollMessageWidget] gives
7296 	you more control (which can be considerably more efficient and adapted to your actual data)
7297 	at the expense of you also needing to be aware of its reality.
7298 
7299 	Please note that it does NOT react to mouse wheel events or various keyboard events as of
7300 	version 10.3. Maybe this will change in the future....
7301 +/
7302 class ScrollMessageWidget : Widget {
7303 	this(Widget parent) {
7304 		super(parent);
7305 
7306 		container = new Widget(this);
7307 		hsb = new HorizontalScrollbar(this);
7308 		vsb = new VerticalScrollbar(this);
7309 
7310 		hsb.addEventListener("scrolltonextline", {
7311 			hsb.setPosition(hsb.position + 1);
7312 			notify();
7313 		});
7314 		hsb.addEventListener("scrolltopreviousline", {
7315 			hsb.setPosition(hsb.position - 1);
7316 			notify();
7317 		});
7318 		vsb.addEventListener("scrolltonextline", {
7319 			vsb.setPosition(vsb.position + 1);
7320 			notify();
7321 		});
7322 		vsb.addEventListener("scrolltopreviousline", {
7323 			vsb.setPosition(vsb.position - 1);
7324 			notify();
7325 		});
7326 		hsb.addEventListener("scrolltonextpage", {
7327 			hsb.setPosition(hsb.position + hsb.step_);
7328 			notify();
7329 		});
7330 		hsb.addEventListener("scrolltopreviouspage", {
7331 			hsb.setPosition(hsb.position - hsb.step_);
7332 			notify();
7333 		});
7334 		vsb.addEventListener("scrolltonextpage", {
7335 			vsb.setPosition(vsb.position + vsb.step_);
7336 			notify();
7337 		});
7338 		vsb.addEventListener("scrolltopreviouspage", {
7339 			vsb.setPosition(vsb.position - vsb.step_);
7340 			notify();
7341 		});
7342 		hsb.addEventListener("scrolltoposition", (Event event) {
7343 			hsb.setPosition(event.intValue);
7344 			notify();
7345 		});
7346 		vsb.addEventListener("scrolltoposition", (Event event) {
7347 			vsb.setPosition(event.intValue);
7348 			notify();
7349 		});
7350 
7351 
7352 		tabStop = false;
7353 		container.tabStop = false;
7354 		magic = true;
7355 	}
7356 
7357 	/++
7358 		Add default event listeners for keyboard and mouse wheel scrolling shortcuts.
7359 
7360 
7361 		The defaults for [addDefaultWheelListeners] are:
7362 
7363 			$(LIST
7364 				* Mouse wheel scrolls vertically
7365 				* Alt key + mouse wheel scrolls horiontally
7366 				* Shift + mouse wheel scrolls faster.
7367 				* Any mouse click or wheel event will focus the inner widget if it has `tabStop = true`
7368 			)
7369 
7370 		The defaults for [addDefaultKeyboardListeners] are:
7371 
7372 			$(LIST
7373 				* Arrow keys scroll by the given amounts
7374 				* Shift+arrow keys scroll by the given amounts times the given shiftMultiplier
7375 				* Page up and down scroll by the vertical viewable area
7376 				* Home and end scroll to the start and end of the verticle viewable area.
7377 				* Alt + page up / page down / home / end will horizonally scroll instead of vertical.
7378 			)
7379 
7380 		My recommendation is to change the scroll amounts if you are scrolling by pixels, but otherwise keep them at one line.
7381 
7382 		Params:
7383 			horizontalArrowScrollAmount =
7384 			verticalArrowScrollAmount =
7385 			verticalWheelScrollAmount = how much should be scrolled vertically on each tick of the mouse wheel
7386 			horizontalWheelScrollAmount = how much should be scrolled horizontally when alt is held on each tick of the mouse wheel
7387 			shiftMultiplier = multiplies the scroll amount by this when shift is held
7388 	+/
7389 	void addDefaultKeyboardListeners(int verticalArrowScrollAmount = 1, int horizontalArrowScrollAmount = 1, int shiftMultiplier = 3) {
7390 		auto _this = this;
7391 
7392 		container.addEventListener((scope KeyDownEvent ke) {
7393 			switch(ke.key) {
7394 				case Key.Left:
7395 					_this.scrollLeft(horizontalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7396 				break;
7397 				case Key.Right:
7398 					_this.scrollRight(horizontalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7399 				break;
7400 				case Key.Up:
7401 					_this.scrollUp(verticalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7402 				break;
7403 				case Key.Down:
7404 					_this.scrollDown(verticalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7405 				break;
7406 				case Key.PageUp:
7407 					if(ke.altKey)
7408 						_this.scrollLeft(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7409 					else
7410 						_this.scrollUp(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7411 				break;
7412 				case Key.PageDown:
7413 					if(ke.altKey)
7414 						_this.scrollRight(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7415 					else
7416 						_this.scrollDown(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7417 				break;
7418 				case Key.Home:
7419 					if(ke.altKey)
7420 						_this.scrollLeft(short.max * 16);
7421 					else
7422 						_this.scrollUp(short.max * 16);
7423 				break;
7424 				case Key.End:
7425 					if(ke.altKey)
7426 						_this.scrollRight(short.max * 16);
7427 					else
7428 						_this.scrollDown(short.max * 16);
7429 				break;
7430 
7431 				default:
7432 					// ignore, not for us.
7433 			}
7434 
7435 		});
7436 	}
7437 
7438 	/// ditto
7439 	void addDefaultWheelListeners(int verticalWheelScrollAmount = 1, int horizontalWheelScrollAmount = 1, int shiftMultiplier = 3) {
7440 		auto _this = this;
7441 		container.addEventListener((scope ClickEvent ce) {
7442 
7443 			if(ce.target && ce.target.tabStop)
7444 				ce.target.focus();
7445 
7446 			// ctrl is reserved for the application
7447 			if(ce.ctrlKey)
7448 				return;
7449 
7450 			if(horizontalWheelScrollAmount == 0 && ce.altKey)
7451 				return;
7452 
7453 			if(shiftMultiplier == 0 && ce.shiftKey)
7454 				return;
7455 
7456 			if(ce.button == MouseButton.wheelDown) {
7457 				if(ce.altKey)
7458 					_this.scrollRight(horizontalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7459 				else
7460 					_this.scrollDown(verticalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7461 			} else if(ce.button == MouseButton.wheelUp) {
7462 				if(ce.altKey)
7463 					_this.scrollLeft(horizontalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7464 				else
7465 					_this.scrollUp(verticalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7466 			}
7467 		});
7468 	}
7469 
7470 	/++
7471 		Scrolls the given amount.
7472 
7473 		History:
7474 			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.
7475 	+/
7476 	void scrollUp(int amount = 1) {
7477 		vsb.setPosition(vsb.position - amount);
7478 		notify();
7479 	}
7480 	/// ditto
7481 	void scrollDown(int amount = 1) {
7482 		vsb.setPosition(vsb.position + amount);
7483 		notify();
7484 	}
7485 	/// ditto
7486 	void scrollLeft(int amount = 1) {
7487 		hsb.setPosition(hsb.position - amount);
7488 		notify();
7489 	}
7490 	/// ditto
7491 	void scrollRight(int amount = 1) {
7492 		hsb.setPosition(hsb.position + amount);
7493 		notify();
7494 	}
7495 
7496 	///
7497 	VerticalScrollbar verticalScrollBar() { return vsb; }
7498 	///
7499 	HorizontalScrollbar horizontalScrollBar() { return hsb; }
7500 
7501 	void notify() {
7502 		static bool insideNotify;
7503 
7504 		if(insideNotify)
7505 			return; // avoid the recursive call, even if it isn't strictly correct
7506 
7507 		insideNotify = true;
7508 		scope(exit) insideNotify = false;
7509 
7510 		this.emit!ScrollEvent();
7511 	}
7512 
7513 	mixin Emits!ScrollEvent;
7514 
7515 	///
7516 	Point position() {
7517 		return Point(hsb.position, vsb.position);
7518 	}
7519 
7520 	///
7521 	void setPosition(int x, int y) {
7522 		hsb.setPosition(x);
7523 		vsb.setPosition(y);
7524 	}
7525 
7526 	///
7527 	void setPageSize(int unitsX, int unitsY) {
7528 		hsb.setStep(unitsX);
7529 		vsb.setStep(unitsY);
7530 	}
7531 
7532 	///
7533 	void setTotalArea(int width, int height) {
7534 		hsb.setMax(width);
7535 		vsb.setMax(height);
7536 	}
7537 
7538 	/// Always set the viewable area AFTER setitng the total area if you are going to change both.
7539 	/// NEVER call this from inside a scroll event. This includes through recomputeChildLayout.
7540 	/// If you need to do that, use [queueRecomputeChildLayout].
7541 	void setViewableArea(int width, int height) {
7542 
7543 		// actually there IS A need to dothis cuz the max might have changed since then
7544 		//if(width == hsb.viewableArea_ && height == vsb.viewableArea_)
7545 			//return; // no need to do what is already done
7546 		hsb.setViewableArea(width);
7547 		vsb.setViewableArea(height);
7548 
7549 		bool needsNotify = false;
7550 
7551 		// FIXME: if at any point the rhs is outside the scrollbar, we need
7552 		// to reset to 0. but it should remember the old position in case the
7553 		// window resizes again, so it can kinda return ot where it was.
7554 		//
7555 		// so there's an inner position and a exposed position. the exposed one is always in bounds and thus may be (0,0)
7556 		if(width >= hsb.max) {
7557 			// there's plenty of room to display it all so we need to reset to zero
7558 			// FIXME: adjust so it matches the note above
7559 			hsb.setPosition(0);
7560 			needsNotify = true;
7561 		}
7562 		if(height >= vsb.max) {
7563 			// there's plenty of room to display it all so we need to reset to zero
7564 			// FIXME: adjust so it matches the note above
7565 			vsb.setPosition(0);
7566 			needsNotify = true;
7567 		}
7568 		if(needsNotify)
7569 			notify();
7570 	}
7571 
7572 	private bool magic;
7573 	override void addChild(Widget w, int position = int.max) {
7574 		if(magic)
7575 			container.addChild(w, position);
7576 		else
7577 			super.addChild(w, position);
7578 	}
7579 
7580 	override void recomputeChildLayout() {
7581 		if(hsb is null || vsb is null || container is null) return;
7582 
7583 		registerMovement();
7584 
7585 		hsb.height = scaleWithDpi(16); // FIXME? are tese 16s sane?
7586 		hsb.x = 0;
7587 		hsb.y = this.height - hsb.height;
7588 		hsb.width = this.width - scaleWithDpi(16);
7589 		hsb.recomputeChildLayout();
7590 
7591 		vsb.width = scaleWithDpi(16); // FIXME?
7592 		vsb.x = this.width - vsb.width;
7593 		vsb.y = 0;
7594 		vsb.height = this.height - scaleWithDpi(16);
7595 		vsb.recomputeChildLayout();
7596 
7597 		if(this.header is null) {
7598 			container.x = 0;
7599 			container.y = 0;
7600 			container.width = this.width - vsb.width;
7601 			container.height = this.height - hsb.height;
7602 			container.recomputeChildLayout();
7603 		} else {
7604 			header.x = 0;
7605 			header.y = 0;
7606 			header.width = this.width - vsb.width;
7607 			header.height = scaleWithDpi(16); // size of the button
7608 			header.recomputeChildLayout();
7609 
7610 			container.x = 0;
7611 			container.y = scaleWithDpi(16);
7612 			container.width = this.width - vsb.width;
7613 			container.height = this.height - hsb.height - scaleWithDpi(16);
7614 			container.recomputeChildLayout();
7615 		}
7616 	}
7617 
7618 	HorizontalScrollbar hsb;
7619 	VerticalScrollbar vsb;
7620 	Widget container;
7621 	private Widget header;
7622 
7623 	/++
7624 		Adds a fixed-size "header" widget. This will be positioned to align with the scroll up button.
7625 
7626 		History:
7627 			Added September 27, 2021 (dub v10.3)
7628 	+/
7629 	Widget getHeader() {
7630 		if(this.header is null) {
7631 			magic = false;
7632 			scope(exit) magic = true;
7633 			this.header = new Widget(this);
7634 			recomputeChildLayout();
7635 		}
7636 		return this.header;
7637 	}
7638 }
7639 
7640 /++
7641 	$(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")
7642 	$(IMG //arsdnet.net/minigui-screenshots/linux/ScrollMessageWidget.png, Same thing, but in the default Linux theme.)
7643 +/
7644 version(minigui_screenshots)
7645 @Screenshot("ScrollMessageWidget")
7646 unittest {
7647 	auto window = new Window("ScrollMessageWidget");
7648 
7649 	auto smw = new ScrollMessageWidget(window);
7650 	smw.addDefaultKeyboardListeners();
7651 	smw.addDefaultWheelListeners();
7652 
7653 	window.loop();
7654 }
7655 
7656 /++
7657 	Bypasses automatic layout for its children, using manual positioning and sizing only.
7658 	While you need to manually position them, you must ensure they are inside the StaticLayout's
7659 	bounding box to avoid undefined behavior.
7660 
7661 	You should almost never use this.
7662 +/
7663 class StaticLayout : Layout {
7664 	///
7665 	this(Widget parent) { super(parent); }
7666 	override void recomputeChildLayout() {
7667 		registerMovement();
7668 		foreach(child; children)
7669 			child.recomputeChildLayout();
7670 	}
7671 }
7672 
7673 /++
7674 	Bypasses automatic positioning when being laid out. It is your responsibility to make
7675 	room for this widget in the parent layout.
7676 
7677 	Its children are laid out normally, unless there is exactly one, in which case it takes
7678 	on the full size of the `StaticPosition` object (if you plan to put stuff on the edge, you
7679 	can do that with `padding`).
7680 +/
7681 class StaticPosition : Layout {
7682 	///
7683 	this(Widget parent) { super(parent); }
7684 
7685 	override void recomputeChildLayout() {
7686 		registerMovement();
7687 		if(this.children.length == 1) {
7688 			auto child = children[0];
7689 			child.x = 0;
7690 			child.y = 0;
7691 			child.width = this.width;
7692 			child.height = this.height;
7693 			child.recomputeChildLayout();
7694 		} else
7695 		foreach(child; children)
7696 			child.recomputeChildLayout();
7697 	}
7698 
7699 	alias width = typeof(super).width;
7700 	alias height = typeof(super).height;
7701 
7702 	@property int width(int w) @nogc pure @safe nothrow {
7703 		return this._width = w;
7704 	}
7705 
7706 	@property int height(int w) @nogc pure @safe nothrow {
7707 		return this._height = w;
7708 	}
7709 
7710 }
7711 
7712 /++
7713 	FixedPosition is like [StaticPosition], but its coordinates
7714 	are always relative to the viewport, meaning they do not scroll with
7715 	the parent content.
7716 +/
7717 class FixedPosition : StaticPosition {
7718 	///
7719 	this(Widget parent) { super(parent); }
7720 }
7721 
7722 version(win32_widgets)
7723 int processWmCommand(HWND parentWindow, HWND handle, ushort cmd, ushort idm) {
7724 	if(true) {
7725 		// cmd == 0 = menu, cmd == 1 = accelerator
7726 		if(auto item = idm in Action.mapping) {
7727 			foreach(handler; (*item).triggered)
7728 				handler();
7729 		/*
7730 			auto event = new Event("triggered", *item);
7731 			event.button = idm;
7732 			event.dispatch();
7733 		*/
7734 			return 0;
7735 		}
7736 	}
7737 	if(handle)
7738 	if(auto widgetp = handle in Widget.nativeMapping) {
7739 		(*widgetp).handleWmCommand(cmd, idm);
7740 		return 0;
7741 	}
7742 	return 1;
7743 }
7744 
7745 
7746 ///
7747 class Window : Widget {
7748 	int mouseCaptureCount = 0;
7749 	Widget mouseCapturedBy;
7750 	void captureMouse(Widget byWhom) {
7751 		assert(mouseCapturedBy is null || byWhom is mouseCapturedBy);
7752 		mouseCaptureCount++;
7753 		mouseCapturedBy = byWhom;
7754 		win.grabInput();
7755 	}
7756 	void releaseMouseCapture() {
7757 		mouseCaptureCount--;
7758 		mouseCapturedBy = null;
7759 		win.releaseInputGrab();
7760 	}
7761 
7762 	/++
7763 		Sets the window icon which is often seen in title bars and taskbars.
7764 
7765 		History:
7766 			Added April 5, 2022 (dub v10.8)
7767 	+/
7768 	@property void icon(MemoryImage icon) {
7769 		if(win && icon)
7770 			win.icon = icon;
7771 	}
7772 
7773 	///
7774 	@scriptable
7775 	@property bool focused() {
7776 		return win.focused;
7777 	}
7778 
7779 	static class Style : Widget.Style {
7780 		override WidgetBackground background() {
7781 			version(custom_widgets)
7782 				return WidgetBackground(WidgetPainter.visualTheme.windowBackgroundColor);
7783 			else version(win32_widgets)
7784 				return WidgetBackground(Color.transparent);
7785 			else static assert(0);
7786 		}
7787 	}
7788 	mixin OverrideStyle!Style;
7789 
7790 	/++
7791 		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.
7792 	+/
7793 	static int lineHeight() {
7794 		OperatingSystemFont font;
7795 		if(auto vt = WidgetPainter.visualTheme) {
7796 			font = vt.defaultFontCached();
7797 		}
7798 
7799 		if(font is null) {
7800 			static int defaultHeightCache;
7801 			if(defaultHeightCache == 0) {
7802 				font = new OperatingSystemFont;
7803 				font.loadDefault;
7804 				defaultHeightCache = font.height() * 5 / 4;
7805 			}
7806 			return defaultHeightCache;
7807 		}
7808 
7809 		return font.height() * 5 / 4;
7810 	}
7811 
7812 	Widget focusedWidget;
7813 
7814 	private SimpleWindow win_;
7815 
7816 	@property {
7817 		/++
7818 			Provides access to the underlying [SimpleWindow]. Note that changing properties on this window may disconnect minigui's event dispatchers.
7819 
7820 			History:
7821 				Prior to June 21, 2021, it was a public (but undocumented) member. Now it a semi-protected property.
7822 		+/
7823 		public SimpleWindow win() {
7824 			return win_;
7825 		}
7826 		///
7827 		protected void win(SimpleWindow w) {
7828 			win_ = w;
7829 		}
7830 	}
7831 
7832 	/// YOU ALMOST CERTAINLY SHOULD NOT USE THIS. This is really only for special purposes like pseudowindows or popup windows doing their own thing.
7833 	this(Widget p) {
7834 		tabStop = false;
7835 		super(p);
7836 	}
7837 
7838 	private void actualRedraw() {
7839 		if(recomputeChildLayoutRequired)
7840 			recomputeChildLayoutEntry();
7841 		if(!showing) return;
7842 
7843 		assert(parentWindow !is null);
7844 
7845 		auto w = drawableWindow;
7846 		if(w is null)
7847 			w = parentWindow.win;
7848 
7849 		if(w.closed())
7850 			return;
7851 
7852 		auto ugh = this.parent;
7853 		int lox, loy;
7854 		while(ugh) {
7855 			lox += ugh.x;
7856 			loy += ugh.y;
7857 			ugh = ugh.parent;
7858 		}
7859 		auto painter = w.draw(true);
7860 		privatePaint(WidgetPainter(painter, this), lox, loy, Rectangle(0, 0, int.max, int.max), false, willDraw());
7861 		// RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
7862 	}
7863 
7864 
7865 	private bool skipNextChar = false;
7866 
7867 	/++
7868 		Creates a window from an existing [SimpleWindow]. This constructor attaches various event handlers to the SimpleWindow object which may overwrite your existing handlers.
7869 
7870 		This constructor is intended primarily for internal use and may be changed to `protected` later.
7871 	+/
7872 	this(SimpleWindow win) {
7873 
7874 		static if(UsingSimpledisplayX11) {
7875 			win.discardAdditionalConnectionState = &discardXConnectionState;
7876 			win.recreateAdditionalConnectionState = &recreateXConnectionState;
7877 		}
7878 
7879 		tabStop = false;
7880 		super(null);
7881 		this.win = win;
7882 
7883 		win.addEventListener((Widget.RedrawEvent) {
7884 			if(win.eventQueued!RecomputeEvent) {
7885 				// import std.stdio; writeln("skipping");
7886 				return; // let the recompute event do the actual redraw
7887 			}
7888 			this.actualRedraw();
7889 		});
7890 
7891 		win.addEventListener((Widget.RecomputeEvent) {
7892 			recomputeChildLayoutEntry();
7893 			if(win.eventQueued!RedrawEvent)
7894 				return; // let the queued one do it
7895 			else {
7896 				// import std.stdio; writeln("drawing");
7897 				this.actualRedraw(); // if not queued, it needs to be done now anyway
7898 			}
7899 		});
7900 
7901 		this.width = win.width;
7902 		this.height = win.height;
7903 		this.parentWindow = this;
7904 
7905 		win.closeQuery = () {
7906 			if(this.emit!ClosingEvent())
7907 				win.close();
7908 		};
7909 		win.onClosing = () {
7910 			this.emit!ClosedEvent();
7911 		};
7912 
7913 		win.windowResized = (int w, int h) {
7914 			this.width = w;
7915 			this.height = h;
7916 			recomputeChildLayout();
7917 			// this causes a HUGE performance problem for no apparent benefit, hence the commenting
7918 			//version(win32_widgets)
7919 				//InvalidateRect(hwnd, null, true);
7920 			redraw();
7921 		};
7922 
7923 		win.onFocusChange = (bool getting) {
7924 			if(this.focusedWidget) {
7925 				if(getting) {
7926 					this.focusedWidget.emit!FocusEvent();
7927 					this.focusedWidget.emit!FocusInEvent();
7928 				} else {
7929 					this.focusedWidget.emit!BlurEvent();
7930 					this.focusedWidget.emit!FocusOutEvent();
7931 				}
7932 			}
7933 
7934 			if(getting) {
7935 				this.emit!FocusEvent();
7936 				this.emit!FocusInEvent();
7937 			} else {
7938 				this.emit!BlurEvent();
7939 				this.emit!FocusOutEvent();
7940 			}
7941 		};
7942 
7943 		win.onDpiChanged = {
7944 			this.queueRecomputeChildLayout();
7945 			auto event = new DpiChangedEvent(this);
7946 			event.sendDirectly();
7947 
7948 			privateDpiChanged();
7949 		};
7950 
7951 		win.setEventHandlers(
7952 			(MouseEvent e) {
7953 				dispatchMouseEvent(e);
7954 			},
7955 			(KeyEvent e) {
7956 				//import std.stdio;
7957 				//writefln("%x   %s", cast(uint) e.key, e.key);
7958 				dispatchKeyEvent(e);
7959 			},
7960 			(dchar e) {
7961 				if(e == 13) e = 10; // hack?
7962 				if(e == 127) return; // linux sends this, windows doesn't. we don't want it.
7963 				dispatchCharEvent(e);
7964 			},
7965 		);
7966 
7967 		addEventListener("char", (Widget, Event ev) {
7968 			if(skipNextChar) {
7969 				ev.preventDefault();
7970 				skipNextChar = false;
7971 			}
7972 		});
7973 
7974 		version(win32_widgets)
7975 		win.handleNativeEvent = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
7976 			if(hwnd !is this.win.impl.hwnd)
7977 				return 1; // we don't care... pass it on
7978 			auto ret = WindowProcedureHelper(this, hwnd, msg, wParam, lParam, mustReturn);
7979 			if(mustReturn)
7980 				return ret;
7981 			return 1; // pass it on
7982 		};
7983 
7984 		if(Window.newWindowCreated)
7985 			Window.newWindowCreated(this);
7986 	}
7987 
7988 	version(custom_widgets)
7989 	override void defaultEventHandler_click(ClickEvent event) {
7990 		if(event.target && event.target.tabStop)
7991 			event.target.focus();
7992 	}
7993 
7994 	private static void delegate(Window) newWindowCreated;
7995 
7996 	version(win32_widgets)
7997 	override void paint(WidgetPainter painter) {
7998 		/*
7999 		RECT rect;
8000 		rect.right = this.width;
8001 		rect.bottom = this.height;
8002 		DrawThemeBackground(theme, painter.impl.hdc, 4, 1, &rect, null);
8003 		*/
8004 		// 3dface is used as window backgrounds by Windows too, so that's why I'm using it here
8005 		auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
8006 		auto p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
8007 		// since the pen is null, to fill the whole space, we need the +1 on both.
8008 		gdi.Rectangle(painter.impl.hdc, 0, 0, this.width + 1, this.height + 1);
8009 		SelectObject(painter.impl.hdc, p);
8010 		SelectObject(painter.impl.hdc, b);
8011 	}
8012 	version(custom_widgets)
8013 	override void paint(WidgetPainter painter) {
8014 		auto cs = getComputedStyle();
8015 		painter.fillColor = cs.windowBackgroundColor;
8016 		painter.outlineColor = cs.windowBackgroundColor;
8017 		painter.drawRectangle(Point(0, 0), this.width, this.height);
8018 	}
8019 
8020 
8021 	override void defaultEventHandler_keydown(KeyDownEvent event) {
8022 		Widget _this = event.target;
8023 
8024 		if(event.key == Key.Tab) {
8025 			/* Window tab ordering is a recursive thingy with each group */
8026 
8027 			// FIXME inefficient
8028 			Widget[] helper(Widget p) {
8029 				if(p.hidden)
8030 					return null;
8031 				Widget[] childOrdering;
8032 
8033 				auto children = p.children.dup;
8034 
8035 				while(true) {
8036 					// UIs should be generally small, so gonna brute force it a little
8037 					// note that it must be a stable sort here; if all are index 0, it should be in order of declaration
8038 
8039 					Widget smallestTab;
8040 					foreach(ref c; children) {
8041 						if(c is null) continue;
8042 						if(smallestTab is null || c.tabOrder < smallestTab.tabOrder) {
8043 							smallestTab = c;
8044 							c = null;
8045 						}
8046 					}
8047 					if(smallestTab !is null) {
8048 						if(smallestTab.tabStop && !smallestTab.hidden)
8049 							childOrdering ~= smallestTab;
8050 						if(!smallestTab.hidden)
8051 							childOrdering ~= helper(smallestTab);
8052 					} else
8053 						break;
8054 
8055 				}
8056 
8057 				return childOrdering;
8058 			}
8059 
8060 			Widget[] tabOrdering = helper(this);
8061 
8062 			Widget recipient;
8063 
8064 			if(tabOrdering.length) {
8065 				bool seenThis = false;
8066 				Widget previous;
8067 				foreach(idx, child; tabOrdering) {
8068 					if(child is focusedWidget) {
8069 
8070 						if(event.shiftKey) {
8071 							if(idx == 0)
8072 								recipient = tabOrdering[$-1];
8073 							else
8074 								recipient = tabOrdering[idx - 1];
8075 							break;
8076 						}
8077 
8078 						seenThis = true;
8079 						if(idx + 1 == tabOrdering.length) {
8080 							// we're at the end, either move to the next group
8081 							// or start back over
8082 							recipient = tabOrdering[0];
8083 						}
8084 						continue;
8085 					}
8086 					if(seenThis) {
8087 						recipient = child;
8088 						break;
8089 					}
8090 					previous = child;
8091 				}
8092 			}
8093 
8094 			if(recipient !is null) {
8095 				// import std.stdio; writeln(typeid(recipient));
8096 				recipient.focus();
8097 
8098 				skipNextChar = true;
8099 			}
8100 		}
8101 
8102 		debug if(event.key == Key.F12) {
8103 			if(devTools) {
8104 				devTools.close();
8105 				devTools = null;
8106 			} else {
8107 				devTools = new DevToolWindow(this);
8108 				devTools.show();
8109 			}
8110 		}
8111 	}
8112 
8113 	debug DevToolWindow devTools;
8114 
8115 
8116 	/++
8117 		Creates a window. Please note windows are created in a hidden state, so you must call [show] or [loop] to get it to display.
8118 
8119 		History:
8120 			Prior to May 12, 2021, the default title was "D Application" (simpledisplay.d's default). After that, the default is `Runtime.args[0]` instead.
8121 
8122 			The width and height arguments were added to the overload that takes `string` first on June 21, 2021.
8123 	+/
8124 	this(int width = 500, int height = 500, string title = null) {
8125 		if(title is null) {
8126 			import core.runtime;
8127 			if(Runtime.args.length)
8128 				title = Runtime.args[0];
8129 		}
8130 		win = new SimpleWindow(width, height, title, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.normal, WindowFlags.dontAutoShow | WindowFlags.managesChildWindowFocus);
8131 
8132 		static if(UsingSimpledisplayX11) {
8133 		///+
8134 		// for input proxy
8135 		auto display = XDisplayConnection.get;
8136 		auto inputProxy = XCreateSimpleWindow(display, win.window, -1, -1, 1, 1, 0, 0, 0);
8137 		XSelectInput(display, inputProxy, EventMask.KeyPressMask | EventMask.KeyReleaseMask);
8138 		XMapWindow(display, inputProxy);
8139 		//import std.stdio; writefln("input proxy: 0x%0x", inputProxy);
8140 		this.inputProxy = new SimpleWindow(inputProxy);
8141 
8142 		XEvent lastEvent;
8143 		this.inputProxy.handleNativeEvent = (XEvent ev) {
8144 			lastEvent = ev;
8145 			return 1;
8146 		};
8147 		this.inputProxy.setEventHandlers(
8148 			(MouseEvent e) {
8149 				dispatchMouseEvent(e);
8150 			},
8151 			(KeyEvent e) {
8152 				//import std.stdio;
8153 				//writefln("%x   %s", cast(uint) e.key, e.key);
8154 				if(dispatchKeyEvent(e)) {
8155 					// FIXME: i should trap error
8156 					if(auto nw = cast(NestedChildWindowWidget) focusedWidget) {
8157 						auto thing = nw.focusableWindow();
8158 						if(thing && thing.window) {
8159 							lastEvent.xkey.window = thing.window;
8160 							// import std.stdio; writeln("sending event ", lastEvent.xkey);
8161 							trapXErrors( {
8162 								XSendEvent(XDisplayConnection.get, thing.window, false, 0, &lastEvent);
8163 							});
8164 						}
8165 					}
8166 				}
8167 			},
8168 			(dchar e) {
8169 				if(e == 13) e = 10; // hack?
8170 				if(e == 127) return; // linux sends this, windows doesn't. we don't want it.
8171 				dispatchCharEvent(e);
8172 			},
8173 		);
8174 		// done
8175 		//+/
8176 		}
8177 
8178 
8179 
8180 		win.setRequestedInputFocus = &this.setRequestedInputFocus;
8181 
8182 		this(win);
8183 	}
8184 
8185 	SimpleWindow inputProxy;
8186 
8187 	private SimpleWindow setRequestedInputFocus() {
8188 		return inputProxy;
8189 	}
8190 
8191 	/// ditto
8192 	this(string title, int width = 500, int height = 500) {
8193 		this(width, height, title);
8194 	}
8195 
8196 	///
8197 	@property string title() { return parentWindow.win.title; }
8198 	///
8199 	@property void title(string title) { parentWindow.win.title = title; }
8200 
8201 	///
8202 	@scriptable
8203 	void close() {
8204 		win.close();
8205 		// I synchronize here upon window closing to ensure all child windows
8206 		// get updated too before the event loop. This avoids some random X errors.
8207 		static if(UsingSimpledisplayX11) {
8208 			runInGuiThread( {
8209 				XSync(XDisplayConnection.get, false);
8210 			});
8211 		}
8212 	}
8213 
8214 	bool dispatchKeyEvent(KeyEvent ev) {
8215 		auto wid = focusedWidget;
8216 		if(wid is null)
8217 			wid = this;
8218 		KeyEventBase event = ev.pressed ? new KeyDownEvent(wid) : new KeyUpEvent(wid);
8219 		event.originalKeyEvent = ev;
8220 		event.key = ev.key;
8221 		event.state = ev.modifierState;
8222 		event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false;
8223 		event.altKey = (ev.modifierState & ModifierState.alt) ? true : false;
8224 		event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false;
8225 		event.dispatch();
8226 
8227 		return !event.propagationStopped;
8228 	}
8229 
8230 	// returns true if propagation should continue into nested things.... prolly not a great thing to do.
8231 	bool dispatchCharEvent(dchar ch) {
8232 		if(focusedWidget) {
8233 			auto event = new CharEvent(focusedWidget, ch);
8234 			event.dispatch();
8235 			return !event.propagationStopped;
8236 		}
8237 		return true;
8238 	}
8239 
8240 	Widget mouseLastOver;
8241 	Widget mouseLastDownOn;
8242 	bool lastWasDoubleClick;
8243 	bool dispatchMouseEvent(MouseEvent ev) {
8244 		auto eleR = widgetAtPoint(this, ev.x, ev.y);
8245 		auto ele = eleR.widget;
8246 
8247 		auto captureEle = ele;
8248 
8249 		if(mouseCapturedBy !is null) {
8250 			if(ele !is mouseCapturedBy && !mouseCapturedBy.isAParentOf(ele))
8251 				captureEle = mouseCapturedBy;
8252 		}
8253 
8254 		// a hack to get it relative to the widget.
8255 		eleR.x = ev.x;
8256 		eleR.y = ev.y;
8257 		auto pain = captureEle;
8258 		while(pain) {
8259 			eleR.x -= pain.x;
8260 			eleR.y -= pain.y;
8261 			pain.addScrollPosition(eleR.x, eleR.y);
8262 			pain = pain.parent;
8263 		}
8264 
8265 		void populateMouseEventBase(MouseEventBase event) {
8266 			event.button = ev.button;
8267 			event.buttonLinear = ev.buttonLinear;
8268 			event.state = ev.modifierState;
8269 			event.clientX = eleR.x;
8270 			event.clientY = eleR.y;
8271 
8272 			event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false;
8273 			event.altKey = (ev.modifierState & ModifierState.alt) ? true : false;
8274 			event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false;
8275 		}
8276 
8277 		if(ev.type == MouseEventType.buttonPressed) {
8278 			{
8279 				auto event = new MouseDownEvent(captureEle);
8280 				populateMouseEventBase(event);
8281 				event.dispatch();
8282 			}
8283 
8284 			if(ev.button != MouseButton.wheelDown && ev.button != MouseButton.wheelUp && mouseLastDownOn is ele && ev.doubleClick) {
8285 				auto event = new DoubleClickEvent(captureEle);
8286 				populateMouseEventBase(event);
8287 				event.dispatch();
8288 				lastWasDoubleClick = ev.doubleClick;
8289 			} else {
8290 				lastWasDoubleClick = false;
8291 			}
8292 
8293 			mouseLastDownOn = ele;
8294 		} else if(ev.type == MouseEventType.buttonReleased) {
8295 			{
8296 				auto event = new MouseUpEvent(captureEle);
8297 				populateMouseEventBase(event);
8298 				event.dispatch();
8299 			}
8300 			if(!lastWasDoubleClick && mouseLastDownOn is ele) {
8301 				auto event = new ClickEvent(captureEle);
8302 				populateMouseEventBase(event);
8303 				event.dispatch();
8304 			}
8305 		} else if(ev.type == MouseEventType.motion) {
8306 			// motion
8307 			{
8308 				auto event = new MouseMoveEvent(captureEle);
8309 				populateMouseEventBase(event); // fills in button which is meaningless but meh
8310 				event.dispatch();
8311 			}
8312 
8313 			if(mouseLastOver !is ele) {
8314 				if(ele !is null) {
8315 					if(!isAParentOf(ele, mouseLastOver)) {
8316 						ele.setDynamicState(DynamicState.hover, true);
8317 						auto event = new MouseEnterEvent(ele);
8318 						event.relatedTarget = mouseLastOver;
8319 						event.sendDirectly();
8320 
8321 						ele.useStyleProperties((scope Widget.Style s) {
8322 							ele.parentWindow.win.cursor = s.cursor;
8323 						});
8324 					}
8325 				}
8326 
8327 				if(mouseLastOver !is null) {
8328 					if(!isAParentOf(mouseLastOver, ele)) {
8329 						mouseLastOver.setDynamicState(DynamicState.hover, false);
8330 						auto event = new MouseLeaveEvent(mouseLastOver);
8331 						event.relatedTarget = ele;
8332 						event.sendDirectly();
8333 					}
8334 				}
8335 
8336 				if(ele !is null) {
8337 					auto event = new MouseOverEvent(ele);
8338 					event.relatedTarget = mouseLastOver;
8339 					event.dispatch();
8340 				}
8341 
8342 				if(mouseLastOver !is null) {
8343 					auto event = new MouseOutEvent(mouseLastOver);
8344 					event.relatedTarget = ele;
8345 					event.dispatch();
8346 				}
8347 
8348 				mouseLastOver = ele;
8349 			}
8350 		}
8351 
8352 		return true; // FIXME: the event default prevented?
8353 	}
8354 
8355 	/++
8356 		Shows the window and runs the application event loop.
8357 
8358 		Blocks until this window is closed.
8359 
8360 		History:
8361 			The [BlockingMode] parameter was added on December 8, 2021.
8362 			The default behavior is to block until the application quits
8363 			(so all windows have been closed), unless another minigui or
8364 			simpledisplay event loop is already running, in which case it
8365 			will block until this window closes specifically.
8366 	+/
8367 	@scriptable
8368 	void loop(BlockingMode bm = BlockingMode.automatic) {
8369 		if(win.closed)
8370 			return; // otherwise show will throw
8371 		show();
8372 		win.eventLoopWithBlockingMode(bm, 0);
8373 	}
8374 
8375 	private bool firstShow = true;
8376 
8377 	@scriptable
8378 	override void show() {
8379 		bool rd = false;
8380 		if(firstShow) {
8381 			firstShow = false;
8382 			recomputeChildLayout();
8383 			auto f = getFirstFocusable(this); // FIXME: autofocus?
8384 			if(f)
8385 				f.focus();
8386 			redraw();
8387 		}
8388 		win.show();
8389 		super.show();
8390 	}
8391 	@scriptable
8392 	override void hide() {
8393 		win.hide();
8394 		super.hide();
8395 	}
8396 
8397 	static Widget getFirstFocusable(Widget start) {
8398 		if(start is null)
8399 			return null;
8400 
8401 		foreach(widget; &start.focusableWidgets) {
8402 			return widget;
8403 		}
8404 
8405 		return null;
8406 	}
8407 
8408 	static Widget getLastFocusable(Widget start) {
8409 		if(start is null)
8410 			return null;
8411 
8412 		Widget last;
8413 		foreach(widget; &start.focusableWidgets) {
8414 			last = widget;
8415 		}
8416 
8417 		return last;
8418 	}
8419 
8420 
8421 	mixin Emits!ClosingEvent;
8422 	mixin Emits!ClosedEvent;
8423 }
8424 
8425 /++
8426 	History:
8427 		Added January 12, 2022
8428 +/
8429 class DpiChangedEvent : Event {
8430 	enum EventString = "dpichanged";
8431 
8432 	this(Widget target) {
8433 		super(EventString, target);
8434 	}
8435 }
8436 
8437 debug private class DevToolWindow : Window {
8438 	Window p;
8439 
8440 	TextEdit parentList;
8441 	TextEdit logWindow;
8442 	TextLabel clickX, clickY;
8443 
8444 	this(Window p) {
8445 		this.p = p;
8446 		super(400, 300, "Developer Toolbox");
8447 
8448 		logWindow = new TextEdit(this);
8449 		parentList = new TextEdit(this);
8450 
8451 		auto hl = new HorizontalLayout(this);
8452 		clickX = new TextLabel("", TextAlignment.Right, hl);
8453 		clickY = new TextLabel("", TextAlignment.Right, hl);
8454 
8455 		parentListeners ~= p.addEventListener("*", (Event ev) {
8456 			log(typeid(ev.source).name, " emitted ", typeid(ev).name);
8457 		});
8458 
8459 		parentListeners ~= p.addEventListener((ClickEvent ev) {
8460 			auto s = ev.srcElement;
8461 			string list = s.toString();
8462 			s = s.parent;
8463 			while(s) {
8464 				list ~= "\n";
8465 				list ~= s.toString();
8466 				s = s.parent;
8467 			}
8468 			parentList.content = list;
8469 
8470 			clickX.label = toInternal!string(ev.clientX);
8471 			clickY.label = toInternal!string(ev.clientY);
8472 		});
8473 	}
8474 
8475 	EventListener[] parentListeners;
8476 
8477 	override void close() {
8478 		assert(p !is null);
8479 		foreach(p; parentListeners)
8480 			p.disconnect();
8481 		parentListeners = null;
8482 		p.devTools = null;
8483 		p = null;
8484 		super.close();
8485 	}
8486 
8487 	override void defaultEventHandler_keydown(KeyDownEvent ev) {
8488 		if(ev.key == Key.F12) {
8489 			this.close();
8490 			if(p)
8491 				p.devTools = null;
8492 		} else {
8493 			super.defaultEventHandler_keydown(ev);
8494 		}
8495 	}
8496 
8497 	void log(T...)(T t) {
8498 		string str;
8499 		import std.conv;
8500 		foreach(i; t)
8501 			str ~= to!string(i);
8502 		str ~= "\n";
8503 		logWindow.addText(str);
8504 
8505 		version(custom_widgets)
8506 		logWindow.ensureVisibleInScroll(logWindow.textLayout.caretBoundingBox());
8507 	}
8508 }
8509 
8510 /++
8511 	A dialog is a transient window that intends to get information from
8512 	the user before being dismissed.
8513 +/
8514 abstract class Dialog : Window {
8515 	///
8516 	this(int width, int height, string title = null) {
8517 		super(width, height, title);
8518 	}
8519 
8520 	///
8521 	abstract void OK();
8522 
8523 	///
8524 	void Cancel() {
8525 		this.close();
8526 	}
8527 }
8528 
8529 /++
8530 	A custom widget similar to the HTML5 <details> tag.
8531 +/
8532 version(none)
8533 class DetailsView : Widget {
8534 
8535 }
8536 
8537 // FIXME: maybe i should expose the other list views Windows offers too
8538 
8539 /++
8540 	A TableView is a widget made to display a table of data strings.
8541 
8542 
8543 	Future_Directions:
8544 		Each item should be able to take an icon too and maybe I'll allow more of the view modes Windows offers.
8545 
8546 		I will add a selection changed event at some point, as well as item clicked events.
8547 	History:
8548 		Added September 24, 2021. Initial api stabilized in dub v10.4, but it isn't completely feature complete yet.
8549 	See_Also:
8550 		[ListWidget] which displays a list of strings without additional columns.
8551 +/
8552 class TableView : Widget {
8553 	/++
8554 
8555 	+/
8556 	this(Widget parent) {
8557 		super(parent);
8558 
8559 		version(win32_widgets) {
8560 			createWin32Window(this, WC_LISTVIEW, "", LVS_REPORT | LVS_OWNERDATA);//| LVS_OWNERDRAWFIXED);
8561 		} else version(custom_widgets) {
8562 			auto smw = new ScrollMessageWidget(this);
8563 			smw.addDefaultKeyboardListeners();
8564 			smw.addDefaultWheelListeners(1, scaleWithDpi(16));
8565 			tvwi = new TableViewWidgetInner(this, smw);
8566 		}
8567 	}
8568 
8569 	// FIXME: auto-size columns on double click of header thing like in Windows
8570 	// it need only make the currently displayed things fit well.
8571 
8572 
8573 	private ColumnInfo[] columns;
8574 	private int itemCount;
8575 
8576 	version(custom_widgets) private {
8577 		TableViewWidgetInner tvwi;
8578 	}
8579 
8580 	/// Passed to [setColumnInfo]
8581 	static struct ColumnInfo {
8582 		const(char)[] name; /// the name displayed in the header
8583 		/++
8584 			The default width, in pixels. As a special case, you can set this to -1
8585 			if you want the system to try to automatically size the width to fit visible
8586 			content. If it can't, it will try to pick a sensible default size.
8587 
8588 			Any other negative value is not allowed and may lead to unpredictable results.
8589 
8590 			History:
8591 				The -1 behavior was specified on December 3, 2021. It actually worked before
8592 				anyway on Win32 but now it is a formal feature with partial Linux support.
8593 
8594 			Bugs:
8595 				It doesn't actually attempt to calculate a best-fit width on Linux as of
8596 				December 3, 2021. I do plan to fix this in the future, but Windows is the
8597 				priority right now. At least it doesn't break things when you use it now.
8598 		+/
8599 		int width;
8600 
8601 		/++
8602 			Alignment of the text in the cell. Applies to the header as well as all data in this
8603 			column.
8604 
8605 			Bugs:
8606 				On Windows, the first column ignores this member and is always left aligned.
8607 				You can work around this by inserting a dummy first column with width = 0
8608 				then putting your actual data in the second column, which does respect the
8609 				alignment.
8610 
8611 				This is a quirk of the operating system's implementation going back a very
8612 				long time and is unlikely to ever be fixed.
8613 		+/
8614 		TextAlignment alignment;
8615 
8616 		/++
8617 			After all the pixel widths have been assigned, any left over
8618 			space is divided up among all columns and distributed to according
8619 			to the widthPercent field.
8620 
8621 
8622 			For example, if you have two fields, both with width 50 and one with
8623 			widthPercent of 25 and the other with widthPercent of 75, and the
8624 			container is 200 pixels wide, first both get their width of 50.
8625 			then the 100 remaining pixels are split up, so the one gets a total
8626 			of 75 pixels and the other gets a total of 125.
8627 
8628 			This is automatically applied as the window is resized.
8629 
8630 			If there is not enough space - that is, when a horizontal scrollbar
8631 			needs to appear - there are 0 pixels divided up, and thus everyone
8632 			gets 0. This can cause a column to shrink out of proportion when
8633 			passing the scroll threshold.
8634 
8635 			It is important to still set a fixed width (that is, to populate the
8636 			`width` field) even if you use the percents because that will be the
8637 			default minimum in the event of a scroll bar appearing.
8638 
8639 			The percents total in the column can never exceed 100 or be less than 0.
8640 			Doing this will trigger an assert error.
8641 
8642 			Implementation note:
8643 
8644 			Please note that percentages are only recalculated 1) upon original
8645 			construction and 2) upon resizing the control. If the user adjusts the
8646 			width of a column, the percentage items will not be updated.
8647 
8648 			On the other hand, if the user adjusts the width of a percentage column
8649 			then resizes the window, it is recalculated, meaning their hand adjustment
8650 			is discarded. This specific behavior may change in the future as it is
8651 			arguably a bug, but I'm not certain yet.
8652 
8653 			History:
8654 				Added November 10, 2021 (dub v10.4)
8655 		+/
8656 		int widthPercent;
8657 
8658 
8659 		private int calculatedWidth;
8660 	}
8661 	/++
8662 		Sets the number of columns along with information about the headers.
8663 
8664 		Please note: on Windows, the first column ignores your alignment preference
8665 		and is always left aligned.
8666 	+/
8667 	void setColumnInfo(ColumnInfo[] columns...) {
8668 
8669 		foreach(ref c; columns) {
8670 			c.name = c.name.idup;
8671 		}
8672 		this.columns = columns.dup;
8673 
8674 		updateCalculatedWidth(false);
8675 
8676 		version(custom_widgets) {
8677 			tvwi.header.updateHeaders();
8678 			tvwi.updateScrolls();
8679 		} else version(win32_widgets)
8680 		foreach(i, column; this.columns) {
8681 			LVCOLUMN lvColumn;
8682 			lvColumn.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
8683 			lvColumn.cx = column.width == -1 ? -1 : column.calculatedWidth;
8684 
8685 			auto bfr = WCharzBuffer(column.name);
8686 			lvColumn.pszText = bfr.ptr;
8687 
8688 			if(column.alignment & TextAlignment.Center)
8689 				lvColumn.fmt = LVCFMT_CENTER;
8690 			else if(column.alignment & TextAlignment.Right)
8691 				lvColumn.fmt = LVCFMT_RIGHT;
8692 			else
8693 				lvColumn.fmt = LVCFMT_LEFT;
8694 
8695 			if(SendMessage(hwnd, LVM_INSERTCOLUMN, cast(WPARAM) i, cast(LPARAM) &lvColumn) == -1)
8696 				throw new WindowsApiException("Insert Column Fail");
8697 		}
8698 	}
8699 
8700 	private int getActualSetSize(size_t i, bool askWindows) {
8701 		version(win32_widgets)
8702 			if(askWindows)
8703 				return cast(int) SendMessage(hwnd, LVM_GETCOLUMNWIDTH, cast(WPARAM) i, 0);
8704 		auto w = columns[i].width;
8705 		if(w == -1)
8706 			return 50; // idk, just give it some space so the percents aren't COMPLETELY off FIXME
8707 		return w;
8708 	}
8709 
8710 	private void updateCalculatedWidth(bool informWindows) {
8711 		int padding;
8712 		version(win32_widgets)
8713 			padding = 4;
8714 		int remaining = this.width;
8715 		foreach(i, column; columns)
8716 			remaining -= this.getActualSetSize(i, informWindows && column.widthPercent == 0) + padding;
8717 		remaining -= padding;
8718 		if(remaining < 0)
8719 			remaining = 0;
8720 
8721 		int percentTotal;
8722 		foreach(i, ref column; columns) {
8723 			percentTotal += column.widthPercent;
8724 
8725 			auto c = this.getActualSetSize(i, informWindows && column.widthPercent == 0) + (remaining * column.widthPercent) / 100;
8726 
8727 			column.calculatedWidth = c;
8728 
8729 			version(win32_widgets)
8730 			if(informWindows)
8731 				SendMessage(hwnd, LVM_SETCOLUMNWIDTH, i, c); // LVSCW_AUTOSIZE or LVSCW_AUTOSIZE_USEHEADER are amazing omg
8732 		}
8733 
8734 		assert(percentTotal >= 0, "The total percents in your column definitions were negative. They must add up to something between 0 and 100.");
8735 		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).");
8736 			
8737 
8738 	}
8739 
8740 	override void registerMovement() {
8741 		super.registerMovement();
8742 
8743 		updateCalculatedWidth(true);
8744 	}
8745 
8746 	/++
8747 		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.
8748 	+/
8749 	void setItemCount(int count) {
8750 		this.itemCount = count;
8751 		version(custom_widgets) {
8752 			tvwi.updateScrolls();
8753 			redraw();
8754 		} else version(win32_widgets) {
8755 			SendMessage(hwnd, LVM_SETITEMCOUNT, count, 0);
8756 		}
8757 	}
8758 
8759 	/++
8760 		Clears all items;
8761 	+/
8762 	void clear() {
8763 		this.itemCount = 0;
8764 		this.columns = null;
8765 		version(custom_widgets) {
8766 			tvwi.header.updateHeaders();
8767 			tvwi.updateScrolls();
8768 			redraw();
8769 		} else version(win32_widgets) {
8770 			SendMessage(hwnd, LVM_DELETEALLITEMS, 0, 0);
8771 		}
8772 	}
8773 
8774 	/+
8775 	version(win32_widgets)
8776 	override int handleWmDrawItem(DRAWITEMSTRUCT* dis) 
8777 		auto itemId = dis.itemID;
8778 		auto hdc = dis.hDC;
8779 		auto rect = dis.rcItem;
8780 		switch(dis.itemAction) {
8781 			case ODA_DRAWENTIRE:
8782 
8783 				// FIXME: do other items
8784 				// FIXME: do the focus rectangle i guess
8785 				// FIXME: alignment
8786 				// FIXME: column width
8787 				// FIXME: padding left
8788 				// FIXME: check dpi scaling
8789 				// FIXME: don't owner draw unless it is necessary.
8790 
8791 				auto padding = GetSystemMetrics(SM_CXEDGE); // FIXME: for dpi
8792 				RECT itemRect;
8793 				itemRect.top = 1; // subitem idx, 1-based
8794 				itemRect.left = LVIR_BOUNDS;
8795 
8796 				SendMessage(hwnd, LVM_GETSUBITEMRECT, itemId, cast(LPARAM) &itemRect);
8797 				itemRect.left += padding;
8798 
8799 				getData(itemId, 0, (in char[] data) {
8800 					auto wdata = WCharzBuffer(data);
8801 					DrawTextW(hdc, wdata.ptr, wdata.length, &itemRect, DT_RIGHT| DT_END_ELLIPSIS);
8802 
8803 				});
8804 			goto case;
8805 			case ODA_FOCUS:
8806 				if(dis.itemState & ODS_FOCUS)
8807 					DrawFocusRect(hdc, &rect);
8808 			break;
8809 			case ODA_SELECT:
8810 				// itemState & ODS_SELECTED
8811 			break;
8812 			default:
8813 		}
8814 		return 1;
8815 	}
8816 	+/
8817 
8818 	version(win32_widgets) {
8819 		CellStyle last;
8820 		COLORREF defaultColor;
8821 		COLORREF defaultBackground;
8822 	}
8823 
8824 	version(win32_widgets)
8825 	override int handleWmNotify(NMHDR* hdr, int code, out int mustReturn) {
8826 		switch(code) {
8827 			case NM_CUSTOMDRAW:
8828 				auto s = cast(NMLVCUSTOMDRAW*) hdr;
8829 				switch(s.nmcd.dwDrawStage) {
8830 					case CDDS_PREPAINT:
8831 						if(getCellStyle is null)
8832 							return 0;
8833 
8834 						mustReturn = true;
8835 						return CDRF_NOTIFYITEMDRAW;
8836 					case CDDS_ITEMPREPAINT:
8837 						mustReturn = true;
8838 						return CDRF_NOTIFYSUBITEMDRAW;
8839 					case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
8840 						mustReturn = true;
8841 
8842 						if(getCellStyle is null) // this SHOULD never happen...
8843 							return 0;
8844 
8845 						if(s.iSubItem == 0) {
8846 							// Windows resets it per row so we'll use item 0 as a chance
8847 							// to capture these for later
8848 							defaultColor = s.clrText;
8849 							defaultBackground = s.clrTextBk;
8850 						}
8851 
8852 						auto style = getCellStyle(cast(int) s.nmcd.dwItemSpec, cast(int) s.iSubItem);
8853 						// if no special style and no reset needed...
8854 						if(style == CellStyle.init && (s.iSubItem == 0 || last == CellStyle.init))
8855 							return 0; // allow default processing to continue
8856 
8857 						last = style;
8858 
8859 						// might still need to reset or use the preference.
8860 
8861 						if(style.flags & CellStyle.Flags.textColorSet)
8862 							s.clrText = style.textColor.asWindowsColorRef;
8863 						else
8864 							s.clrText = defaultColor; // reset in case it was set from last iteration not a fan
8865 						if(style.flags & CellStyle.Flags.backgroundColorSet)
8866 							s.clrTextBk = style.backgroundColor.asWindowsColorRef;
8867 						else
8868 							s.clrTextBk = defaultBackground; // need to reset it... not a fan of this
8869 
8870 						return CDRF_NEWFONT;
8871 					default:
8872 						return 0;
8873 
8874 				}
8875 			case NM_RETURN: // no need since i subclass keydown
8876 			break;
8877 			case LVN_COLUMNCLICK:
8878 				auto info = cast(LPNMLISTVIEW) hdr;
8879 				this.emit!HeaderClickedEvent(info.iSubItem);
8880 			break;
8881 			case NM_CLICK:
8882 			case NM_DBLCLK:
8883 			case NM_RCLICK:
8884 			case NM_RDBLCLK:
8885 				// the item/subitem is set here and that can be a useful notification
8886 				// even beyond the normal click notification
8887 			break;
8888 			case LVN_GETDISPINFO:
8889 				LV_DISPINFO* info = cast(LV_DISPINFO*) hdr;
8890 				if(info.item.mask & LVIF_TEXT) {
8891 					if(getData) {
8892 						getData(info.item.iItem, info.item.iSubItem, (in char[] dataReceived) {
8893 							auto bfr = WCharzBuffer(dataReceived);
8894 							auto len = info.item.cchTextMax;
8895 							if(bfr.length < len)
8896 								len = cast(typeof(len)) bfr.length;
8897 							info.item.pszText[0 .. len] = bfr.ptr[0 .. len];
8898 							info.item.pszText[len] = 0;
8899 						});
8900 					} else {
8901 						info.item.pszText[0] = 0;
8902 					}
8903 					//info.item.iItem
8904 					//if(info.item.iSubItem)
8905 				}
8906 			break;
8907 			default:
8908 		}
8909 		return 0;
8910 	}
8911 
8912 	override bool encapsulatedChildren() {
8913 		return true;
8914 	}
8915 
8916 	/++
8917 		Informs the control that content has changed.
8918 
8919 		History:
8920 			Added November 10, 2021 (dub v10.4)
8921 	+/
8922 	void update() {
8923 		version(custom_widgets)
8924 			redraw();
8925 		else {
8926 			SendMessage(hwnd, LVM_REDRAWITEMS, 0, SendMessage(hwnd, LVM_GETITEMCOUNT, 0, 0));
8927 			UpdateWindow(hwnd);
8928 		}
8929 			
8930 
8931 	}
8932 
8933 	/++
8934 		Called by the system to request the text content of an individual cell. You
8935 		should pass the text into the provided `sink` delegate. This function will be
8936 		called for each visible cell as-needed when drawing.
8937 	+/
8938 	void delegate(int row, int column, scope void delegate(in char[]) sink) getData;
8939 
8940 	/++
8941 		Available per-cell style customization options. Use one of the constructors
8942 		provided to set the values conveniently, or default construct it and set individual
8943 		values yourself. Just remember to set the `flags` so your values are actually used.
8944 		If the flag isn't set, the field is ignored and the system default is used instead.
8945 
8946 		This is returned by the [getCellStyle] delegate.
8947 
8948 		Examples:
8949 			---
8950 			// assumes you have a variables called `my_data` which is an array of arrays of numbers
8951 			auto table = new TableView(window);
8952 			// snip: you would set up columns here
8953 
8954 			// this is how you provide data to the table view class
8955 			table.getData = delegate(int row, int column, scope void delegate(in char[]) sink) {
8956 				import std.conv;
8957 				sink(to!string(my_data[row][column]));
8958 			};
8959 
8960 			// and this is how you customize the colors
8961 			table.getCellStyle = delegate(int row, int column) {
8962 				return (my_data[row][column] < 0) ?
8963 					TableView.CellStyle(Color.red); // make negative numbers red
8964 					: TableView.CellStyle.init; // leave the rest alone
8965 			};
8966 			// snip: you would call table.setItemCount here then continue with the rest of your window setup work
8967 			---
8968 
8969 		History:
8970 			Added November 27, 2021 (dub v10.4)
8971 	+/
8972 	struct CellStyle {
8973 		/// 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.
8974 		this(Color textColor) {
8975 			this.textColor = textColor;
8976 			this.flags |= Flags.textColorSet;
8977 		}
8978 		/// Sets a custom text and background color.
8979 		this(Color textColor, Color backgroundColor) {
8980 			this.textColor = textColor;
8981 			this.backgroundColor = backgroundColor;
8982 			this.flags |= Flags.textColorSet | Flags.backgroundColorSet;
8983 		}
8984 
8985 		Color textColor;
8986 		Color backgroundColor;
8987 		int flags; /// bitmask of [Flags]
8988 		/// available options to combine into [flags]
8989 		enum Flags {
8990 			textColorSet = 1 << 0,
8991 			backgroundColorSet = 1 << 1,
8992 		}
8993 	}
8994 	/++
8995 		Companion delegate to [getData] that allows you to custom style each
8996 		cell of the table.
8997 
8998 		Returns:
8999 			A [CellStyle] structure that describes the desired style for the
9000 			given cell. `return CellStyle.init` if you want the default style.
9001 
9002 		History:
9003 			Added November 27, 2021 (dub v10.4)
9004 	+/
9005 	CellStyle delegate(int row, int column) getCellStyle;
9006 
9007 	// i want to be able to do things like draw little colored things to show red for negative numbers
9008 	// or background color indicators or even in-cell charts
9009 	// void delegate(int row, int column, WidgetPainter painter, int width, int height, in char[] text) drawCell;
9010 
9011 	/++
9012 		When the user clicks on a header, this event is emitted. It has a meber to identify which header (by index) was clicked.
9013 	+/
9014 	mixin Emits!HeaderClickedEvent;
9015 }
9016 
9017 /++
9018 	This is emitted by the [TableView] when a user clicks on a column header.
9019 
9020 	Its member `columnIndex` has the zero-based index of the column that was clicked.
9021 
9022 	The default behavior of this event is to do nothing, so `preventDefault` has no effect.
9023 
9024 	History:
9025 		Added November 27, 2021 (dub v10.4)
9026 +/
9027 class HeaderClickedEvent : Event {
9028 	enum EventString = "HeaderClicked";
9029 	this(Widget target, int columnIndex) {
9030 		this.columnIndex = columnIndex;
9031 		super(EventString, target);
9032 	}
9033 
9034 	/// The index of the column
9035 	int columnIndex;
9036 
9037 	///
9038 	override @property int intValue() {
9039 		return columnIndex;
9040 	}
9041 }
9042 
9043 version(custom_widgets)
9044 private class TableViewWidgetInner : Widget {
9045 
9046 // wrap this thing in a ScrollMessageWidget
9047 
9048 	TableView tvw;
9049 	ScrollMessageWidget smw;
9050 	HeaderWidget header;
9051 
9052 	this(TableView tvw, ScrollMessageWidget smw) {
9053 		this.tvw = tvw;
9054 		this.smw = smw;
9055 		super(smw);
9056 
9057 		this.tabStop = true;
9058 
9059 		header = new HeaderWidget(this, smw.getHeader());
9060 
9061 		smw.addEventListener("scroll", () {
9062 			this.redraw();
9063 			header.redraw();
9064 		});
9065 
9066 
9067 		// I need headers outside the scroll area but rendered on the same line as the up arrow
9068 		// FIXME: add a fixed header to the SMW
9069 	}
9070 
9071 	enum padding = 3;
9072 
9073 	void updateScrolls() {
9074 		int w;
9075 		foreach(idx, column; tvw.columns) {
9076 			if(column.width == 0) continue;
9077 			w += tvw.getActualSetSize(idx, false);// + padding;
9078 		}
9079 		smw.setTotalArea(w, tvw.itemCount);
9080 		columnsWidth = w;
9081 	}
9082 
9083 	private int columnsWidth;
9084 
9085 	private int lh() { return scaleWithDpi(16); } // FIXME lineHeight
9086 
9087 	override void registerMovement() {
9088 		super.registerMovement();
9089 		// FIXME: actual column width. it might need to be done per-pixel instead of per-colum
9090 		smw.setViewableArea(this.width, this.height / lh);
9091 	}
9092 
9093 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
9094 		int x;
9095 		int y;
9096 
9097 		int row = smw.position.y;
9098 
9099 		foreach(lol; 0 .. this.height / lh) {
9100 			if(row >= tvw.itemCount)
9101 				break;
9102 			x = 0;
9103 			foreach(columnNumber, column; tvw.columns) {
9104 				auto x2 = x + column.calculatedWidth;
9105 				auto smwx = smw.position.x;
9106 
9107 				if(x2 > smwx /* if right side of it is visible at all */ || (x >= smwx && x < smwx + this.width) /* left side is visible at all*/) {
9108 					auto startX = x;
9109 					auto endX = x + column.calculatedWidth;
9110 					switch (column.alignment & (TextAlignment.Left | TextAlignment.Center | TextAlignment.Right)) {
9111 						case TextAlignment.Left: startX += padding; break;
9112 						case TextAlignment.Center: startX += padding; endX -= padding; break;
9113 						case TextAlignment.Right: endX -= padding; break;
9114 						default: /* broken */ break;
9115 					}
9116 					if(column.width != 0) // no point drawing an invisible column
9117 					tvw.getData(row, cast(int) columnNumber, (info) {
9118 						// auto clip = painter.setClipRectangle(
9119 
9120 						void dotext(WidgetPainter painter) {
9121 							painter.drawText(Point(startX - smw.position.x, y), info, Point(endX - smw.position.x, y + lh), column.alignment);
9122 						}
9123 
9124 						if(tvw.getCellStyle !is null) {
9125 							auto style = tvw.getCellStyle(row, cast(int) columnNumber);
9126 
9127 							if(style.flags & TableView.CellStyle.Flags.backgroundColorSet) {
9128 								auto tempPainter = painter;
9129 								tempPainter.fillColor = style.backgroundColor;
9130 								tempPainter.outlineColor = style.backgroundColor;
9131 
9132 								tempPainter.drawRectangle(Point(startX - smw.position.x, y), 
9133 									Point(endX - smw.position.x, y + lh));
9134 							}
9135 							auto tempPainter = painter;
9136 							if(style.flags & TableView.CellStyle.Flags.textColorSet)
9137 								tempPainter.outlineColor = style.textColor;
9138 
9139 							dotext(tempPainter);
9140 						} else {
9141 							dotext(painter);
9142 						}
9143 					});
9144 				}
9145 
9146 				x += column.calculatedWidth;
9147 			}
9148 			row++;
9149 			y += lh;
9150 		}
9151 		return bounds;
9152 	}
9153 
9154 	static class Style : Widget.Style {
9155 		override WidgetBackground background() {
9156 			return WidgetBackground(WidgetPainter.visualTheme.widgetBackgroundColor);
9157 		}
9158 	}
9159 	mixin OverrideStyle!Style;
9160 
9161 	private static class HeaderWidget : Widget {
9162 		this(TableViewWidgetInner tvw, Widget parent) {
9163 			super(parent);
9164 			this.tvw = tvw;
9165 
9166 			this.remainder = new Button("", this);
9167 
9168 			this.addEventListener((scope ClickEvent ev) {
9169 				int header = -1;
9170 				foreach(idx, child; this.children[1 .. $]) {
9171 					if(child is ev.target) {
9172 						header = cast(int) idx;
9173 						break;
9174 					}
9175 				}
9176 
9177 				if(header != -1) {
9178 					auto hce = new HeaderClickedEvent(tvw.tvw, header);
9179 					hce.dispatch();
9180 				}
9181 
9182 			});
9183 		}
9184 
9185 		void updateHeaders() {
9186 			foreach(child; children[1 .. $])
9187 				child.removeWidget();
9188 
9189 			foreach(column; tvw.tvw.columns) {
9190 				// the cast is ok because I dup it above, just the type is never changed.
9191 				// all this is private so it should never get messed up.
9192 				new Button(ImageLabel(cast(string) column.name, column.alignment), this);
9193 			}
9194 		}
9195 
9196 		Button remainder;
9197 		TableViewWidgetInner tvw;
9198 
9199 		override void recomputeChildLayout() {
9200 			registerMovement();
9201 			int pos;
9202 			foreach(idx, child; children[1 .. $]) {
9203 				if(idx >= tvw.tvw.columns.length)
9204 					continue;
9205 				child.x = pos;
9206 				child.y = 0;
9207 				child.width = tvw.tvw.columns[idx].calculatedWidth;
9208 				child.height = scaleWithDpi(16);// this.height;
9209 				pos += child.width;
9210 
9211 				child.recomputeChildLayout();
9212 			}
9213 
9214 			if(remainder is null)
9215 				return;
9216 
9217 			remainder.x = pos;
9218 			remainder.y = 0;
9219 			if(pos < this.width)
9220 				remainder.width = this.width - pos;// + 4;
9221 			else
9222 				remainder.width = 0;
9223 			remainder.height = scaleWithDpi(16);
9224 
9225 			remainder.recomputeChildLayout();
9226 		}
9227 
9228 		// for the scrollable children mixin
9229 		Point scrollOrigin() {
9230 			return Point(tvw.smw.position.x, 0);
9231 		}
9232 		void paintFrameAndBackground(WidgetPainter painter) { }
9233 
9234 		mixin ScrollableChildren;
9235 	}
9236 }
9237 
9238 /+
9239 
9240 // given struct / array / number / string / etc, make it viewable and editable
9241 class DataViewerWidget : Widget {
9242 
9243 }
9244 +/
9245 
9246 /++
9247 	A line edit box with an associated label.
9248 
9249 	History:
9250 		On May 17, 2021, the default internal layout was changed from horizontal to vertical.
9251 
9252 		```
9253 		Old: ________
9254 
9255 		New:
9256 		____________
9257 		```
9258 
9259 		To restore the old behavior, use `new LabeledLineEdit("label", TextAlignment.Right, parent);`
9260 
9261 		You can also use `new LabeledLineEdit("label", TextAlignment.Left, parent);` if you want a
9262 		horizontal label but left aligned. You may also consider a [GridLayout].
9263 +/
9264 alias LabeledLineEdit = Labeled!LineEdit;
9265 
9266 /++
9267 	History:
9268 		Added May 19, 2021
9269 +/
9270 class Labeled(T) : Widget {
9271 	///
9272 	this(string label, Widget parent) {
9273 		super(parent);
9274 		initialize!VerticalLayout(label, TextAlignment.Left, parent);
9275 	}
9276 
9277 	/++
9278 		History:
9279 			The alignment parameter was added May 17, 2021
9280 	+/
9281 	this(string label, TextAlignment alignment, Widget parent) {
9282 		super(parent);
9283 		initialize!HorizontalLayout(label, alignment, parent);
9284 	}
9285 
9286 	private void initialize(L)(string label, TextAlignment alignment, Widget parent) {
9287 		tabStop = false;
9288 		horizontal = is(L == HorizontalLayout);
9289 		auto hl = new L(this);
9290 		this.label = new TextLabel(label, alignment, hl);
9291 		this.lineEdit = new T(hl);
9292 
9293 		this.label.labelFor = this.lineEdit;
9294 	}
9295 
9296 	private bool horizontal;
9297 
9298 	TextLabel label; ///
9299 	T lineEdit; ///
9300 
9301 	override int flexBasisWidth() { return 250; }
9302 
9303 	override int minHeight() { return (horizontal ? 1 : 2) * defaultLineHeight + 4; }
9304 	override int maxHeight() { return (horizontal ? 1 : 2) * defaultLineHeight + 4; }
9305 	override int marginTop() { return 4; }
9306 	override int marginBottom() { return 4; }
9307 
9308 	// FIXME: i should prolly call it value as well as content tbh
9309 
9310 	///
9311 	@property string content() {
9312 		return lineEdit.content;
9313 	}
9314 	///
9315 	@property void content(string c) {
9316 		return lineEdit.content(c);
9317 	}
9318 
9319 	///
9320 	void selectAll() {
9321 		lineEdit.selectAll();
9322 	}
9323 
9324 	override void focus() {
9325 		lineEdit.focus();
9326 	}
9327 }
9328 
9329 /++
9330 	A labeled password edit.
9331 
9332 	History:
9333 		Added as a class on January 25, 2021, changed into an alias of the new [Labeled] template on May 19, 2021
9334 
9335 		The default parameters for the constructors were also removed on May 19, 2021
9336 +/
9337 alias LabeledPasswordEdit = Labeled!PasswordEdit;
9338 
9339 private string toMenuLabel(string s) {
9340 	string n;
9341 	n.reserve(s.length);
9342 	foreach(c; s)
9343 		if(c == '_')
9344 			n ~= ' ';
9345 		else
9346 			n ~= c;
9347 	return n;
9348 }
9349 
9350 private void autoExceptionHandler(Exception e) {
9351 	messageBox(e.msg);
9352 }
9353 
9354 private void delegate() makeAutomaticHandler(alias fn, T)(T t) {
9355 	static if(is(T : void delegate())) {
9356 		return () {
9357 			try
9358 				t();
9359 			catch(Exception e)
9360 				autoExceptionHandler(e);
9361 		};
9362 	} else static if(is(typeof(fn) Params == __parameters)) {
9363 		static if(Params.length == 1 && is(Params[0] == FileName!(member, filters, type), alias member, string[] filters, FileDialogType type)) {
9364 			return () {
9365 				void onOK(string s) {
9366 					member = s;
9367 					try
9368 						t(Params[0](s));
9369 					catch(Exception e)
9370 						autoExceptionHandler(e);
9371 				}
9372 
9373 				if(
9374 					(type == FileDialogType.Automatic && (__traits(identifier, fn).startsWith("Save") || __traits(identifier, fn).startsWith("Export")))
9375 					|| type == FileDialogType.Save)
9376 				{
9377 					getSaveFileName(&onOK, member, filters, null);
9378 				} else
9379 					getOpenFileName(&onOK, member, filters, null);
9380 			};
9381 		} else {
9382 			struct S {
9383 				static if(!__traits(compiles, mixin(`{ static foreach(i; 1..4) {} }`))) {
9384 					pragma(msg, "warning: automatic handler of params not yet implemented on your compiler");
9385 				} else mixin(q{
9386 				static foreach(idx, ignore; Params) {
9387 					mixin("Params[idx] " ~ __traits(identifier, Params[idx .. idx + 1]) ~ ";");
9388 				}
9389 				});
9390 			}
9391 			return () {
9392 				dialog((S s) {
9393 					try
9394 						cast(void) t(s.tupleof);
9395 					catch(Exception e)
9396 						autoExceptionHandler(e);
9397 				}, null, __traits(identifier, fn));
9398 			};
9399 		}
9400 	}
9401 }
9402 
9403 private template hasAnyRelevantAnnotations(a...) {
9404 	bool helper() {
9405 		bool any;
9406 		foreach(attr; a) {
9407 			static if(is(typeof(attr) == .menu))
9408 				any = true;
9409 			else static if(is(typeof(attr) == .toolbar))
9410 				any = true;
9411 			else static if(is(attr == .separator))
9412 				any = true;
9413 			else static if(is(typeof(attr) == .accelerator))
9414 				any = true;
9415 			else static if(is(typeof(attr) == .hotkey))
9416 				any = true;
9417 			else static if(is(typeof(attr) == .icon))
9418 				any = true;
9419 			else static if(is(typeof(attr) == .label))
9420 				any = true;
9421 			else static if(is(typeof(attr) == .tip))
9422 				any = true;
9423 		}
9424 		return any;
9425 	}
9426 
9427 	enum bool hasAnyRelevantAnnotations = helper();
9428 }
9429 
9430 /++
9431 	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.
9432 +/
9433 class MainWindow : Window {
9434 	///
9435 	this(string title = null, int initialWidth = 500, int initialHeight = 500) {
9436 		super(initialWidth, initialHeight, title);
9437 
9438 		_clientArea = new ClientAreaWidget();
9439 		_clientArea.x = 0;
9440 		_clientArea.y = 0;
9441 		_clientArea.width = this.width;
9442 		_clientArea.height = this.height;
9443 		_clientArea.tabStop = false;
9444 
9445 		super.addChild(_clientArea);
9446 
9447 		statusBar = new StatusBar(this);
9448 	}
9449 
9450 	/++
9451 		Adds a menu and toolbar from annotated functions.
9452 
9453 	---
9454         struct Commands {
9455                 @menu("File") {
9456                         void New() {}
9457                         void Open() {}
9458                         void Save() {}
9459                         @separator
9460                         void Exit() @accelerator("Alt+F4") @hotkey('x') {
9461                                 window.close();
9462                         }
9463                 }
9464 
9465                 @menu("Edit") {
9466                         void Undo() {
9467                                 undo();
9468                         }
9469                         @separator
9470                         void Cut() {}
9471                         void Copy() {}
9472                         void Paste() {}
9473                 }
9474 
9475                 @menu("Help") {
9476                         void About() {}
9477                 }
9478         }
9479 
9480         Commands commands;
9481 
9482         window.setMenuAndToolbarFromAnnotatedCode(commands);
9483 	---
9484 
9485 	Note that you can call this function multiple times and it will add the items in order to the given items.
9486 
9487 	+/
9488 	void setMenuAndToolbarFromAnnotatedCode(T)(ref T t) if(!is(T == class) && !is(T == interface)) {
9489 		setMenuAndToolbarFromAnnotatedCode_internal(t);
9490 	}
9491 	void setMenuAndToolbarFromAnnotatedCode(T)(T t) if(is(T == class) || is(T == interface)) {
9492 		setMenuAndToolbarFromAnnotatedCode_internal(t);
9493 	}
9494 	void setMenuAndToolbarFromAnnotatedCode_internal(T)(ref T t) {
9495 		Action[] toolbarActions;
9496 		auto menuBar = this.menuBar is null ? new MenuBar() : this.menuBar;
9497 		Menu[string] mcs;
9498 
9499 		foreach(menu; menuBar.subMenus) {
9500 			mcs[menu.label] = menu;
9501 		}
9502 
9503 		foreach(memberName; __traits(derivedMembers, T)) {
9504 			static if(memberName != "this")
9505 			static if(hasAnyRelevantAnnotations!(__traits(getAttributes, __traits(getMember, T, memberName)))) {
9506 				.menu menu;
9507 				.toolbar toolbar;
9508 				bool separator;
9509 				.accelerator accelerator;
9510 				.hotkey hotkey;
9511 				.icon icon;
9512 				string label;
9513 				string tip;
9514 				foreach(attr; __traits(getAttributes, __traits(getMember, T, memberName))) {
9515 					static if(is(typeof(attr) == .menu))
9516 						menu = attr;
9517 					else static if(is(typeof(attr) == .toolbar))
9518 						toolbar = attr;
9519 					else static if(is(attr == .separator))
9520 						separator = true;
9521 					else static if(is(typeof(attr) == .accelerator))
9522 						accelerator = attr;
9523 					else static if(is(typeof(attr) == .hotkey))
9524 						hotkey = attr;
9525 					else static if(is(typeof(attr) == .icon))
9526 						icon = attr;
9527 					else static if(is(typeof(attr) == .label))
9528 						label = attr.label;
9529 					else static if(is(typeof(attr) == .tip))
9530 						tip = attr.tip;
9531 				}
9532 
9533 				if(menu !is .menu.init || toolbar !is .toolbar.init) {
9534 					ushort correctIcon = icon.id; // FIXME
9535 					if(label.length == 0)
9536 						label = memberName.toMenuLabel;
9537 
9538 					auto handler = makeAutomaticHandler!(__traits(getMember, T, memberName))(&__traits(getMember, t, memberName));
9539 
9540 					auto action = new Action(label, correctIcon, handler);
9541 
9542 					if(accelerator.keyString.length) {
9543 						auto ke = KeyEvent.parse(accelerator.keyString);
9544 						action.accelerator = ke;
9545 						accelerators[ke.toStr] = handler;
9546 					}
9547 
9548 					if(toolbar !is .toolbar.init)
9549 						toolbarActions ~= action;
9550 					if(menu !is .menu.init) {
9551 						Menu mc;
9552 						if(menu.name in mcs) {
9553 							mc = mcs[menu.name];
9554 						} else {
9555 							mc = new Menu(menu.name, this);
9556 							menuBar.addItem(mc);
9557 							mcs[menu.name] = mc;
9558 						}
9559 
9560 						if(separator)
9561 							mc.addSeparator();
9562 						mc.addItem(new MenuItem(action));
9563 					}
9564 				}
9565 			}
9566 		}
9567 
9568 		this.menuBar = menuBar;
9569 
9570 		if(toolbarActions.length) {
9571 			auto tb = new ToolBar(toolbarActions, this);
9572 		}
9573 	}
9574 
9575 	void delegate()[string] accelerators;
9576 
9577 	override void defaultEventHandler_keydown(KeyDownEvent event) {
9578 		auto str = event.originalKeyEvent.toStr;
9579 		if(auto acl = str in accelerators)
9580 			(*acl)();
9581 		super.defaultEventHandler_keydown(event);
9582 	}
9583 
9584 	override void defaultEventHandler_mouseover(MouseOverEvent event) {
9585 		super.defaultEventHandler_mouseover(event);
9586 		if(this.statusBar !is null && event.target.statusTip.length)
9587 			this.statusBar.parts[0].content = event.target.statusTip;
9588 		else if(this.statusBar !is null && this.statusTip.length)
9589 			this.statusBar.parts[0].content = this.statusTip; // ~ " " ~ event.target.toString();
9590 	}
9591 
9592 	override void addChild(Widget c, int position = int.max) {
9593 		if(auto tb = cast(ToolBar) c)
9594 			version(win32_widgets)
9595 				super.addChild(c, 0);
9596 			else version(custom_widgets)
9597 				super.addChild(c, menuBar ? 1 : 0);
9598 			else static assert(0);
9599 		else
9600 			clientArea.addChild(c, position);
9601 	}
9602 
9603 	ToolBar _toolBar;
9604 	///
9605 	ToolBar toolBar() { return _toolBar; }
9606 	///
9607 	ToolBar toolBar(ToolBar t) {
9608 		_toolBar = t;
9609 		foreach(child; this.children)
9610 			if(child is t)
9611 				return t;
9612 		version(win32_widgets)
9613 			super.addChild(t, 0);
9614 		else version(custom_widgets)
9615 			super.addChild(t, menuBar ? 1 : 0);
9616 		else static assert(0);
9617 		return t;
9618 	}
9619 
9620 	MenuBar _menu;
9621 	///
9622 	MenuBar menuBar() { return _menu; }
9623 	///
9624 	MenuBar menuBar(MenuBar m) {
9625 		if(m is _menu) {
9626 			version(custom_widgets)
9627 				recomputeChildLayout();
9628 			return m;
9629 		}
9630 
9631 		if(_menu !is null) {
9632 			// make sure it is sanely removed
9633 			// FIXME
9634 		}
9635 
9636 		_menu = m;
9637 
9638 		version(win32_widgets) {
9639 			SetMenu(parentWindow.win.impl.hwnd, m.handle);
9640 		} else version(custom_widgets) {
9641 			super.addChild(m, 0);
9642 
9643 		//	clientArea.y = menu.height;
9644 		//	clientArea.height = this.height - menu.height;
9645 
9646 			recomputeChildLayout();
9647 		} else static assert(false);
9648 
9649 		return _menu;
9650 	}
9651 	private Widget _clientArea;
9652 	///
9653 	@property Widget clientArea() { return _clientArea; }
9654 	protected @property void clientArea(Widget wid) {
9655 		_clientArea = wid;
9656 	}
9657 
9658 	private StatusBar _statusBar;
9659 	/++
9660 		Returns the window's [StatusBar]. Be warned it may be `null`.
9661 	+/
9662 	@property StatusBar statusBar() { return _statusBar; }
9663 	/// ditto
9664 	@property void statusBar(StatusBar bar) {
9665 		if(_statusBar !is null)
9666 			_statusBar.removeWidget();
9667 		_statusBar = bar;
9668 		if(bar !is null)
9669 			super.addChild(_statusBar);
9670 	}
9671 }
9672 
9673 /+
9674 	This is really an implementation detail of [MainWindow]
9675 +/
9676 private class ClientAreaWidget : Widget {
9677 	this() {
9678 		this.tabStop = false;
9679 		super(null);
9680 		//sa = new ScrollableWidget(this);
9681 	}
9682 	/*
9683 	ScrollableWidget sa;
9684 	override void addChild(Widget w, int position) {
9685 		if(sa is null)
9686 			super.addChild(w, position);
9687 		else {
9688 			sa.addChild(w, position);
9689 			sa.setContentSize(this.minWidth + 1, this.minHeight);
9690 			import std.stdio; writeln(sa.contentWidth, "x", sa.contentHeight);
9691 		}
9692 	}
9693 	*/
9694 }
9695 
9696 /**
9697 	Toolbars are lists of buttons (typically icons) that appear under the menu.
9698 	Each button ought to correspond to a menu item, represented by [Action] objects.
9699 */
9700 class ToolBar : Widget {
9701 	version(win32_widgets) {
9702 		private const int idealHeight;
9703 		override int minHeight() { return idealHeight; }
9704 		override int maxHeight() { return idealHeight; }
9705 	} else version(custom_widgets) {
9706 		override int minHeight() { return toolbarIconSize; }// defaultLineHeight * 3/2; }
9707 		override int maxHeight() { return toolbarIconSize; } //defaultLineHeight * 3/2; }
9708 	} else static assert(false);
9709 	override int heightStretchiness() { return 0; }
9710 
9711 	version(win32_widgets) 
9712 		HIMAGELIST imageList;
9713 
9714 	this(Widget parent) {
9715 		this(null, parent);
9716 	}
9717 
9718 	///
9719 	this(Action[] actions, Widget parent) {
9720 		super(parent);
9721 
9722 		tabStop = false;
9723 
9724 		version(win32_widgets) {
9725 			// so i like how the flat thing looks on windows, but not on wine
9726 			// and eh, with windows visual styles enabled it looks cool anyway soooo gonna
9727 			// leave it commented
9728 			createWin32Window(this, "ToolbarWindow32"w, "", TBSTYLE_LIST|/*TBSTYLE_FLAT|*/TBSTYLE_TOOLTIPS);
9729 			
9730 			SendMessageW(hwnd, TB_SETEXTENDEDSTYLE, 0, 8/*TBSTYLE_EX_MIXEDBUTTONS*/);
9731 
9732 			imageList = ImageList_Create(
9733 				// width, height
9734 				16, 16,
9735 				ILC_COLOR16 | ILC_MASK,
9736 				16 /*numberOfButtons*/, 0);
9737 
9738 			SendMessageW(hwnd, TB_SETIMAGELIST, cast(WPARAM) 0, cast(LPARAM) imageList);
9739 			SendMessageW(hwnd, TB_LOADIMAGES, cast(WPARAM) IDB_STD_SMALL_COLOR, cast(LPARAM) HINST_COMMCTRL);
9740 			SendMessageW(hwnd, TB_SETMAXTEXTROWS, 0, 0);
9741 			SendMessageW(hwnd, TB_AUTOSIZE, 0, 0);
9742 
9743 			TBBUTTON[] buttons;
9744 
9745 			// FIXME: I_IMAGENONE is if here is no icon
9746 			foreach(action; actions)
9747 				buttons ~= TBBUTTON(
9748 					MAKELONG(cast(ushort)(action.iconId ? (action.iconId - 1) : -2 /* I_IMAGENONE */), 0),
9749 					action.id,
9750 					TBSTATE_ENABLED, // state
9751 					0, // style
9752 					0, // reserved array, just zero it out
9753 					0, // dwData
9754 					cast(size_t) toWstringzInternal(action.label) // INT_PTR
9755 				);
9756 
9757 			SendMessageW(hwnd, TB_BUTTONSTRUCTSIZE, cast(WPARAM)TBBUTTON.sizeof, 0);
9758 			SendMessageW(hwnd, TB_ADDBUTTONSW, cast(WPARAM) buttons.length, cast(LPARAM)buttons.ptr);
9759 
9760 			SIZE size;
9761 			import core.sys.windows.commctrl;
9762 			SendMessageW(hwnd, TB_GETMAXSIZE, 0, cast(LPARAM) &size);
9763 			idealHeight = size.cy + 4; // the plus 4 is a hack
9764 
9765 			/*
9766 			RECT rect;
9767 			GetWindowRect(hwnd, &rect);
9768 			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
9769 			*/
9770 
9771 			assert(idealHeight);
9772 		} else version(custom_widgets) {
9773 			foreach(action; actions)
9774 				new ToolButton(action, this);
9775 		} else static assert(false);
9776 	}
9777 
9778 	override void recomputeChildLayout() {
9779 		.recomputeChildLayout!"width"(this);
9780 	}
9781 }
9782 
9783 enum toolbarIconSize = 24;
9784 
9785 /// 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.
9786 class ToolButton : Button {
9787 	///
9788 	this(string label, Widget parent) {
9789 		super(label, parent);
9790 		tabStop = false;
9791 	}
9792 	///
9793 	this(Action action, Widget parent) {
9794 		super(action.label, parent);
9795 		tabStop = false;
9796 		this.action = action;
9797 	}
9798 
9799 	version(custom_widgets)
9800 	override void defaultEventHandler_click(ClickEvent event) {
9801 		foreach(handler; action.triggered)
9802 			handler();
9803 	}
9804 
9805 	Action action;
9806 
9807 	override int maxWidth() { return toolbarIconSize; }
9808 	override int minWidth() { return toolbarIconSize; }
9809 	override int maxHeight() { return toolbarIconSize; }
9810 	override int minHeight() { return toolbarIconSize; }
9811 
9812 	version(custom_widgets)
9813 	override void paint(WidgetPainter painter) {
9814 	painter.drawThemed(delegate Rectangle (const Rectangle bounds) {
9815 		painter.outlineColor = Color.black;
9816 
9817 		// I want to get from 16 to 24. that's * 3 / 2
9818 		static assert(toolbarIconSize >= 16);
9819 		enum multiplier = toolbarIconSize / 8;
9820 		enum divisor = 2 + ((toolbarIconSize % 8) ? 1 : 0);
9821 		switch(action.iconId) {
9822 			case GenericIcons.New:
9823 				painter.fillColor = Color.white;
9824 				painter.drawPolygon(
9825 					Point(3, 2) * multiplier / divisor, Point(3, 13) * multiplier / divisor, Point(12, 13) * multiplier / divisor, Point(12, 6) * multiplier / divisor,
9826 					Point(8, 2) * multiplier / divisor, Point(8, 6) * multiplier / divisor, Point(12, 6) * multiplier / divisor, Point(8, 2) * multiplier / divisor,
9827 					Point(3, 2) * multiplier / divisor, Point(3, 13) * multiplier / divisor
9828 				);
9829 			break;
9830 			case GenericIcons.Save:
9831 				painter.fillColor = Color.white;
9832 				painter.outlineColor = Color.black;
9833 				painter.drawRectangle(Point(2, 2) * multiplier / divisor, Point(13, 13) * multiplier / divisor);
9834 
9835 				// the label
9836 				painter.drawRectangle(Point(4, 8) * multiplier / divisor, Point(11, 13) * multiplier / divisor);
9837 
9838 				// the slider
9839 				painter.fillColor = Color.black;
9840 				painter.outlineColor = Color.black;
9841 				painter.drawRectangle(Point(4, 3) * multiplier / divisor, Point(10, 6) * multiplier / divisor);
9842 
9843 				painter.fillColor = Color.white;
9844 				painter.outlineColor = Color.white;
9845 				// the disc window
9846 				painter.drawRectangle(Point(5, 3) * multiplier / divisor, Point(6, 5) * multiplier / divisor);
9847 			break;
9848 			case GenericIcons.Open:
9849 				painter.fillColor = Color.white;
9850 				painter.drawPolygon(
9851 					Point(4, 4) * multiplier / divisor, Point(4, 12) * multiplier / divisor, Point(13, 12) * multiplier / divisor, Point(13, 3) * multiplier / divisor,
9852 					Point(9, 3) * multiplier / divisor, Point(9, 4) * multiplier / divisor, Point(4, 4) * multiplier / divisor);
9853 				painter.drawPolygon(
9854 					Point(2, 6) * multiplier / divisor, Point(11, 6) * multiplier / divisor,
9855 					Point(12, 12) * multiplier / divisor, Point(4, 12) * multiplier / divisor,
9856 					Point(2, 6) * multiplier / divisor);
9857 				//painter.drawLine(Point(9, 6) * multiplier / divisor, Point(13, 7) * multiplier / divisor);
9858 			break;
9859 			case GenericIcons.Copy:
9860 				painter.fillColor = Color.white;
9861 				painter.drawRectangle(Point(3, 2) * multiplier / divisor, Point(9, 10) * multiplier / divisor);
9862 				painter.drawRectangle(Point(6, 5) * multiplier / divisor, Point(12, 13) * multiplier / divisor);
9863 			break;
9864 			case GenericIcons.Cut:
9865 				painter.fillColor = Color.transparent;
9866 				painter.outlineColor = getComputedStyle.foregroundColor();
9867 				painter.drawLine(Point(3, 2) * multiplier / divisor, Point(10, 9) * multiplier / divisor);
9868 				painter.drawLine(Point(4, 9) * multiplier / divisor, Point(11, 2) * multiplier / divisor);
9869 				painter.drawRectangle(Point(3, 9) * multiplier / divisor, Point(5, 13) * multiplier / divisor);
9870 				painter.drawRectangle(Point(9, 9) * multiplier / divisor, Point(11, 12) * multiplier / divisor);
9871 			break;
9872 			case GenericIcons.Paste:
9873 				painter.fillColor = Color.white;
9874 				painter.drawRectangle(Point(2, 3) * multiplier / divisor, Point(11, 11) * multiplier / divisor);
9875 				painter.drawRectangle(Point(6, 8) * multiplier / divisor, Point(13, 13) * multiplier / divisor);
9876 				painter.drawLine(Point(6, 2) * multiplier / divisor, Point(4, 5) * multiplier / divisor);
9877 				painter.drawLine(Point(6, 2) * multiplier / divisor, Point(9, 5) * multiplier / divisor);
9878 				painter.fillColor = Color.black;
9879 				painter.drawRectangle(Point(4, 5) * multiplier / divisor, Point(9, 6) * multiplier / divisor);
9880 			break;
9881 			case GenericIcons.Help:
9882 				painter.outlineColor = getComputedStyle.foregroundColor();
9883 				painter.drawText(Point(0, 0), "?", Point(width, height), TextAlignment.Center | TextAlignment.VerticalCenter);
9884 			break;
9885 			case GenericIcons.Undo:
9886 				painter.fillColor = Color.transparent;
9887 				painter.drawArc(Point(3, 4) * multiplier / divisor, 9 * multiplier / divisor, 9 * multiplier / divisor, 0, 360 * 64);
9888 				painter.outlineColor = Color.black;
9889 				painter.fillColor = Color.black;
9890 				painter.drawPolygon(
9891 					Point(4, 4) * multiplier / divisor,
9892 					Point(8, 2) * multiplier / divisor,
9893 					Point(8, 6) * multiplier / divisor,
9894 					Point(4, 4) * multiplier / divisor,
9895 				);
9896 			break;
9897 			case GenericIcons.Redo:
9898 				painter.fillColor = Color.transparent;
9899 				painter.drawArc(Point(3, 4) * multiplier / divisor, 9 * multiplier / divisor, 9 * multiplier / divisor, 0, 360 * 64);
9900 				painter.outlineColor = Color.black;
9901 				painter.fillColor = Color.black;
9902 				painter.drawPolygon(
9903 					Point(10, 4) * multiplier / divisor,
9904 					Point(6, 2) * multiplier / divisor,
9905 					Point(6, 6) * multiplier / divisor,
9906 					Point(10, 4) * multiplier / divisor,
9907 				);
9908 			break;
9909 			default:
9910 				painter.drawText(Point(0, 0), action.label, Point(width, height), TextAlignment.Center | TextAlignment.VerticalCenter);
9911 		}
9912 		return bounds;
9913 		});
9914 	}
9915 
9916 }
9917 
9918 
9919 ///
9920 class MenuBar : Widget {
9921 	MenuItem[] items;
9922 	Menu[] subMenus;
9923 
9924 	version(win32_widgets) {
9925 		HMENU handle;
9926 		///
9927 		this(Widget parent = null) {
9928 			super(parent);
9929 
9930 			handle = CreateMenu();
9931 			tabStop = false;
9932 		}
9933 	} else version(custom_widgets) {
9934 		///
9935 		this(Widget parent = null) {
9936 			tabStop = false; // these are selected some other way
9937 			super(parent);
9938 		}
9939 
9940 		mixin Padding!q{2};
9941 	} else static assert(false);
9942 
9943 	version(custom_widgets)
9944 	override void paint(WidgetPainter painter) {
9945 		draw3dFrame(this, painter, FrameStyle.risen, getComputedStyle().background.color);
9946 	}
9947 
9948 	///
9949 	MenuItem addItem(MenuItem item) {
9950 		this.addChild(item);
9951 		items ~= item;
9952 		version(win32_widgets) {
9953 			AppendMenuW(handle, MF_STRING, item.action is null ? 9000 : item.action.id, toWstringzInternal(item.label));
9954 		}
9955 		return item;
9956 	}
9957 
9958 
9959 	///
9960 	Menu addItem(Menu item) {
9961 
9962 		subMenus ~= item;
9963 
9964 		auto mbItem = new MenuItem(item.label, null);// this.parentWindow); // I'ma add the child down below so hopefully this isn't too insane
9965 
9966 		addChild(mbItem);
9967 		items ~= mbItem;
9968 
9969 		version(win32_widgets) {
9970 			AppendMenuW(handle, MF_STRING | MF_POPUP, cast(UINT) item.handle, toWstringzInternal(item.label));
9971 		} else version(custom_widgets) {
9972 			mbItem.defaultEventHandlers["mousedown"] = (Widget e, Event ev) {
9973 				item.popup(mbItem);
9974 			};
9975 		} else static assert(false);
9976 
9977 		return item;
9978 	}
9979 
9980 	override void recomputeChildLayout() {
9981 		.recomputeChildLayout!"width"(this);
9982 	}
9983 
9984 	override int maxHeight() { return defaultLineHeight + 4; }
9985 	override int minHeight() { return defaultLineHeight + 4; }
9986 }
9987 
9988 
9989 /**
9990 	Status bars appear at the bottom of a MainWindow.
9991 	They are made out of Parts, with a width and content.
9992 
9993 	They can have multiple parts or be in simple mode. FIXME: implement simple mode.
9994 
9995 
9996 	sb.parts[0].content = "Status bar text!";
9997 */
9998 class StatusBar : Widget {
9999 	private Part[] partsArray;
10000 	///
10001 	struct Parts {
10002 		@disable this();
10003 		this(StatusBar owner) { this.owner = owner; }
10004 		//@disable this(this);
10005 		///
10006 		@property int length() { return cast(int) owner.partsArray.length; }
10007 		private StatusBar owner;
10008 		private this(StatusBar owner, Part[] parts) {
10009 			this.owner.partsArray = parts;
10010 			this.owner = owner;
10011 		}
10012 		///
10013 		Part opIndex(int p) {
10014 			if(owner.partsArray.length == 0)
10015 				this ~= new StatusBar.Part(300);
10016 			return owner.partsArray[p];
10017 		}
10018 
10019 		///
10020 		Part opOpAssign(string op : "~" )(Part p) {
10021 			assert(owner.partsArray.length < 255);
10022 			p.owner = this.owner;
10023 			p.idx = cast(int) owner.partsArray.length;
10024 			owner.partsArray ~= p;
10025 			version(win32_widgets) {
10026 				int[256] pos;
10027 				int cpos = 0;
10028 				foreach(idx, part; owner.partsArray) {
10029 					if(part.width)
10030 						cpos += part.width;
10031 					else
10032 						cpos += 100;
10033 
10034 					if(idx + 1 == owner.partsArray.length)
10035 						pos[idx] = -1;
10036 					else
10037 						pos[idx] = cpos;
10038 				}
10039 				SendMessageW(owner.hwnd, WM_USER + 4 /*SB_SETPARTS*/, owner.partsArray.length, cast(size_t) pos.ptr);
10040 			} else version(custom_widgets) {
10041 				owner.redraw();
10042 			} else static assert(false);
10043 
10044 			return p;
10045 		}
10046 	}
10047 
10048 	private Parts _parts;
10049 	///
10050 	final @property Parts parts() {
10051 		return _parts;
10052 	}
10053 
10054 	///
10055 	static class Part {
10056 		int width;
10057 		StatusBar owner;
10058 
10059 		///
10060 		this(int w = 100) { width = w; }
10061 
10062 		private int idx;
10063 		private string _content;
10064 		///
10065 		@property string content() { return _content; }
10066 		///
10067 		@property void content(string s) {
10068 			version(win32_widgets) {
10069 				_content = s;
10070 				WCharzBuffer bfr = WCharzBuffer(s);
10071 				SendMessageW(owner.hwnd, SB_SETTEXT, idx, cast(LPARAM) bfr.ptr);
10072 			} else version(custom_widgets) {
10073 				if(_content != s) {
10074 					_content = s;
10075 					owner.redraw();
10076 				}
10077 			} else static assert(false);
10078 		}
10079 	}
10080 	string simpleModeContent;
10081 	bool inSimpleMode;
10082 
10083 
10084 	///
10085 	this(Widget parent) {
10086 		super(null); // FIXME
10087 		_parts = Parts(this);
10088 		tabStop = false;
10089 		version(win32_widgets) {
10090 			parentWindow = parent.parentWindow;
10091 			createWin32Window(this, "msctls_statusbar32"w, "", 0);
10092 
10093 			RECT rect;
10094 			GetWindowRect(hwnd, &rect);
10095 			idealHeight = rect.bottom - rect.top;
10096 			assert(idealHeight);
10097 		} else version(custom_widgets) {
10098 		} else static assert(false);
10099 	}
10100 
10101 	version(win32_widgets)
10102 	override protected void dpiChanged() {
10103 		RECT rect;
10104 		GetWindowRect(hwnd, &rect);
10105 		idealHeight = rect.bottom - rect.top;
10106 		assert(idealHeight);
10107 	}
10108 
10109 	version(custom_widgets)
10110 	override void paint(WidgetPainter painter) {
10111 		auto cs = getComputedStyle();
10112 		this.draw3dFrame(painter, FrameStyle.sunk, cs.background.color);
10113 		int cpos = 0;
10114 		int remainingLength = this.width;
10115 		foreach(idx, part; this.partsArray) {
10116 			auto partWidth = part.width ? part.width : ((idx + 1 == this.partsArray.length) ? remainingLength : 100);
10117 			painter.setClipRectangle(Point(cpos, 0), partWidth, height);
10118 			draw3dFrame(cpos, 0, partWidth, height, painter, FrameStyle.sunk, cs.background.color);
10119 			painter.setClipRectangle(Point(cpos + 2, 2), partWidth - 4, height - 4);
10120 
10121 			painter.outlineColor = cs.foregroundColor();
10122 			painter.fillColor = cs.foregroundColor();
10123 
10124 			painter.drawText(Point(cpos + 4, 0), part.content, Point(width, height), TextAlignment.VerticalCenter);
10125 			cpos += partWidth;
10126 			remainingLength -= partWidth;
10127 		}
10128 	}
10129 
10130 
10131 	version(win32_widgets) {
10132 		private int idealHeight;
10133 		override int maxHeight() { return idealHeight; }
10134 		override int minHeight() { return idealHeight; }
10135 	} else version(custom_widgets) {
10136 		override int maxHeight() { return defaultLineHeight + 4; }
10137 		override int minHeight() { return defaultLineHeight + 4; }
10138 	} else static assert(false);
10139 }
10140 
10141 /// Displays an in-progress indicator without known values
10142 version(none)
10143 class IndefiniteProgressBar : Widget {
10144 	version(win32_widgets)
10145 	this(Widget parent) {
10146 		super(parent);
10147 		createWin32Window(this, "msctls_progress32"w, "", 8 /* PBS_MARQUEE */);
10148 		tabStop = false;
10149 	}
10150 	override int minHeight() { return 10; }
10151 }
10152 
10153 /// A progress bar with a known endpoint and completion amount
10154 class ProgressBar : Widget {
10155 	/++
10156 		History:
10157 			Added March 16, 2022 (dub v10.7)
10158 	+/
10159 	this(int min, int max, Widget parent) {
10160 		this(parent);
10161 		setRange(cast(ushort) min, cast(ushort) max); // FIXME
10162 	}
10163 	this(Widget parent) {
10164 		version(win32_widgets) {
10165 			super(parent);
10166 			createWin32Window(this, "msctls_progress32"w, "", 0);
10167 			tabStop = false;
10168 		} else version(custom_widgets) {
10169 			super(parent);
10170 			max = 100;
10171 			step = 10;
10172 			tabStop = false;
10173 		} else static assert(0);
10174 	}
10175 
10176 	version(custom_widgets)
10177 	override void paint(WidgetPainter painter) {
10178 		auto cs = getComputedStyle();
10179 		this.draw3dFrame(painter, FrameStyle.sunk, cs.background.color);
10180 		painter.fillColor = cs.progressBarColor;
10181 		painter.drawRectangle(Point(0, 0), width * current / max, height);
10182 	}
10183 
10184 
10185 	version(custom_widgets) {
10186 		int current;
10187 		int max;
10188 		int step;
10189 	}
10190 
10191 	///
10192 	void advanceOneStep() {
10193 		version(win32_widgets)
10194 			SendMessageW(hwnd, PBM_STEPIT, 0, 0);
10195 		else version(custom_widgets)
10196 			addToPosition(step);
10197 		else static assert(false);
10198 	}
10199 
10200 	///
10201 	void setStepIncrement(int increment) {
10202 		version(win32_widgets)
10203 			SendMessageW(hwnd, PBM_SETSTEP, increment, 0);
10204 		else version(custom_widgets)
10205 			step = increment;
10206 		else static assert(false);
10207 	}
10208 
10209 	///
10210 	void addToPosition(int amount) {
10211 		version(win32_widgets)
10212 			SendMessageW(hwnd, PBM_DELTAPOS, amount, 0);
10213 		else version(custom_widgets)
10214 			setPosition(current + amount);
10215 		else static assert(false);
10216 	}
10217 
10218 	///
10219 	void setPosition(int pos) {
10220 		version(win32_widgets)
10221 			SendMessageW(hwnd, PBM_SETPOS, pos, 0);
10222 		else version(custom_widgets) {
10223 			current = pos;
10224 			if(current > max)
10225 				current = max;
10226 			redraw();
10227 		}
10228 		else static assert(false);
10229 	}
10230 
10231 	///
10232 	void setRange(ushort min, ushort max) {
10233 		version(win32_widgets)
10234 			SendMessageW(hwnd, PBM_SETRANGE, 0, MAKELONG(min, max));
10235 		else version(custom_widgets) {
10236 			this.max = max;
10237 		}
10238 		else static assert(false);
10239 	}
10240 
10241 	override int minHeight() { return 10; }
10242 }
10243 
10244 version(custom_widgets)
10245 private void extractWindowsStyleLabel(scope const char[] label, out string thisLabel, out dchar thisAccelerator) {
10246 	thisLabel.reserve(label.length);
10247 	bool justSawAmpersand;
10248 	foreach(ch; label) {
10249 		if(justSawAmpersand) {
10250 			justSawAmpersand = false;
10251 			if(ch == '&') {
10252 				goto plain;
10253 			}
10254 			thisAccelerator = ch;
10255 		} else {
10256 			if(ch == '&') {
10257 				justSawAmpersand = true;
10258 				continue;
10259 			}
10260 			plain:
10261 			thisLabel ~= ch;
10262 		}
10263 	}
10264 }
10265 
10266 /++
10267 	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.
10268 
10269 
10270 	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
10271 
10272 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
10273 
10274 	History:
10275 		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.
10276 +/
10277 class Fieldset : Widget {
10278 	// FIXME: on Windows,it doesn't draw the background on the label
10279 	// on X, it doesn't fix the clipping rectangle for it
10280 	version(win32_widgets)
10281 		override int paddingTop() { return defaultLineHeight; }
10282 	else version(custom_widgets)
10283 		override int paddingTop() { return defaultLineHeight + 2; }
10284 	else static assert(false);
10285 	override int paddingBottom() { return 6; }
10286 	override int paddingLeft() { return 6; }
10287 	override int paddingRight() { return 6; }
10288 
10289 	override int marginLeft() { return 6; }
10290 	override int marginRight() { return 6; }
10291 	override int marginTop() { return 2; }
10292 	override int marginBottom() { return 2; }
10293 
10294 	string legend;
10295 
10296 	version(custom_widgets) private dchar accelerator;
10297 
10298 	this(string legend, Widget parent) {
10299 		version(win32_widgets) {
10300 			super(parent);
10301 			this.legend = legend;
10302 			createWin32Window(this, "button"w, legend, BS_GROUPBOX);
10303 			tabStop = false;
10304 		} else version(custom_widgets) {
10305 			super(parent);
10306 			tabStop = false;
10307 
10308 			legend.extractWindowsStyleLabel(this.legend, this.accelerator);
10309 		} else static assert(0);
10310 	}
10311 
10312 	version(custom_widgets)
10313 	override void paint(WidgetPainter painter) {
10314 		painter.fillColor = Color.transparent;
10315 		auto cs = getComputedStyle();
10316 		painter.pen = Pen(cs.foregroundColor, 1);
10317 		painter.drawRectangle(Point(0, defaultLineHeight / 2), width, height - Window.lineHeight / 2);
10318 
10319 		auto tx = painter.textSize(legend);
10320 		painter.outlineColor = Color.transparent;
10321 
10322 		static if(UsingSimpledisplayX11) {
10323 			painter.fillColor = getComputedStyle().windowBackgroundColor;
10324 			painter.drawRectangle(Point(8, 0), tx.width, tx.height);
10325 		} else version(Windows) {
10326 			auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
10327 			painter.drawRectangle(Point(8, -tx.height/2), tx.width, tx.height);
10328 			SelectObject(painter.impl.hdc, b);
10329 		} else static assert(0);
10330 		painter.outlineColor = cs.foregroundColor;
10331 		painter.drawText(Point(8, 0), legend);
10332 	}
10333 
10334 	override int maxHeight() {
10335 		auto m = paddingTop() + paddingBottom();
10336 		foreach(child; children) {
10337 			auto mh = child.maxHeight();
10338 			if(mh == int.max)
10339 				return int.max;
10340 			m += mh;
10341 			m += child.marginBottom();
10342 			m += child.marginTop();
10343 		}
10344 		m += 6;
10345 		if(m < minHeight)
10346 			return minHeight;
10347 		return m;
10348 	}
10349 
10350 	override int minHeight() {
10351 		auto m = paddingTop() + paddingBottom();
10352 		foreach(child; children) {
10353 			m += child.minHeight();
10354 			m += child.marginBottom();
10355 			m += child.marginTop();
10356 		}
10357 		return m + 6;
10358 	}
10359 }
10360 
10361 /++
10362 	$(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")
10363 	$(IMG //arsdnet.net/minigui-screenshots/linux/Fieldset.png, Same thing, but in the default Linux theme.)
10364 +/
10365 version(minigui_screenshots)
10366 @Screenshot("Fieldset")
10367 unittest {
10368 	auto window = new Window(200, 100);
10369 	auto set = new Fieldset("Baby will", window);
10370 	auto option1 = new Radiobox("Eat", set);
10371 	auto option2 = new Radiobox("Cry", set);
10372 	auto option3 = new Radiobox("Sleep", set);
10373 	window.loop();
10374 }
10375 
10376 /// Draws a line
10377 class HorizontalRule : Widget {
10378 	mixin Margin!q{ 2 };
10379 	override int minHeight() { return 2; }
10380 	override int maxHeight() { return 2; }
10381 
10382 	///
10383 	this(Widget parent) {
10384 		super(parent);
10385 	}
10386 
10387 	override void paint(WidgetPainter painter) {
10388 		auto cs = getComputedStyle();
10389 		painter.outlineColor = cs.darkAccentColor;
10390 		painter.drawLine(Point(0, 0), Point(width, 0));
10391 		painter.outlineColor = cs.lightAccentColor;
10392 		painter.drawLine(Point(0, 1), Point(width, 1));
10393 	}
10394 }
10395 
10396 version(minigui_screenshots)
10397 @Screenshot("HorizontalRule")
10398 /++
10399 	$(IMG //arsdnet.net/minigui-screenshots/linux/HorizontalRule.png, Same thing, but in the default Linux theme.)
10400 
10401 +/
10402 unittest {
10403 	auto window = new Window(200, 100);
10404 	auto above = new TextLabel("Above the line", TextAlignment.Left, window);
10405 	new HorizontalRule(window);
10406 	auto below = new TextLabel("Below the line", TextAlignment.Left, window);
10407 	window.loop();
10408 }
10409 
10410 /// ditto
10411 class VerticalRule : Widget {
10412 	mixin Margin!q{ 2 };
10413 	override int minWidth() { return 2; }
10414 	override int maxWidth() { return 2; }
10415 
10416 	///
10417 	this(Widget parent) {
10418 		super(parent);
10419 	}
10420 
10421 	override void paint(WidgetPainter painter) {
10422 		auto cs = getComputedStyle();
10423 		painter.outlineColor = cs.darkAccentColor;
10424 		painter.drawLine(Point(0, 0), Point(0, height));
10425 		painter.outlineColor = cs.lightAccentColor;
10426 		painter.drawLine(Point(1, 0), Point(1, height));
10427 	}
10428 }
10429 
10430 
10431 ///
10432 class Menu : Window {
10433 	void remove() {
10434 		foreach(i, child; parentWindow.children)
10435 			if(child is this) {
10436 				parentWindow._children = parentWindow._children[0 .. i] ~ parentWindow._children[i + 1 .. $];
10437 				break;
10438 			}
10439 		parentWindow.redraw();
10440 
10441 		parentWindow.releaseMouseCapture();
10442 	}
10443 
10444 	///
10445 	void addSeparator() {
10446 		version(win32_widgets)
10447 			AppendMenu(handle, MF_SEPARATOR, 0, null);
10448 		else version(custom_widgets)
10449 			auto hr = new HorizontalRule(this);
10450 		else static assert(0);
10451 	}
10452 
10453 	override int paddingTop() { return 4; }
10454 	override int paddingBottom() { return 4; }
10455 	override int paddingLeft() { return 2; }
10456 	override int paddingRight() { return 2; }
10457 
10458 	version(win32_widgets) {}
10459 	else version(custom_widgets) {
10460 		SimpleWindow dropDown;
10461 		Widget menuParent;
10462 		void popup(Widget parent, int offsetX = 0, int offsetY = int.min) {
10463 			this.menuParent = parent;
10464 
10465 			int w = 150;
10466 			int h = paddingTop + paddingBottom;
10467 			if(this.children.length) {
10468 				// hacking it to get the ideal height out of recomputeChildLayout
10469 				this.width = w;
10470 				this.height = h;
10471 				this.recomputeChildLayout();
10472 				h = this.children[$-1].y + this.children[$-1].height + this.children[$-1].marginBottom;
10473 				h += paddingBottom;
10474 
10475 				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
10476 			}
10477 
10478 			if(offsetY == int.min)
10479 				offsetY = parent.defaultLineHeight;
10480 
10481 			auto coord = parent.globalCoordinates();
10482 			dropDown.moveResize(coord.x + offsetX, coord.y + offsetY, w, h);
10483 			this.x = 0;
10484 			this.y = 0;
10485 			this.width = dropDown.width;
10486 			this.height = dropDown.height;
10487 			this.drawableWindow = dropDown;
10488 			this.recomputeChildLayout();
10489 
10490 			static if(UsingSimpledisplayX11)
10491 				XSync(XDisplayConnection.get, 0);
10492 
10493 			dropDown.visibilityChanged = (bool visible) {
10494 				if(visible) {
10495 					this.redraw();
10496 					dropDown.grabInput();
10497 				} else {
10498 					dropDown.releaseInputGrab();
10499 				}
10500 			};
10501 
10502 			dropDown.show();
10503 
10504 			clickListener = this.addEventListener((scope ClickEvent ev) {
10505 				unpopup();
10506 				// need to unlock asap just in case other user handlers block...
10507 				static if(UsingSimpledisplayX11)
10508 					flushGui();
10509 			}, true /* again for asap action */);
10510 		}
10511 
10512 		EventListener clickListener;
10513 	}
10514 	else static assert(false);
10515 
10516 	version(custom_widgets)
10517 	void unpopup() {
10518 		mouseLastOver = mouseLastDownOn = null;
10519 		dropDown.hide();
10520 		if(!menuParent.parentWindow.win.closed) {
10521 			if(auto maw = cast(MouseActivatedWidget) menuParent) {
10522 				maw.setDynamicState(DynamicState.depressed, false);
10523 				maw.setDynamicState(DynamicState.hover, false);
10524 				maw.redraw();
10525 			}
10526 			// menuParent.parentWindow.win.focus();
10527 		}
10528 		clickListener.disconnect();
10529 	}
10530 
10531 	MenuItem[] items;
10532 
10533 	///
10534 	MenuItem addItem(MenuItem item) {
10535 		addChild(item);
10536 		items ~= item;
10537 		version(win32_widgets) {
10538 			AppendMenuW(handle, MF_STRING, item.action is null ? 9000 : item.action.id, toWstringzInternal(item.label));
10539 		}
10540 		return item;
10541 	}
10542 
10543 	string label;
10544 
10545 	version(win32_widgets) {
10546 		HMENU handle;
10547 		///
10548 		this(string label, Widget parent) {
10549 			// not actually passing the parent since it effs up the drawing
10550 			super(cast(Widget) null);// parent);
10551 			this.label = label;
10552 			handle = CreatePopupMenu();
10553 		}
10554 	} else version(custom_widgets) {
10555 		///
10556 		this(string label, Widget parent) {
10557 
10558 			if(dropDown) {
10559 				dropDown.close();
10560 			}
10561 			dropDown = new SimpleWindow(
10562 				150, 4,
10563 				null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.dropdownMenu, WindowFlags.dontAutoShow, parent ? parent.parentWindow.win : null);
10564 
10565 			this.label = label;
10566 
10567 			super(dropDown);
10568 		}
10569 	} else static assert(false);
10570 
10571 	override int maxHeight() { return defaultLineHeight; }
10572 	override int minHeight() { return defaultLineHeight; }
10573 
10574 	version(custom_widgets)
10575 	override void paint(WidgetPainter painter) {
10576 		this.draw3dFrame(painter, FrameStyle.risen, getComputedStyle.background.color);
10577 	}
10578 }
10579 
10580 /++
10581 	A MenuItem belongs to a [Menu] - use [Menu.addItem] to add one - and calls an [Action] when it is clicked.
10582 +/
10583 class MenuItem : MouseActivatedWidget {
10584 	Menu submenu;
10585 
10586 	Action action;
10587 	string label;
10588 
10589 	override int paddingLeft() { return 4; }
10590 
10591 	override int maxHeight() { return defaultLineHeight + 4; }
10592 	override int minHeight() { return defaultLineHeight + 4; }
10593 	override int minWidth() { return defaultTextWidth(label) + 8 + scaleWithDpi(12); }
10594 	override int maxWidth() {
10595 		if(cast(MenuBar) parent) {
10596 			return minWidth();
10597 		}
10598 		return int.max;
10599 	}
10600 	/// This should ONLY be used if there is no associated action, for example, if the menu item is just a submenu.
10601 	this(string lbl, Widget parent = null) {
10602 		super(parent);
10603 		//label = lbl; // FIXME
10604 		foreach(char ch; lbl) // FIXME
10605 			if(ch != '&') // FIXME
10606 				label ~= ch; // FIXME
10607 		tabStop = false; // these are selected some other way
10608 	}
10609 
10610 	///
10611 	this(Action action, Widget parent = null) {
10612 		assert(action !is null);
10613 		this(action.label, parent);
10614 		this.action = action;
10615 		tabStop = false; // these are selected some other way
10616 	}
10617 
10618 	version(custom_widgets)
10619 	override void paint(WidgetPainter painter) {
10620 		auto cs = getComputedStyle();
10621 		if(dynamicState & DynamicState.depressed)
10622 			this.draw3dFrame(painter, FrameStyle.sunk, cs.background.color);
10623 		if(dynamicState & DynamicState.hover)
10624 			painter.outlineColor = cs.activeMenuItemColor;
10625 		else
10626 			painter.outlineColor = cs.foregroundColor;
10627 		painter.fillColor = Color.transparent;
10628 		painter.drawText(scaleWithDpi(Point(cast(MenuBar) this.parent ? 4 : 20, 0)), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
10629 		if(action && action.accelerator !is KeyEvent.init) {
10630 			painter.drawText(scaleWithDpi(Point(cast(MenuBar) this.parent ? 4 : 20, 0)), action.accelerator.toStr(), Point(width - 4, height), TextAlignment.Right | TextAlignment.VerticalCenter);
10631 
10632 		}
10633 	}
10634 
10635 	static class Style : Widget.Style {
10636 		override bool variesWithState(ulong dynamicStateFlags) {
10637 			return super.variesWithState(dynamicStateFlags) || (dynamicStateFlags & (DynamicState.depressed | DynamicState.hover));
10638 		}
10639 	}
10640 	mixin OverrideStyle!Style;
10641 
10642 	override void defaultEventHandler_triggered(Event event) {
10643 		if(action)
10644 		foreach(handler; action.triggered)
10645 			handler();
10646 
10647 		if(auto pmenu = cast(Menu) this.parent)
10648 			pmenu.remove();
10649 
10650 		super.defaultEventHandler_triggered(event);
10651 	}
10652 }
10653 
10654 version(win32_widgets)
10655 /// A "mouse activiated widget" is really just an abstract variant of button.
10656 class MouseActivatedWidget : Widget {
10657 	@property bool isChecked() {
10658 		assert(hwnd);
10659 		return SendMessageW(hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED;
10660 
10661 	}
10662 	@property void isChecked(bool state) {
10663 		assert(hwnd);
10664 		SendMessageW(hwnd, BM_SETCHECK, state ? BST_CHECKED : BST_UNCHECKED, 0);
10665 
10666 	}
10667 
10668 	override void handleWmCommand(ushort cmd, ushort id) {
10669 		if(cmd == 0) {
10670 			auto event = new Event(EventType.triggered, this);
10671 			event.dispatch();
10672 		}
10673 	}
10674 
10675 	this(Widget parent) {
10676 		super(parent);
10677 	}
10678 }
10679 else version(custom_widgets)
10680 /// ditto
10681 class MouseActivatedWidget : Widget {
10682 	@property bool isChecked() { return isChecked_; }
10683 	@property bool isChecked(bool b) { return isChecked_ = b; }
10684 
10685 	private bool isChecked_;
10686 
10687 	this(Widget parent) {
10688 		super(parent);
10689 
10690 		addEventListener((MouseDownEvent ev) {
10691 			if(ev.button == MouseButton.left) {
10692 				setDynamicState(DynamicState.depressed, true);
10693 				setDynamicState(DynamicState.hover, true);
10694 				redraw();
10695 			}
10696 		});
10697 
10698 		addEventListener((MouseUpEvent ev) {
10699 			if(ev.button == MouseButton.left) {
10700 				setDynamicState(DynamicState.depressed, false);
10701 				setDynamicState(DynamicState.hover, false);
10702 				redraw();
10703 			}
10704 		});
10705 
10706 		addEventListener((MouseMoveEvent mme) {
10707 			if(!(mme.state & ModifierState.leftButtonDown)) {
10708 				if(dynamicState_ & DynamicState.depressed) {
10709 					setDynamicState(DynamicState.depressed, false);
10710 					redraw();
10711 				}
10712 			}
10713 		});
10714 	}
10715 
10716 	override void defaultEventHandler_focus(Event ev) {
10717 		super.defaultEventHandler_focus(ev);
10718 		this.redraw();
10719 	}
10720 	override void defaultEventHandler_blur(Event ev) {
10721 		super.defaultEventHandler_blur(ev);
10722 		setDynamicState(DynamicState.depressed, false);
10723 		this.redraw();
10724 	}
10725 	override void defaultEventHandler_keydown(KeyDownEvent ev) {
10726 		super.defaultEventHandler_keydown(ev);
10727 		if(ev.key == Key.Space || ev.key == Key.Enter || ev.key == Key.PadEnter) {
10728 			setDynamicState(DynamicState.depressed, true);
10729 			setDynamicState(DynamicState.hover, true);
10730 			this.redraw();
10731 		}
10732 	}
10733 	override void defaultEventHandler_keyup(KeyUpEvent ev) {
10734 		super.defaultEventHandler_keyup(ev);
10735 		if(!(dynamicState & DynamicState.depressed))
10736 			return;
10737 		setDynamicState(DynamicState.depressed, false);
10738 		setDynamicState(DynamicState.hover, false);
10739 		this.redraw();
10740 
10741 		auto event = new Event(EventType.triggered, this);
10742 		event.sendDirectly();
10743 	}
10744 	override void defaultEventHandler_click(ClickEvent ev) {
10745 		super.defaultEventHandler_click(ev);
10746 		if(ev.button == MouseButton.left) {
10747 			auto event = new Event(EventType.triggered, this);
10748 			event.sendDirectly();
10749 		}
10750 	}
10751 
10752 }
10753 else static assert(false);
10754 
10755 /*
10756 /++
10757 	Like the tablet thing, it would have a label, a description, and a switch slider thingy.
10758 
10759 	Basically the same as a checkbox.
10760 +/
10761 class OnOffSwitch : MouseActivatedWidget {
10762 
10763 }
10764 */
10765 
10766 /++
10767 	History:
10768 		Added June 15, 2021 (dub v10.1)
10769 +/
10770 struct ImageLabel {
10771 	/++
10772 		Defines a label+image combo used by some widgets.
10773 		
10774 		If you provide just a text label, that is all the widget will try to
10775 		display. Or just an image will display just that. If you provide both,
10776 		it may display both text and image side by side or display the image
10777 		and offer text on an input event depending on the widget.
10778 
10779 		History:
10780 			The `alignment` parameter was added on September 27, 2021
10781 	+/
10782 	this(string label, TextAlignment alignment = TextAlignment.Center) {
10783 		this.label = label;
10784 		this.displayFlags = DisplayFlags.displayText;
10785 		this.alignment = alignment;
10786 	}
10787 
10788 	/// ditto
10789 	this(string label, MemoryImage image, TextAlignment alignment = TextAlignment.Center) {
10790 		this.label = label;
10791 		this.image = image;
10792 		this.displayFlags = DisplayFlags.displayText | DisplayFlags.displayImage;
10793 		this.alignment = alignment;
10794 	}
10795 
10796 	/// ditto
10797 	this(MemoryImage image, TextAlignment alignment = TextAlignment.Center) {
10798 		this.image = image;
10799 		this.displayFlags = DisplayFlags.displayImage;
10800 		this.alignment = alignment;
10801 	}
10802 
10803 	/// ditto
10804 	this(string label, MemoryImage image, int displayFlags, TextAlignment alignment = TextAlignment.Center) {
10805 		this.label = label;
10806 		this.image = image;
10807 		this.alignment = alignment;
10808 		this.displayFlags = displayFlags;
10809 	}
10810 
10811 	string label;
10812 	MemoryImage image;
10813 
10814 	enum DisplayFlags {
10815 		displayText = 1 << 0,
10816 		displayImage = 1 << 1,
10817 	}
10818 
10819 	int displayFlags = DisplayFlags.displayText | DisplayFlags.displayImage;
10820 
10821 	TextAlignment alignment;
10822 }
10823 
10824 /++
10825 	A basic checked or not checked box with an attached label.
10826 
10827 
10828 	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
10829 
10830 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
10831 
10832 	History:
10833 		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.
10834 +/
10835 class Checkbox : MouseActivatedWidget {
10836 	version(win32_widgets) {
10837 		override int maxHeight() { return scaleWithDpi(16); }
10838 		override int minHeight() { return scaleWithDpi(16); }
10839 	} else version(custom_widgets) {
10840 		override int maxHeight() { return defaultLineHeight; }
10841 		override int minHeight() { return defaultLineHeight; }
10842 	} else static assert(0);
10843 
10844 	override int marginLeft() { return 4; }
10845 
10846 	override int flexBasisWidth() { return 24 + cast(int) label.length * 7; }
10847 
10848 	/++
10849 		Just an alias because I keep typing checked out of web habit.
10850 
10851 		History:
10852 			Added May 31, 2021
10853 	+/
10854 	alias checked = isChecked;
10855 
10856 	private string label;
10857 	private dchar accelerator;
10858 
10859 	/++
10860 	+/
10861 	this(string label, Widget parent) {
10862 		this(ImageLabel(label), Appearance.checkbox, parent);
10863 	}
10864 
10865 	/// ditto
10866 	this(string label, Appearance appearance, Widget parent) {
10867 		this(ImageLabel(label), appearance, parent);
10868 	}
10869 
10870 	/++
10871 		Changes the look and may change the ideal size of the widget without changing its behavior. The precise look is platform-specific.
10872 
10873 		History:
10874 			Added June 29, 2021 (dub v10.2)
10875 	+/
10876 	enum Appearance {
10877 		checkbox, /// a normal checkbox
10878 		pushbutton, /// a button that is showed as pushed when checked and up when unchecked. Similar to the bold button in a toolbar in Wordpad.
10879 		//sliderswitch,
10880 	}
10881 	private Appearance appearance;
10882 
10883 	/// ditto
10884 	private this(ImageLabel label, Appearance appearance, Widget parent) {
10885 		super(parent);
10886 		version(win32_widgets) {
10887 			this.label = label.label;
10888 
10889 			uint extraStyle;
10890 			final switch(appearance) {
10891 				case Appearance.checkbox:
10892 				break;
10893 				case Appearance.pushbutton:
10894 					extraStyle |= BS_PUSHLIKE;
10895 				break;
10896 			}
10897 
10898 			createWin32Window(this, "button"w, label.label, BS_CHECKBOX | extraStyle);
10899 		} else version(custom_widgets) {
10900 			label.label.extractWindowsStyleLabel(this.label, this.accelerator);
10901 		} else static assert(0);
10902 	}
10903 
10904 	version(custom_widgets)
10905 	override void paint(WidgetPainter painter) {
10906 		auto cs = getComputedStyle();
10907 		if(isFocused()) {
10908 			painter.pen = Pen(Color.black, 1, Pen.Style.Dotted);
10909 			painter.fillColor = cs.windowBackgroundColor;
10910 			painter.drawRectangle(Point(0, 0), width, height);
10911 			painter.pen = Pen(Color.black, 1, Pen.Style.Solid);
10912 		} else {
10913 			painter.pen = Pen(cs.windowBackgroundColor, 1, Pen.Style.Solid);
10914 			painter.fillColor = cs.windowBackgroundColor;
10915 			painter.drawRectangle(Point(0, 0), width, height);
10916 		}
10917 
10918 
10919 		enum buttonSize = 16;
10920 
10921 		painter.outlineColor = Color.black;
10922 		painter.fillColor = Color.white;
10923 		painter.drawRectangle(scaleWithDpi(Point(2, 2)), scaleWithDpi(buttonSize - 2), scaleWithDpi(buttonSize - 2));
10924 
10925 		if(isChecked) {
10926 			painter.pen = Pen(Color.black, 2);
10927 			// I'm using height so the checkbox is square
10928 			enum padding = 5;
10929 			painter.drawLine(scaleWithDpi(Point(padding, padding)), scaleWithDpi(Point(buttonSize - (padding-2), buttonSize - (padding-2))));
10930 			painter.drawLine(scaleWithDpi(Point(buttonSize-(padding-2), padding)), scaleWithDpi(Point(padding, buttonSize - (padding-2))));
10931 
10932 			painter.pen = Pen(Color.black, 1);
10933 		}
10934 
10935 		if(label !is null) {
10936 			painter.outlineColor = cs.foregroundColor();
10937 			painter.fillColor = cs.foregroundColor();
10938 
10939 			// FIXME: should prolly just align the baseline or something
10940 			painter.drawText(scaleWithDpi(Point(buttonSize + 4, 2)), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
10941 		}
10942 	}
10943 
10944 	override void defaultEventHandler_triggered(Event ev) {
10945 		isChecked = !isChecked;
10946 
10947 		this.emit!(ChangeEvent!bool)(&isChecked);
10948 
10949 		redraw();
10950 	}
10951 
10952 	/// Emits a change event with the checked state
10953 	mixin Emits!(ChangeEvent!bool);
10954 }
10955 
10956 /// Adds empty space to a layout.
10957 class VerticalSpacer : Widget {
10958 	///
10959 	this(Widget parent) {
10960 		super(parent);
10961 	}
10962 }
10963 
10964 /// ditto
10965 class HorizontalSpacer : Widget {
10966 	///
10967 	this(Widget parent) {
10968 		super(parent);
10969 		this.tabStop = false;
10970 	}
10971 }
10972 
10973 
10974 /++
10975 	Creates a radio button with an associated label. These are usually put inside a [Fieldset].
10976 
10977 
10978 	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
10979 
10980 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
10981 
10982 	History:
10983 		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.
10984 +/
10985 class Radiobox : MouseActivatedWidget {
10986 
10987 	version(win32_widgets) {
10988 		override int maxHeight() { return scaleWithDpi(16); }
10989 		override int minHeight() { return scaleWithDpi(16); }
10990 	} else version(custom_widgets) {
10991 		override int maxHeight() { return defaultLineHeight; }
10992 		override int minHeight() { return defaultLineHeight; }
10993 	} else static assert(0);
10994 
10995 	override int marginLeft() { return 4; }
10996 
10997 	private string label;
10998 	private dchar accelerator;
10999 
11000 	version(win32_widgets)
11001 	this(string label, Widget parent) {
11002 		super(parent);
11003 		this.label = label;
11004 		createWin32Window(this, "button"w, label, BS_AUTORADIOBUTTON);
11005 	}
11006 	else version(custom_widgets)
11007 	this(string label, Widget parent) {
11008 		super(parent);
11009 		label.extractWindowsStyleLabel(this.label, this.accelerator);
11010 		height = 16;
11011 		width = height + 4 + cast(int) label.length * 16;
11012 	}
11013 	else static assert(false);
11014 
11015 	version(custom_widgets)
11016 	override void paint(WidgetPainter painter) {
11017 		auto cs = getComputedStyle();
11018 		if(isFocused) {
11019 			painter.fillColor = cs.windowBackgroundColor;
11020 			painter.pen = Pen(Color.black, 1, Pen.Style.Dotted);
11021 		} else {
11022 			painter.fillColor = cs.windowBackgroundColor;
11023 			painter.outlineColor = cs.windowBackgroundColor;
11024 		}
11025 		painter.drawRectangle(Point(0, 0), width, height);
11026 
11027 		painter.pen = Pen(Color.black, 1, Pen.Style.Solid);
11028 
11029 		enum buttonSize = 16;
11030 
11031 		painter.outlineColor = Color.black;
11032 		painter.fillColor = Color.white;
11033 		painter.drawEllipse(scaleWithDpi(Point(2, 2)), scaleWithDpi(Point(buttonSize - 2, buttonSize - 2)));
11034 		if(isChecked) {
11035 			painter.outlineColor = Color.black;
11036 			painter.fillColor = Color.black;
11037 			// I'm using height so the checkbox is square
11038 			painter.drawEllipse(scaleWithDpi(Point(5, 5)), scaleWithDpi(Point(buttonSize - 5, buttonSize - 5)));
11039 		}
11040 
11041 		painter.outlineColor = cs.foregroundColor();
11042 		painter.fillColor = cs.foregroundColor();
11043 
11044 		painter.drawText(scaleWithDpi(Point(buttonSize + 4, 0)), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
11045 	}
11046 
11047 
11048 	override void defaultEventHandler_triggered(Event ev) {
11049 		isChecked = true;
11050 
11051 		if(this.parent) {
11052 			foreach(child; this.parent.children) {
11053 				if(child is this) continue;
11054 				if(auto rb = cast(Radiobox) child) {
11055 					rb.isChecked = false;
11056 					rb.emit!(ChangeEvent!bool)(&rb.isChecked);
11057 					rb.redraw();
11058 				}
11059 			}
11060 		}
11061 
11062 		this.emit!(ChangeEvent!bool)(&this.isChecked);
11063 
11064 		redraw();
11065 	}
11066 
11067 	/// 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.
11068 	mixin Emits!(ChangeEvent!bool);
11069 }
11070 
11071 
11072 /++
11073 	Creates a push button with unbounded size. When it is clicked, it emits a `triggered` event.
11074 
11075 
11076 	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
11077 
11078 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
11079 
11080 	History:
11081 		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.
11082 +/
11083 class Button : MouseActivatedWidget {
11084 	override int heightStretchiness() { return 3; }
11085 	override int widthStretchiness() { return 3; }
11086 
11087 	/++
11088 		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.
11089 
11090 		History:
11091 			Added July 2, 2021
11092 	+/
11093 	public bool triggersOnMultiClick;
11094 
11095 	private string label_;
11096 	private TextAlignment alignment;
11097 	private dchar accelerator;
11098 
11099 	///
11100 	string label() { return label_; }
11101 	///
11102 	void label(string l) {
11103 		label_ = l;
11104 		version(win32_widgets) {
11105 			WCharzBuffer bfr = WCharzBuffer(l);
11106 			SetWindowTextW(hwnd, bfr.ptr);
11107 		} else version(custom_widgets) {
11108 			redraw();
11109 		}
11110 	}
11111 
11112 	override void defaultEventHandler_dblclick(DoubleClickEvent ev) {
11113 		super.defaultEventHandler_dblclick(ev);
11114 		if(triggersOnMultiClick) {
11115 			if(ev.button == MouseButton.left) {
11116 				auto event = new Event(EventType.triggered, this);
11117 				event.sendDirectly();
11118 			}
11119 		}
11120 	}
11121 
11122 	private Sprite sprite;
11123 	private int displayFlags;
11124 
11125 	/++
11126 		Creates a push button with the given label, which may be an image or some text.
11127 
11128 		Bugs:
11129 			If the image is bigger than the button, it may not be displayed in the right position on Linux.
11130 
11131 		History:
11132 			The [ImageLabel] overload was added on June 21, 2021 (dub v10.1).
11133 
11134 			The button with label and image will respect requests to show both on Windows as
11135 			of March 28, 2022 iff you provide a manifest file to opt into common controls v6.
11136 	+/
11137 	this(ImageLabel label, Widget parent) {
11138 		version(win32_widgets) {
11139 			// FIXME: use ideal button size instead
11140 			width = 50;
11141 			height = 30;
11142 			super(parent);
11143 
11144 			// BS_BITMAP is set when we want image only, so checking for exactly that combination
11145 			enum imgFlags = ImageLabel.DisplayFlags.displayImage | ImageLabel.DisplayFlags.displayText;
11146 			auto extraStyle = ((label.displayFlags & imgFlags) == ImageLabel.DisplayFlags.displayImage) ? BS_BITMAP : 0;
11147 
11148 			// the transparent thing can mess up borders in other cases, so only going to keep it for bitmap things where it might matter
11149 			createWin32Window(this, "button"w, label.label, BS_PUSHBUTTON | extraStyle, extraStyle == BS_BITMAP ? WS_EX_TRANSPARENT : 0 );
11150 
11151 			if(label.image) {
11152 				sprite = Sprite.fromMemoryImage(parentWindow.win, label.image, true);
11153 
11154 				SendMessageW(hwnd, BM_SETIMAGE, IMAGE_BITMAP, cast(LPARAM) sprite.nativeHandle);
11155 			}
11156 
11157 			this.label = label.label;
11158 		} else version(custom_widgets) {
11159 			width = 50;
11160 			height = 30;
11161 			super(parent);
11162 
11163 			label.label.extractWindowsStyleLabel(this.label_, this.accelerator);
11164 
11165 			if(label.image) {
11166 				this.sprite = Sprite.fromMemoryImage(parentWindow.win, label.image);
11167 				this.displayFlags = label.displayFlags;
11168 			}
11169 
11170 			this.alignment = label.alignment;
11171 		}
11172 	}
11173 
11174 	///
11175 	this(string label, Widget parent) {
11176 		this(ImageLabel(label), parent);
11177 	}
11178 
11179 	override int minHeight() { return defaultLineHeight + 4; }
11180 
11181 	static class Style : Widget.Style {
11182 		override WidgetBackground background() {
11183 			auto cs = widget.getComputedStyle(); // FIXME: this is potentially recursive
11184 
11185 			auto pressed = DynamicState.depressed | DynamicState.hover;
11186 			if((widget.dynamicState & pressed) == pressed) {
11187 				return WidgetBackground(cs.depressedButtonColor());
11188 			} else if(widget.dynamicState & DynamicState.hover) {
11189 				return WidgetBackground(cs.hoveringColor());
11190 			} else {
11191 				return WidgetBackground(cs.buttonColor());
11192 			}
11193 		}
11194 
11195 		override FrameStyle borderStyle() {
11196 			auto pressed = DynamicState.depressed | DynamicState.hover;
11197 			if((widget.dynamicState & pressed) == pressed) {
11198 				return FrameStyle.sunk;
11199 			} else {
11200 				return FrameStyle.risen;
11201 			}
11202 
11203 		}
11204 
11205 		override bool variesWithState(ulong dynamicStateFlags) {
11206 			return super.variesWithState(dynamicStateFlags) || (dynamicStateFlags & (DynamicState.depressed | DynamicState.hover));
11207 		}
11208 	}
11209 	mixin OverrideStyle!Style;
11210 
11211 	version(custom_widgets)
11212 	override void paint(WidgetPainter painter) {
11213 		painter.drawThemed(delegate Rectangle(const Rectangle bounds) {
11214 			if(sprite) {
11215 				sprite.drawAt(
11216 					painter,
11217 					bounds.upperLeft + Point((bounds.width - sprite.width) / 2, (bounds.height - sprite.height) / 2),
11218 					Point(0, 0),
11219 					bounds.size
11220 				);
11221 			} else {
11222 				painter.drawText(bounds.upperLeft, label, bounds.lowerRight, alignment | TextAlignment.VerticalCenter);
11223 			}
11224 			return bounds;
11225 		});
11226 	}
11227 
11228 	override int flexBasisWidth() {
11229 		version(win32_widgets) {
11230 			SIZE size;
11231 			SendMessage(hwnd, BCM_GETIDEALSIZE, 0, cast(LPARAM) &size);
11232 			if(size.cx == 0)
11233 				goto fallback;
11234 			return size.cx + scaleWithDpi(16);
11235 		}
11236 		fallback:
11237 			return scaleWithDpi(cast(int) label.length * 8 + 16);
11238 	}
11239 
11240 	override int flexBasisHeight() {
11241 		version(win32_widgets) {
11242 			SIZE size;
11243 			SendMessage(hwnd, BCM_GETIDEALSIZE, 0, cast(LPARAM) &size);
11244 			if(size.cy == 0)
11245 				goto fallback;
11246 			return size.cy + scaleWithDpi(6);
11247 		}
11248 		fallback:
11249 			return defaultLineHeight + 4;
11250 	}
11251 }
11252 
11253 /++
11254 	A button with a consistent size, suitable for user commands like OK and cANCEL.
11255 +/
11256 class CommandButton : Button {
11257 	this(string label, Widget parent) {
11258 		super(label, parent);
11259 	}
11260 
11261 	// FIXME: I think I can simply make this 0 stretchiness instead of max now that the flex basis is there
11262 
11263 	override int maxHeight() {
11264 		return defaultLineHeight + 4;
11265 	}
11266 
11267 	override int maxWidth() {
11268 		return defaultLineHeight * 4;
11269 	}
11270 
11271 	override int marginLeft() { return 12; }
11272 	override int marginRight() { return 12; }
11273 	override int marginTop() { return 12; }
11274 	override int marginBottom() { return 12; }
11275 }
11276 
11277 ///
11278 enum ArrowDirection {
11279 	left, ///
11280 	right, ///
11281 	up, ///
11282 	down ///
11283 }
11284 
11285 ///
11286 version(custom_widgets)
11287 class ArrowButton : Button {
11288 	///
11289 	this(ArrowDirection direction, Widget parent) {
11290 		super("", parent);
11291 		this.direction = direction;
11292 		triggersOnMultiClick = true;
11293 	}
11294 
11295 	private ArrowDirection direction;
11296 
11297 	override int minHeight() { return scaleWithDpi(16); }
11298 	override int maxHeight() { return scaleWithDpi(16); }
11299 	override int minWidth() { return scaleWithDpi(16); }
11300 	override int maxWidth() { return scaleWithDpi(16); }
11301 
11302 	override void paint(WidgetPainter painter) {
11303 		super.paint(painter);
11304 
11305 		auto cs = getComputedStyle();
11306 
11307 		painter.outlineColor = cs.foregroundColor;
11308 		painter.fillColor = cs.foregroundColor;
11309 
11310 		auto offset = Point((this.width - scaleWithDpi(16)) / 2, (this.height - scaleWithDpi(16)) / 2);
11311 
11312 		final switch(direction) {
11313 			case ArrowDirection.up:
11314 				painter.drawPolygon(
11315 					scaleWithDpi(Point(2, 10) + offset),
11316 					scaleWithDpi(Point(7, 5) + offset),
11317 					scaleWithDpi(Point(12, 10) + offset),
11318 					scaleWithDpi(Point(2, 10) + offset)
11319 				);
11320 			break;
11321 			case ArrowDirection.down:
11322 				painter.drawPolygon(
11323 					scaleWithDpi(Point(2, 6) + offset),
11324 					scaleWithDpi(Point(7, 11) + offset),
11325 					scaleWithDpi(Point(12, 6) + offset),
11326 					scaleWithDpi(Point(2, 6) + offset)
11327 				);
11328 			break;
11329 			case ArrowDirection.left:
11330 				painter.drawPolygon(
11331 					scaleWithDpi(Point(10, 2) + offset),
11332 					scaleWithDpi(Point(5, 7) + offset),
11333 					scaleWithDpi(Point(10, 12) + offset),
11334 					scaleWithDpi(Point(10, 2) + offset)
11335 				);
11336 			break;
11337 			case ArrowDirection.right:
11338 				painter.drawPolygon(
11339 					scaleWithDpi(Point(6, 2) + offset),
11340 					scaleWithDpi(Point(11, 7) + offset),
11341 					scaleWithDpi(Point(6, 12) + offset),
11342 					scaleWithDpi(Point(6, 2) + offset)
11343 				);
11344 			break;
11345 		}
11346 	}
11347 }
11348 
11349 private
11350 int[2] getChildPositionRelativeToParentOrigin(Widget c) nothrow {
11351 	int x, y;
11352 	Widget par = c;
11353 	while(par) {
11354 		x += par.x;
11355 		y += par.y;
11356 		par = par.parent;
11357 	}
11358 	return [x, y];
11359 }
11360 
11361 version(win32_widgets)
11362 private
11363 int[2] getChildPositionRelativeToParentHwnd(Widget c) nothrow {
11364 // MapWindowPoints?
11365 	int x, y;
11366 	Widget par = c;
11367 	while(par) {
11368 		x += par.x;
11369 		y += par.y;
11370 		par = par.parent;
11371 		if(par !is null && par.useNativeDrawing())
11372 			break;
11373 	}
11374 	return [x, y];
11375 }
11376 
11377 ///
11378 class ImageBox : Widget {
11379 	private MemoryImage image_;
11380 
11381 	override int widthStretchiness() { return 1; }
11382 	override int heightStretchiness() { return 1; }
11383 	override int widthShrinkiness() { return 1; }
11384 	override int heightShrinkiness() { return 1; }
11385 
11386 	override int flexBasisHeight() {
11387 		return image_.height;
11388 	}
11389 
11390 	override int flexBasisWidth() {
11391 		return image_.width;
11392 	}
11393 
11394 	///
11395 	public void setImage(MemoryImage image){
11396 		this.image_ = image;
11397 		if(this.parentWindow && this.parentWindow.win) {
11398 			if(sprite)
11399 				sprite.dispose();
11400 			sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_, true));
11401 		}
11402 		redraw();
11403 	}
11404 
11405 	/// How to fit the image in the box if they aren't an exact match in size?
11406 	enum HowToFit {
11407 		center, /// centers the image, cropping around all the edges as needed
11408 		crop, /// always draws the image in the upper left, cropping the lower right if needed
11409 		// stretch, /// not implemented
11410 	}
11411 
11412 	private Sprite sprite;
11413 	private HowToFit howToFit_;
11414 
11415 	private Color backgroundColor_;
11416 
11417 	///
11418 	this(MemoryImage image, HowToFit howToFit, Color backgroundColor, Widget parent) {
11419 		this.image_ = image;
11420 		this.tabStop = false;
11421 		this.howToFit_ = howToFit;
11422 		this.backgroundColor_ = backgroundColor;
11423 		super(parent);
11424 		updateSprite();
11425 	}
11426 
11427 	/// ditto
11428 	this(MemoryImage image, HowToFit howToFit, Widget parent) {
11429 		this(image, howToFit, Color.transparent, parent);
11430 	}
11431 
11432 	private void updateSprite() {
11433 		if(sprite is null && this.parentWindow && this.parentWindow.win) {
11434 			sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_, true));
11435 		}
11436 	}
11437 
11438 	override void paint(WidgetPainter painter) {
11439 		updateSprite();
11440 		if(backgroundColor_.a) {
11441 			painter.fillColor = backgroundColor_;
11442 			painter.drawRectangle(Point(0, 0), width, height);
11443 		}
11444 		if(howToFit_ == HowToFit.crop)
11445 			sprite.drawAt(painter, Point(0, 0));
11446 		else if(howToFit_ == HowToFit.center) {
11447 			sprite.drawAt(painter, Point((width - image_.width) / 2, (height - image_.height) / 2));
11448 		}
11449 	}
11450 }
11451 
11452 ///
11453 class TextLabel : Widget {
11454 	override int maxHeight() { return defaultLineHeight; }
11455 	override int minHeight() { return defaultLineHeight; }
11456 	override int minWidth() { return 32; }
11457 
11458 	override int flexBasisHeight() { return minHeight(); }
11459 	override int flexBasisWidth() { return defaultTextWidth(label); }
11460 
11461 	string label_;
11462 
11463 	/++
11464 		Indicates which other control this label is here for. Similar to HTML `for` attribute.
11465 
11466 		In practice this means a click on the label will focus the `labelFor`. In future versions
11467 		it will also set screen reader hints but that is not yet implemented.
11468 
11469 		History:
11470 			Added October 3, 2021 (dub v10.4)
11471 	+/
11472 	Widget labelFor;
11473 
11474 	///
11475 	@scriptable
11476 	string label() { return label_; }
11477 
11478 	///
11479 	@scriptable
11480 	void label(string l) {
11481 		label_ = l;
11482 		version(win32_widgets) {
11483 			WCharzBuffer bfr = WCharzBuffer(l);
11484 			SetWindowTextW(hwnd, bfr.ptr);
11485 		} else version(custom_widgets)
11486 			redraw();
11487 	}
11488 
11489 	///
11490 	this(string label, TextAlignment alignment, Widget parent) {
11491 		this.label_ = label;
11492 		this.alignment = alignment;
11493 		this.tabStop = false;
11494 		super(parent);
11495 
11496 		version(win32_widgets)
11497 		createWin32Window(this, "static"w, label, (alignment & TextAlignment.Center) ? SS_CENTER : 0, (alignment & TextAlignment.Right) ? WS_EX_RIGHT : WS_EX_LEFT);
11498 	}
11499 
11500 	override void defaultEventHandler_click(scope ClickEvent ce) {
11501 		if(this.labelFor !is null)
11502 			this.labelFor.focus();
11503 	}
11504 
11505 	/++
11506 		WARNING: this currently sets TextAlignment.Right as the default. That will change in a future version.
11507 		For future-proofing of your code, if you rely on TextAlignment.Right, you MUST specify that explicitly.
11508 	+/
11509 	this(string label, Widget parent) {
11510 		this(label, TextAlignment.Right, parent);
11511 	}
11512 
11513 
11514 	TextAlignment alignment;
11515 
11516 	version(custom_widgets)
11517 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
11518 		painter.outlineColor = getComputedStyle().foregroundColor;
11519 		painter.drawText(Point(0, 0), this.label, Point(width, height), alignment);
11520 		return bounds;
11521 	}
11522 
11523 }
11524 
11525 version(custom_widgets)
11526 	private struct etc {
11527 		mixin ExperimentalTextComponent;
11528 	}
11529 
11530 version(win32_widgets)
11531 	alias EditableTextWidgetParent = Widget; ///
11532 else version(custom_widgets)
11533 	alias EditableTextWidgetParent = ScrollableWidget; ///
11534 else static assert(0);
11535 
11536 /// Contains the implementation of text editing
11537 abstract class EditableTextWidget : EditableTextWidgetParent {
11538 	this(Widget parent) {
11539 		super(parent);
11540 	}
11541 
11542 	bool wordWrapEnabled_ = false;
11543 	void wordWrapEnabled(bool enabled) {
11544 		version(win32_widgets) {
11545 			SendMessageW(hwnd, EM_FMTLINES, enabled ? 1 : 0, 0);
11546 		} else version(custom_widgets) {
11547 			wordWrapEnabled_ = enabled; // FIXME
11548 		} else static assert(false);
11549 	}
11550 
11551 	override int minWidth() { return scaleWithDpi(16); }
11552 	override int minHeight() { return defaultLineHeight + 0; } // the +0 is to leave room for the padding
11553 	override int widthStretchiness() { return 7; }
11554 
11555 	void selectAll() {
11556 		version(win32_widgets)
11557 			SendMessage(hwnd, EM_SETSEL, 0, -1);
11558 		else version(custom_widgets) {
11559 			textLayout.selectAll();
11560 			redraw();
11561 		}
11562 	}
11563 
11564 	@property string content() {
11565 		version(win32_widgets) {
11566 			wchar[4096] bufferstack;
11567 			wchar[] buffer;
11568 			auto len = GetWindowTextLength(hwnd);
11569 			if(len < bufferstack.length)
11570 				buffer = bufferstack[0 .. len + 1];
11571 			else
11572 				buffer = new wchar[](len + 1);
11573 
11574 			auto l = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
11575 			if(l >= 0)
11576 				return makeUtf8StringFromWindowsString(buffer[0 .. l]);
11577 			else
11578 				return null;
11579 		} else version(custom_widgets) {
11580 			return textLayout.getPlainText();
11581 		} else static assert(false);
11582 	}
11583 	@property void content(string s) {
11584 		version(win32_widgets) {
11585 			WCharzBuffer bfr = WCharzBuffer(s, WindowsStringConversionFlags.convertNewLines);
11586 			SetWindowTextW(hwnd, bfr.ptr);
11587 		} else version(custom_widgets) {
11588 			textLayout.clear();
11589 			textLayout.addText(s);
11590 
11591 			{
11592 			// FIXME: it should be able to get this info easier
11593 			auto painter = draw();
11594 			textLayout.redoLayout(painter);
11595 			}
11596 			auto cbb = textLayout.contentBoundingBox();
11597 			setContentSize(cbb.width, cbb.height);
11598 			/*
11599 			textLayout.addText(ForegroundColor.red, s);
11600 			textLayout.addText(ForegroundColor.blue, TextFormat.underline, "http://dpldocs.info/");
11601 			textLayout.addText(" is the best!");
11602 			*/
11603 			redraw();
11604 		}
11605 		else static assert(false);
11606 	}
11607 
11608 	void addText(string txt) {
11609 		version(custom_widgets) {
11610 
11611 			textLayout.addText(txt);
11612 
11613 			{
11614 			// FIXME: it should be able to get this info easier
11615 			auto painter = draw();
11616 			textLayout.redoLayout(painter);
11617 			}
11618 			auto cbb = textLayout.contentBoundingBox();
11619 			setContentSize(cbb.width, cbb.height);
11620 
11621 		} else version(win32_widgets) {
11622 			// get the current selection
11623 			DWORD StartPos, EndPos;
11624 			SendMessageW( hwnd, EM_GETSEL, cast(WPARAM)(&StartPos), cast(LPARAM)(&EndPos) );
11625 
11626 			// move the caret to the end of the text
11627 			int outLength = GetWindowTextLengthW(hwnd);
11628 			SendMessageW( hwnd, EM_SETSEL, outLength, outLength );
11629 
11630 			// insert the text at the new caret position
11631 			WCharzBuffer bfr = WCharzBuffer(txt, WindowsStringConversionFlags.convertNewLines);
11632 			SendMessageW( hwnd, EM_REPLACESEL, TRUE, cast(LPARAM) bfr.ptr );
11633 
11634 			// restore the previous selection
11635 			SendMessageW( hwnd, EM_SETSEL, StartPos, EndPos );
11636 		} else static assert(0);
11637 	}
11638 
11639 	version(custom_widgets)
11640 	override void paintFrameAndBackground(WidgetPainter painter) {
11641 		this.draw3dFrame(painter, FrameStyle.sunk, Color.white);
11642 	}
11643 
11644 	version(win32_widgets) { /* will do it with Windows calls in the classes */ }
11645 	else version(custom_widgets) {
11646 		// FIXME
11647 
11648 		static if(SimpledisplayTimerAvailable)
11649 			Timer caretTimer;
11650 		etc.TextLayout textLayout;
11651 
11652 		void setupCustomTextEditing() {
11653 			textLayout = new etc.TextLayout(Rectangle(4, 2, width - 8, height - 4));
11654 			textLayout.selectionXorColor = getComputedStyle().activeListXorColor;
11655 		}
11656 
11657 		override void paint(WidgetPainter painter) {
11658 			if(parentWindow.win.closed) return;
11659 
11660 			textLayout.boundingBox = Rectangle(4, 2, width - 8, height - 4);
11661 
11662 			/*
11663 			painter.outlineColor = Color.white;
11664 			painter.fillColor = Color.white;
11665 			painter.drawRectangle(Point(4, 4), contentWidth, contentHeight);
11666 			*/
11667 
11668 			painter.outlineColor = Color.black;
11669 			// painter.drawText(Point(4, 4), content, Point(width - 4, height - 4));
11670 
11671 			textLayout.caretShowingOnScreen = false;
11672 
11673 			textLayout.drawInto(painter, !parentWindow.win.closed && isFocused());
11674 		}
11675 
11676 		static class Style : Widget.Style {
11677 			override MouseCursor cursor() {
11678 				return GenericCursor.Text;
11679 			}
11680 		}
11681 		mixin OverrideStyle!Style;
11682 	}
11683 	else static assert(false);
11684 
11685 
11686 
11687 	version(custom_widgets)
11688 	override void defaultEventHandler_mousedown(MouseDownEvent ev) {
11689 		super.defaultEventHandler_mousedown(ev);
11690 		if(parentWindow.win.closed) return;
11691 		if(ev.button == MouseButton.left) {
11692 			if(textLayout.selectNone())
11693 				redraw();
11694 			textLayout.moveCaretToPixelCoordinates(ev.clientX, ev.clientY);
11695 			this.focus();
11696 			//this.parentWindow.win.grabInput();
11697 		} else if(ev.button == MouseButton.middle) {
11698 			static if(UsingSimpledisplayX11) {
11699 				getPrimarySelection(parentWindow.win, (txt) {
11700 					textLayout.insert(txt);
11701 					redraw();
11702 
11703 					auto cbb = textLayout.contentBoundingBox();
11704 					setContentSize(cbb.width, cbb.height);
11705 				});
11706 			}
11707 		}
11708 	}
11709 
11710 	version(custom_widgets)
11711 	override void defaultEventHandler_mouseup(MouseUpEvent ev) {
11712 		//this.parentWindow.win.releaseInputGrab();
11713 		super.defaultEventHandler_mouseup(ev);
11714 	}
11715 
11716 	version(custom_widgets)
11717 	override void defaultEventHandler_mousemove(MouseMoveEvent ev) {
11718 		super.defaultEventHandler_mousemove(ev);
11719 		if(ev.state & ModifierState.leftButtonDown) {
11720 			textLayout.selectToPixelCoordinates(ev.clientX, ev.clientY);
11721 			redraw();
11722 		}
11723 	}
11724 
11725 	version(custom_widgets)
11726 	override void defaultEventHandler_focus(Event ev) {
11727 		super.defaultEventHandler_focus(ev);
11728 		if(parentWindow.win.closed) return;
11729 		auto painter = this.draw();
11730 		textLayout.drawCaret(painter);
11731 
11732 		static if(SimpledisplayTimerAvailable)
11733 		if(caretTimer) {
11734 			caretTimer.destroy();
11735 			caretTimer = null;
11736 		}
11737 
11738 		bool blinkingCaret = true;
11739 		static if(UsingSimpledisplayX11)
11740 			if(!Image.impl.xshmAvailable)
11741 				blinkingCaret = false; // if on a remote connection, don't waste bandwidth on an expendable blink
11742 
11743 		if(blinkingCaret)
11744 		static if(SimpledisplayTimerAvailable)
11745 		caretTimer = new Timer(500, {
11746 			if(parentWindow.win.closed) {
11747 				caretTimer.destroy();
11748 				return;
11749 			}
11750 			if(isFocused()) {
11751 				auto painter = this.draw();
11752 				textLayout.drawCaret(painter);
11753 			} else if(textLayout.caretShowingOnScreen) {
11754 				auto painter = this.draw();
11755 				textLayout.eraseCaret(painter);
11756 			}
11757 		});
11758 	}
11759 
11760 	private string lastContentBlur;
11761 
11762 	override void defaultEventHandler_blur(Event ev) {
11763 		super.defaultEventHandler_blur(ev);
11764 		if(parentWindow.win.closed) return;
11765 		version(custom_widgets) {
11766 			auto painter = this.draw();
11767 			textLayout.eraseCaret(painter);
11768 			static if(SimpledisplayTimerAvailable)
11769 			if(caretTimer) {
11770 				caretTimer.destroy();
11771 				caretTimer = null;
11772 			}
11773 		}
11774 
11775 		if(this.content != lastContentBlur) {
11776 			auto evt = new ChangeEvent!string(this, &this.content);
11777 			evt.dispatch();
11778 			lastContentBlur = this.content;
11779 		}
11780 	}
11781 
11782 	version(custom_widgets)
11783 	override void defaultEventHandler_char(CharEvent ev) {
11784 		super.defaultEventHandler_char(ev);
11785 		textLayout.insert(ev.character);
11786 		redraw();
11787 
11788 		// FIXME: too inefficient
11789 		auto cbb = textLayout.contentBoundingBox();
11790 		setContentSize(cbb.width, cbb.height);
11791 	}
11792 	version(custom_widgets)
11793 	override void defaultEventHandler_keydown(KeyDownEvent ev) {
11794 		//super.defaultEventHandler_keydown(ev);
11795 		switch(ev.key) {
11796 			case Key.Delete:
11797 				textLayout.delete_();
11798 				redraw();
11799 			break;
11800 			case Key.Left:
11801 				textLayout.moveLeft();
11802 				redraw();
11803 			break;
11804 			case Key.Right:
11805 				textLayout.moveRight();
11806 				redraw();
11807 			break;
11808 			case Key.Up:
11809 				textLayout.moveUp();
11810 				redraw();
11811 			break;
11812 			case Key.Down:
11813 				textLayout.moveDown();
11814 				redraw();
11815 			break;
11816 			case Key.Home:
11817 				textLayout.moveHome();
11818 				redraw();
11819 			break;
11820 			case Key.End:
11821 				textLayout.moveEnd();
11822 				redraw();
11823 			break;
11824 			case Key.PageUp:
11825 				foreach(i; 0 .. 32)
11826 				textLayout.moveUp();
11827 				redraw();
11828 			break;
11829 			case Key.PageDown:
11830 				foreach(i; 0 .. 32)
11831 				textLayout.moveDown();
11832 				redraw();
11833 			break;
11834 
11835 			default:
11836 				 {} // intentionally blank, let "char" handle it
11837 		}
11838 		/*
11839 		if(ev.key == Key.Backspace) {
11840 			textLayout.backspace();
11841 			redraw();
11842 		}
11843 		*/
11844 		ensureVisibleInScroll(textLayout.caretBoundingBox());
11845 	}
11846 
11847 
11848 }
11849 
11850 ///
11851 class LineEdit : EditableTextWidget {
11852 	// FIXME: hack
11853 	version(custom_widgets) {
11854 	override bool showingVerticalScroll() { return false; }
11855 	override bool showingHorizontalScroll() { return false; }
11856 	}
11857 
11858 	override int flexBasisWidth() { return 250; }
11859 
11860 	///
11861 	this(Widget parent) {
11862 		super(parent);
11863 		version(win32_widgets) {
11864 			createWin32Window(this, "edit"w, "", 
11865 				0, WS_EX_CLIENTEDGE);//|WS_HSCROLL|ES_AUTOHSCROLL);
11866 		} else version(custom_widgets) {
11867 			setupCustomTextEditing();
11868 			addEventListener(delegate(CharEvent ev) {
11869 				if(ev.character == '\n')
11870 					ev.preventDefault();
11871 			});
11872 		} else static assert(false);
11873 	}
11874 	override int maxHeight() { return defaultLineHeight + 4; }
11875 	override int minHeight() { return defaultLineHeight + 4; }
11876 
11877 	/+
11878 	@property void passwordMode(bool p) {
11879 		SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) | ES_PASSWORD);
11880 	}
11881 	+/
11882 }
11883 
11884 /++
11885 	A [LineEdit] that displays `*` in place of the actual characters.
11886 
11887 	Alas, Windows requires the window to be created differently to use this style,
11888 	so it had to be a new class instead of a toggle on and off on an existing object.
11889 
11890 	FIXME: this is not yet implemented on Linux, it will work the same as a TextEdit there for now.
11891 
11892 	History:
11893 		Added January 24, 2021
11894 +/
11895 class PasswordEdit : EditableTextWidget {
11896 	version(custom_widgets) {
11897 	override bool showingVerticalScroll() { return false; }
11898 	override bool showingHorizontalScroll() { return false; }
11899 	}
11900 
11901 	override int flexBasisWidth() { return 250; }
11902 
11903 	///
11904 	this(Widget parent) {
11905 		super(parent);
11906 		version(win32_widgets) {
11907 			createWin32Window(this, "edit"w, "", 
11908 				ES_PASSWORD, WS_EX_CLIENTEDGE);//|WS_HSCROLL|ES_AUTOHSCROLL);
11909 		} else version(custom_widgets) {
11910 			setupCustomTextEditing();
11911 			addEventListener(delegate(CharEvent ev) {
11912 				if(ev.character == '\n')
11913 					ev.preventDefault();
11914 			});
11915 		} else static assert(false);
11916 	}
11917 	override int maxHeight() { return defaultLineHeight + 4; }
11918 	override int minHeight() { return defaultLineHeight + 4; }
11919 }
11920 
11921 
11922 ///
11923 class TextEdit : EditableTextWidget {
11924 	///
11925 	this(Widget parent) {
11926 		super(parent);
11927 		version(win32_widgets) {
11928 			createWin32Window(this, "edit"w, "", 
11929 				0|WS_VSCROLL|WS_HSCROLL|ES_MULTILINE|ES_WANTRETURN|ES_AUTOHSCROLL|ES_AUTOVSCROLL, WS_EX_CLIENTEDGE);
11930 		} else version(custom_widgets) {
11931 			setupCustomTextEditing();
11932 		} else static assert(false);
11933 	}
11934 	override int maxHeight() { return int.max; }
11935 	override int heightStretchiness() { return 7; }
11936 
11937 	override int flexBasisWidth() { return 250; }
11938 	override int flexBasisHeight() { return 250; }
11939 }
11940 
11941 
11942 /++
11943 
11944 +/
11945 version(none)
11946 class RichTextDisplay : Widget {
11947 	@property void content(string c) {}
11948 	void appendContent(string c) {}
11949 }
11950 
11951 ///
11952 class MessageBox : Window {
11953 	private string message;
11954 	MessageBoxButton buttonPressed = MessageBoxButton.None;
11955 	///
11956 	this(string message, string[] buttons = ["OK"], MessageBoxButton[] buttonIds = [MessageBoxButton.OK]) {
11957 		super(300, 100);
11958 
11959 		assert(buttons.length);
11960 		assert(buttons.length ==  buttonIds.length);
11961 
11962 		this.message = message;
11963 
11964 		int buttonsWidth = cast(int) buttons.length * 50 + (cast(int) buttons.length - 1) * 16;
11965 		buttonsWidth = scaleWithDpi(buttonsWidth);
11966 
11967 		int x = this.width / 2 - buttonsWidth / 2;
11968 
11969 		foreach(idx, buttonText; buttons) {
11970 			auto button = new Button(buttonText, this);
11971 			button.x = x;
11972 			button.y = height - (button.height + 10);
11973 			button.addEventListener(EventType.triggered, ((size_t idx) { return () {
11974 				this.buttonPressed = buttonIds[idx];
11975 				win.close();
11976 			}; })(idx));
11977 
11978 			button.registerMovement();
11979 			x += button.width;
11980 			x += scaleWithDpi(16);
11981 			if(idx == 0)
11982 				button.focus();
11983 		}
11984 
11985 		win.show();
11986 		redraw();
11987 	}
11988 
11989 	override void paint(WidgetPainter painter) {
11990 		super.paint(painter);
11991 
11992 		auto cs = getComputedStyle();
11993 
11994 		painter.outlineColor = cs.foregroundColor();
11995 		painter.fillColor = cs.foregroundColor();
11996 
11997 		painter.drawText(Point(0, 0), message, Point(width, height / 2), TextAlignment.Center | TextAlignment.VerticalCenter);
11998 	}
11999 
12000 	// this one is all fixed position
12001 	override void recomputeChildLayout() {}
12002 }
12003 
12004 ///
12005 enum MessageBoxStyle {
12006 	OK, ///
12007 	OKCancel, ///
12008 	RetryCancel, ///
12009 	YesNo, ///
12010 	YesNoCancel, ///
12011 	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.
12012 }
12013 
12014 ///
12015 enum MessageBoxIcon {
12016 	None, ///
12017 	Info, ///
12018 	Warning, ///
12019 	Error ///
12020 }
12021 
12022 /// Identifies the button the user pressed on a message box.
12023 enum MessageBoxButton {
12024 	None, /// The user closed the message box without clicking any of the buttons.
12025 	OK, ///
12026 	Cancel, ///
12027 	Retry, ///
12028 	Yes, ///
12029 	No, ///
12030 	Continue ///
12031 }
12032 
12033 
12034 /++
12035 	Displays a modal message box, blocking until the user dismisses it.
12036 
12037 	Returns: the button pressed.
12038 +/
12039 MessageBoxButton messageBox(string title, string message, MessageBoxStyle style = MessageBoxStyle.OK, MessageBoxIcon icon = MessageBoxIcon.None) {
12040 	version(win32_widgets) {
12041 		WCharzBuffer t = WCharzBuffer(title);
12042 		WCharzBuffer m = WCharzBuffer(message);
12043 		UINT type;
12044 		with(MessageBoxStyle)
12045 		final switch(style) {
12046 			case OK: type |= MB_OK; break;
12047 			case OKCancel: type |= MB_OKCANCEL; break;
12048 			case RetryCancel: type |= MB_RETRYCANCEL; break;
12049 			case YesNo: type |= MB_YESNO; break;
12050 			case YesNoCancel: type |= MB_YESNOCANCEL; break;
12051 			case RetryCancelContinue: type |= MB_CANCELTRYCONTINUE; break;
12052 		}
12053 		with(MessageBoxIcon)
12054 		final switch(icon) {
12055 			case None: break;
12056 			case Info: type |= MB_ICONINFORMATION; break;
12057 			case Warning: type |= MB_ICONWARNING; break;
12058 			case Error: type |= MB_ICONERROR; break;
12059 		}
12060 		switch(MessageBoxW(null, m.ptr, t.ptr, type)) {
12061 			case IDOK: return MessageBoxButton.OK;
12062 			case IDCANCEL: return MessageBoxButton.Cancel;
12063 			case IDTRYAGAIN, IDRETRY: return MessageBoxButton.Retry;
12064 			case IDYES: return MessageBoxButton.Yes;
12065 			case IDNO: return MessageBoxButton.No;
12066 			case IDCONTINUE: return MessageBoxButton.Continue;
12067 			default: return MessageBoxButton.None;
12068 		}
12069 	} else {
12070 		string[] buttons;
12071 		MessageBoxButton[] buttonIds;
12072 		with(MessageBoxStyle)
12073 		final switch(style) {
12074 			case OK:
12075 				buttons = ["OK"];
12076 				buttonIds = [MessageBoxButton.OK];
12077 			break;
12078 			case OKCancel:
12079 				buttons = ["OK", "Cancel"];
12080 				buttonIds = [MessageBoxButton.OK, MessageBoxButton.Cancel];
12081 			break;
12082 			case RetryCancel:
12083 				buttons = ["Retry", "Cancel"];
12084 				buttonIds = [MessageBoxButton.Retry, MessageBoxButton.Cancel];
12085 			break;
12086 			case YesNo:
12087 				buttons = ["Yes", "No"];
12088 				buttonIds = [MessageBoxButton.Yes, MessageBoxButton.No];
12089 			break;
12090 			case YesNoCancel:
12091 				buttons = ["Yes", "No", "Cancel"];
12092 				buttonIds = [MessageBoxButton.Yes, MessageBoxButton.No, MessageBoxButton.Cancel];
12093 			break;
12094 			case RetryCancelContinue:
12095 				buttons = ["Try Again", "Cancel", "Continue"];
12096 				buttonIds = [MessageBoxButton.Retry, MessageBoxButton.Cancel, MessageBoxButton.Continue];
12097 			break;
12098 		}
12099 		auto mb = new MessageBox(message, buttons, buttonIds);
12100 		EventLoop el = EventLoop.get;
12101 		el.run(() { return !mb.win.closed; });
12102 		return mb.buttonPressed;
12103 	}
12104 }
12105 
12106 /// ditto
12107 int messageBox(string message, MessageBoxStyle style = MessageBoxStyle.OK, MessageBoxIcon icon = MessageBoxIcon.None) {
12108 	return messageBox(null, message, style, icon);
12109 }
12110 
12111 
12112 
12113 ///
12114 alias void delegate(Widget handlerAttachedTo, Event event) EventHandler;
12115 
12116 /++
12117 	This is an opaque type you can use to disconnect an event handler when you're no longer interested.
12118 
12119 	History:
12120 		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.
12121 +/
12122 struct EventListener {
12123 	private Widget widget;
12124 	private string event;
12125 	private EventHandler handler;
12126 	private bool useCapture;
12127 
12128 	///
12129 	void disconnect() {
12130 		widget.removeEventListener(this);
12131 	}
12132 }
12133 
12134 /++
12135 	The purpose of this enum was to give a compile-time checked version of various standard event strings.
12136 
12137 	Now, I recommend you use a statically typed event object instead.
12138 
12139 	See_Also: [Event]
12140 +/
12141 enum EventType : string {
12142 	click = "click", ///
12143 
12144 	mouseenter = "mouseenter", ///
12145 	mouseleave = "mouseleave", ///
12146 	mousein = "mousein", ///
12147 	mouseout = "mouseout", ///
12148 	mouseup = "mouseup", ///
12149 	mousedown = "mousedown", ///
12150 	mousemove = "mousemove", ///
12151 
12152 	keydown = "keydown", ///
12153 	keyup = "keyup", ///
12154 	char_ = "char", ///
12155 
12156 	focus = "focus", ///
12157 	blur = "blur", ///
12158 
12159 	triggered = "triggered", ///
12160 
12161 	change = "change", ///
12162 }
12163 
12164 /++
12165 	Represents an event that is currently being processed.
12166 
12167 
12168 	Minigui's event model is based on the web browser. An event has a name, a target,
12169 	and an associated data object. It starts from the window and works its way down through
12170 	the target through all intermediate [Widget]s, triggering capture phase handlers as it goes,
12171 	then goes back up again all the way back to the window, triggering bubble phase handlers. At
12172 	the end, if [Event.preventDefault] has not been called, it calls the target widget's default
12173 	handlers for the event (please note that default handlers will be called even if [Event.stopPropagation]
12174 	was called; that just stops it from calling other handlers in the widget tree, but the default happens
12175 	whenever propagation is done, not only if it gets to the end of the chain).
12176 
12177 	This model has several nice points:
12178 
12179 	$(LIST
12180 		* It is easy to delegate dynamic handlers to a parent. You can have a parent container
12181 		  with event handlers set, then add/remove children as much as you want without needing
12182 		  to manage the event handlers on them - the parent alone can manage everything.
12183 
12184 		* It is easy to create new custom events in your application.
12185 
12186 		* It is familiar to many web developers.
12187 	)
12188 
12189 	There's a few downsides though:
12190 
12191 	$(LIST
12192 		* There's not a lot of type safety.
12193 
12194 		* You don't get a static list of what events a widget can emit.
12195 
12196 		* Tracing where an event got cancelled along the chain can get difficult; the downside of
12197 		  the central delegation benefit is it can be lead to debugging of action at a distance.
12198 	)
12199 
12200 	In May 2021, I started to adjust this model to minigui takes better advantage of D over Javascript
12201 	while keeping the benefits - and most compatibility with - the existing model. The main idea is
12202 	to simply use a D object type which provides a static interface as well as a built-in event name.
12203 	Then, a new static interface allows you to see what an event can emit and attach handlers to it
12204 	similarly to C#, which just forwards to the JS style api. They're fully compatible so you can still
12205 	delegate to a parent and use custom events as well as using the runtime dynamic access, in addition
12206 	to having a little more help from the D compiler and documentation generator.
12207 
12208 	Your code would change like this:
12209 
12210 	---
12211 	// old
12212 	widget.addEventListener("keydown", (Event ev) { ... }, /* optional arg */ useCapture );
12213 
12214 	// new
12215 	widget.addEventListener((KeyDownEvent ev) { ... }, /* optional arg */ useCapture );
12216 	---
12217 
12218 	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.
12219 
12220 	All you have to do is replace the string with a specific Event subclass. It will figure out the event string from the class.
12221 
12222 	Alternatively, you can cast the Event yourself to the appropriate subclass, but it is easier to let the library do it for you!
12223 
12224 	Thus the family of functions are:
12225 
12226 	[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.
12227 
12228 	[Widget.addDirectEventListener] is addEventListener, but only calls the handler if target == this. Useful for something you can't afford to delegate.
12229 
12230 	[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.
12231 
12232 	Let's implement a custom widget that can emit a ChangeEvent describing its `checked` property:
12233 
12234 	---
12235 	class MyCheckbox : Widget {
12236 		/// This gives a chance to document it and generates a convenience function to send it and attach handlers.
12237 		/// It is NOT actually required but should be used whenever possible.
12238 		mixin Emits!(ChangeEvent!bool);
12239 
12240 		this(Widget parent) {
12241 			super(parent);
12242 			setDefaultEventHandler((ClickEvent) { checked = !checked; });
12243 		}
12244 
12245 		private bool _checked;
12246 		@property bool checked() { return _checked; }
12247 		@property void checked(bool set) {
12248 			_checked = set;
12249 			emit!(ChangeEvent!bool)(&checked);
12250 		}
12251 	}
12252 	---
12253 
12254 	## Creating Your Own Events
12255 
12256 	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.
12257 
12258 	---
12259 	class MyEvent : Event {
12260 		this(Widget target) { super(EventString, target); }
12261 		mixin Register; // adds EventString and other reflection information
12262 	}
12263 	---
12264 
12265 	Then declare that it is sent with the [Emits] mixin, so you can use [Widget.emit] to dispatch it.
12266 
12267 	History:
12268 		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.
12269 
12270 		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.
12271 +/
12272 /+
12273 
12274 	## General Conventions
12275 
12276 	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.
12277 
12278 
12279 	## Qt-style signals and slots
12280 
12281 	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.
12282 
12283 	The intention is for events to be used when
12284 
12285 	---
12286 	class Demo : Widget {
12287 		this() {
12288 			myPropertyChanged = Signal!int(this);
12289 		}
12290 		@property myProperty(int v) {
12291 			myPropertyChanged.emit(v);
12292 		}
12293 
12294 		Signal!int myPropertyChanged; // i need to get `this` off it and inspect the name...
12295 		// but it can just genuinely not care about `this` since that's not really passed.
12296 	}
12297 
12298 	class Foo : Widget {
12299 		// the slot uda is not necessary, but it helps the script and ui builder find it.
12300 		@slot void setValue(int v) { ... }
12301 	}
12302 
12303 	demo.myPropertyChanged.connect(&foo.setValue);
12304 	---
12305 
12306 	The Signal type has a disabled default constructor, meaning your widget constructor must pass `this` to it in its constructor.
12307 
12308 	Some events may also wish to implement the Signal interface. These use particular arguments to call a method automatically.
12309 
12310 	class StringChangeEvent : ChangeEvent, Signal!string {
12311 		mixin SignalImpl
12312 	}
12313 
12314 +/
12315 class Event : ReflectableProperties {
12316 	/// Creates an event without populating any members and without sending it. See [dispatch]
12317 	this(string eventName, Widget emittedBy) {
12318 		this.eventName = eventName;
12319 		this.srcElement = emittedBy;
12320 	}
12321 
12322 
12323 	/// Implementations for the [ReflectableProperties] interface/
12324 	void getPropertiesList(scope void delegate(string name) sink) const {}
12325 	/// ditto
12326 	void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink) { }
12327 	/// ditto
12328 	SetPropertyResult setPropertyFromString(string name, scope const(char)[] str, bool strIsJson) {
12329 		return SetPropertyResult.notPermitted;
12330 	}
12331 
12332 
12333 	/+
12334 	/++
12335 		This is an internal implementation detail of [Register] and is subject to be changed or removed at any time without notice.
12336 
12337 		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.
12338 	+/
12339 	protected final void sinkJsonString(string memberName, scope const(char)[] value, scope void delegate(string name, scope const(char)[] value) finalSink) {
12340 		if(value.length == 0) {
12341 			finalSink(memberName, `""`);
12342 			return;
12343 		}
12344 
12345 		char[1024] bufferBacking;
12346 		char[] buffer = bufferBacking;
12347 		int bufferPosition;
12348 
12349 		void sink(char ch) {
12350 			if(bufferPosition >= buffer.length)
12351 				buffer.length = buffer.length + 1024;
12352 			buffer[bufferPosition++] = ch;
12353 		}
12354 
12355 		sink('"');
12356 
12357 		foreach(ch; value) {
12358 			switch(ch) {
12359 				case '\\':
12360 					sink('\\'); sink('\\');
12361 				break;
12362 				case '"':
12363 					sink('\\'); sink('"');
12364 				break;
12365 				case '\n':
12366 					sink('\\'); sink('n');
12367 				break;
12368 				case '\r':
12369 					sink('\\'); sink('r');
12370 				break;
12371 				case '\t':
12372 					sink('\\'); sink('t');
12373 				break;
12374 				default:
12375 					sink(ch);
12376 			}
12377 		}
12378 
12379 		sink('"');
12380 
12381 		finalSink(memberName, buffer[0 .. bufferPosition]);
12382 	}
12383 	+/
12384 
12385 	/+
12386 	enum EventInitiator {
12387 		system,
12388 		minigui,
12389 		user
12390 	}
12391 
12392 	immutable EventInitiator; initiatedBy;
12393 	+/
12394 
12395 	/++
12396 		Events should generally follow the propagation model, but there's some exceptions
12397 		to that rule. If so, they should override this to return false. In that case, only
12398 		bubbling event handlers on the target itself and capturing event handlers on the containing
12399 		window will be called. (That is, [dispatch] will call [sendDirectly] instead of doing the normal
12400 		capture -> target -> bubble process.)
12401 
12402 		History:
12403 			Added May 12, 2021
12404 	+/
12405 	bool propagates() const pure nothrow @nogc @safe {
12406 		return true;
12407 	}
12408 
12409 	/++
12410 		hints as to whether preventDefault will actually do anything. not entirely reliable.
12411 
12412 		History:
12413 			Added May 14, 2021
12414 	+/
12415 	bool cancelable() const pure nothrow @nogc @safe {
12416 		return true;
12417 	}
12418 
12419 	/++
12420 		You can mix this into child class to register some boilerplate. It includes the `EventString`
12421 		member, a constructor, and implementations of the dynamic get data interfaces.
12422 
12423 		If you fail to do this, your event will probably not have full compatibility but it might still work for you.
12424 
12425 
12426 		You can override the default EventString by simply providing your own in the form of
12427 		`enum string EventString = "some.name";` The default is the name of your class and its parent entity
12428 		which provides some namespace protection against conflicts in other libraries while still being fairly
12429 		easy to use.
12430 
12431 		If you provide your own constructor, it will override the default constructor provided here. A constructor
12432 		must call `super(EventString, passed_widget_target)` at some point. The `passed_widget_target` must be the
12433 		first argument to your constructor.
12434 
12435 		History:
12436 			Added May 13, 2021.
12437 	+/
12438 	protected static mixin template Register() {
12439 		public enum string EventString = __traits(identifier, __traits(parent, typeof(this))) ~ "." ~  __traits(identifier, typeof(this));
12440 		this(Widget target) { super(EventString, target); }
12441 
12442 		mixin ReflectableProperties.RegisterGetters;
12443 	}
12444 
12445 	/++
12446 		This is the widget that emitted the event.
12447 
12448 
12449 		The aliased names come from Javascript for ease of web developers to transition in, but they're all synonyms.
12450 
12451 		History:
12452 			The `source` name was added on May 14, 2021. It is a little weird that `source` and `target` are synonyms,
12453 			but that's a side effect of it doing both capture and bubble handlers and people are used to it from the web
12454 			so I don't intend to remove these aliases.
12455 	+/
12456 	Widget source;
12457 	/// ditto
12458 	alias source target;
12459 	/// ditto
12460 	alias source srcElement;
12461 
12462 	Widget relatedTarget; /// Note: likely to be deprecated at some point.
12463 
12464 	/// Prevents the default event handler (if there is one) from being called
12465 	void preventDefault() {
12466 		lastDefaultPrevented = true;
12467 		defaultPrevented = true;
12468 	}
12469 
12470 	/// Stops the event propagation immediately.
12471 	void stopPropagation() {
12472 		propagationStopped = true;
12473 	}
12474 
12475 	private bool defaultPrevented;
12476 	private bool propagationStopped;
12477 	private string eventName;
12478 
12479 	private bool isBubbling;
12480 
12481 	/// 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.
12482 	protected void adjustScrolling() { }
12483 	/// ditto
12484 	protected void adjustClientCoordinates(int deltaX, int deltaY) { }
12485 
12486 	/++
12487 		this sends it only to the target. If you want propagation, use dispatch() instead.
12488 
12489 		This should be made private!!!
12490 
12491 	+/
12492 	void sendDirectly() {
12493 		if(srcElement is null)
12494 			return;
12495 
12496 		// 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.
12497 
12498 		//debug if(eventName != "mousemove" && target !is null && target.parentWindow && target.parentWindow.devTools)
12499 			//target.parentWindow.devTools.log("Event ", eventName, " dispatched directly to ", srcElement);
12500 
12501 		adjustScrolling();
12502 
12503 		if(auto e = target.parentWindow) {
12504 			if(auto handlers = "*" in e.capturingEventHandlers)
12505 			foreach(handler; *handlers)
12506 				if(handler) handler(e, this);
12507 			if(auto handlers = eventName in e.capturingEventHandlers)
12508 			foreach(handler; *handlers)
12509 				if(handler) handler(e, this);
12510 		}
12511 
12512 		auto e = srcElement;
12513 
12514 		if(auto handlers = eventName in e.bubblingEventHandlers)
12515 		foreach(handler; *handlers)
12516 			if(handler) handler(e, this);
12517 
12518 		if(auto handlers = "*" in e.bubblingEventHandlers)
12519 		foreach(handler; *handlers)
12520 			if(handler) handler(e, this);
12521 
12522 		// there's never a default for a catch-all event
12523 		if(!defaultPrevented)
12524 			if(eventName in e.defaultEventHandlers)
12525 				e.defaultEventHandlers[eventName](e, this);
12526 	}
12527 
12528 	/// this dispatches the element using the capture -> target -> bubble process
12529 	void dispatch() {
12530 		if(srcElement is null)
12531 			return;
12532 
12533 		if(!propagates) {
12534 			sendDirectly;
12535 			return;
12536 		}
12537 
12538 		//debug if(eventName != "mousemove" && target !is null && target.parentWindow && target.parentWindow.devTools)
12539 			//target.parentWindow.devTools.log("Event ", eventName, " dispatched to ", srcElement);
12540 
12541 		adjustScrolling();
12542 		// first capture, then bubble
12543 
12544 		Widget[] chain;
12545 		Widget curr = srcElement;
12546 		while(curr) {
12547 			auto l = curr;
12548 			chain ~= l;
12549 			curr = curr.parent;
12550 		}
12551 
12552 		isBubbling = false;
12553 
12554 		foreach_reverse(e; chain) {
12555 			if(auto handlers = "*" in e.capturingEventHandlers)
12556 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
12557 
12558 			if(propagationStopped)
12559 				break;
12560 
12561 			if(auto handlers = eventName in e.capturingEventHandlers)
12562 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
12563 
12564 			// the default on capture should really be to always do nothing
12565 
12566 			//if(!defaultPrevented)
12567 			//	if(eventName in e.defaultEventHandlers)
12568 			//		e.defaultEventHandlers[eventName](e.element, this);
12569 
12570 			if(propagationStopped)
12571 				break;
12572 		}
12573 
12574 		int adjustX;
12575 		int adjustY;
12576 
12577 		isBubbling = true;
12578 		if(!propagationStopped)
12579 		foreach(e; chain) {
12580 			if(auto handlers = eventName in e.bubblingEventHandlers)
12581 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
12582 
12583 			if(propagationStopped)
12584 				break;
12585 
12586 			if(auto handlers = "*" in e.bubblingEventHandlers)
12587 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
12588 
12589 			if(propagationStopped)
12590 				break;
12591 
12592 			if(e.encapsulatedChildren()) {
12593 				adjustClientCoordinates(adjustX, adjustY);
12594 				target = e;
12595 			} else {
12596 				adjustX += e.x;
12597 				adjustY += e.y;
12598 			}
12599 		}
12600 
12601 		if(!defaultPrevented)
12602 		foreach(e; chain) {
12603 			if(eventName in e.defaultEventHandlers)
12604 				e.defaultEventHandlers[eventName](e, this);
12605 		}
12606 	}
12607 
12608 
12609 	/* old compatibility things */
12610 	deprecated("Use some subclass of KeyEventBase instead of plain Event in your handler going forward. WARNING these may crash on non-key events!")
12611 	final @property {
12612 		Key key() { return (cast(KeyEventBase) this).key; }
12613 		KeyEvent originalKeyEvent() { return (cast(KeyEventBase) this).originalKeyEvent; }
12614 
12615 		bool ctrlKey() { return (cast(KeyEventBase) this).ctrlKey; }
12616 		bool altKey() { return (cast(KeyEventBase) this).altKey; }
12617 		bool shiftKey() { return (cast(KeyEventBase) this).shiftKey; }
12618 	}
12619 
12620 	deprecated("Use some subclass of MouseEventBase instead of Event in your handler going forward. WARNING these may crash on non-mouse events!")
12621 	final @property {
12622 		int clientX() { return (cast(MouseEventBase) this).clientX; }
12623 		int clientY() { return (cast(MouseEventBase) this).clientY; }
12624 
12625 		int viewportX() { return (cast(MouseEventBase) this).viewportX; }
12626 		int viewportY() { return (cast(MouseEventBase) this).viewportY; }
12627 
12628 		int button() { return (cast(MouseEventBase) this).button; }
12629 		int buttonLinear() { return (cast(MouseEventBase) this).buttonLinear; }
12630 	}
12631 
12632 	deprecated("Use either a KeyEventBase or a MouseEventBase instead of Event in your handler going forward")
12633 	final @property {
12634 		int state() {
12635 			if(auto meb = cast(MouseEventBase) this)
12636 				return meb.state;
12637 			if(auto keb = cast(KeyEventBase) this)
12638 				return keb.state;
12639 			assert(0);
12640 		}
12641 	}
12642 
12643 	deprecated("Use a CharEvent instead of Event in your handler going forward")
12644 	final @property {
12645 		dchar character() {
12646 			if(auto ce = cast(CharEvent) this)
12647 				return ce.character;
12648 			return dchar.init;
12649 		}
12650 	}
12651 
12652 	// for change events
12653 	@property {
12654 		///
12655 		int intValue() { return 0; }
12656 		///
12657 		string stringValue() { return null; }
12658 	}
12659 }
12660 
12661 /++
12662 	This lets you statically verify you send the events you claim you send and gives you a hook to document them.
12663 
12664 	Please note that a widget may send events not listed as Emits. You can always construct and dispatch
12665 	dynamic and custom events, but the static list helps ensure you get them right.
12666 
12667 	If this is declared, you can use [Widget.emit] to send the event.
12668 
12669 	All events work the same way though, following the capture->widget->bubble model described under [Event].
12670 
12671 	History:
12672 		Added May 4, 2021
12673 +/
12674 mixin template Emits(EventType) {
12675 	import arsd.minigui : EventString;
12676 	static if(is(EventType : Event) && !is(EventType == Event))
12677 		mixin("private EventType[0] emits_" ~ EventStringIdentifier!EventType ~";");
12678 	else
12679 		static assert(0, "You can only emit subclasses of Event");
12680 }
12681 
12682 /// ditto
12683 mixin template Emits(string eventString) {
12684 	mixin("private Event[0] emits_" ~ eventString ~";");
12685 }
12686 
12687 /*
12688 class SignalEvent(string name) : Event {
12689 
12690 }
12691 */
12692 
12693 /++
12694 	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".
12695 
12696 
12697 	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.
12698 
12699 	History:
12700 		Added on May 13, 2021. Prior to that, you'd most likely `addEventListener(EventType.triggered, ...)` to handle similar things.
12701 +/
12702 class CommandEvent : Event {
12703 	enum EventString = "command";
12704 	this(Widget source, string CommandString = EventString) {
12705 		super(CommandString, source);
12706 	}
12707 }
12708 
12709 /++
12710 	A [CommandEvent] is typically actually an instance of these to hold the strongly-typed arguments.
12711 +/
12712 class CommandEventWithArgs(Args...) : CommandEvent {
12713 	this(Widget source, string CommandString, Args args) { super(source, CommandString); this.args = args; }
12714 	Args args;
12715 }
12716 
12717 /++
12718 	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.
12719 
12720 	See [CommandEvent] for more information.
12721 
12722 	Returns:
12723 		The [EventListener] you can use to remove the handler.
12724 +/
12725 EventListener consumesCommand(string CommandString, WidgetType, Args...)(WidgetType w, void delegate(Args) handler) {
12726 	return w.addEventListener(CommandString, (Event ev) {
12727 		if(ev.target is w)
12728 			return; // it does not consume its own commands!
12729 		if(auto cev = cast(CommandEventWithArgs!Args) ev) {
12730 			handler(cev.args);
12731 			ev.stopPropagation();
12732 		}
12733 	});
12734 }
12735 
12736 /++
12737 	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.
12738 +/
12739 void emitCommand(string CommandString, WidgetType, Args...)(WidgetType w, Args args) {
12740 	auto event = new CommandEventWithArgs!Args(w, CommandString, args);
12741 	event.dispatch();
12742 }
12743 
12744 class ResizeEvent : Event {
12745 	enum EventString = "resize";
12746 
12747 	this(Widget target) { super(EventString, target); }
12748 
12749 	override bool propagates() const { return false; }
12750 }
12751 
12752 /++
12753 	ClosingEvent is fired when a user is attempting to close a window. You can `preventDefault` to cancel the close.
12754 
12755 	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.
12756 
12757 	History:
12758 		Added June 21, 2021 (dub v10.1)
12759 +/
12760 class ClosingEvent : Event {
12761 	enum EventString = "closing";
12762 
12763 	this(Widget target) { super(EventString, target); }
12764 
12765 	override bool propagates() const { return false; }
12766 	override bool cancelable() const { return true; }
12767 }
12768 
12769 /// ditto
12770 class ClosedEvent : Event {
12771 	enum EventString = "closed";
12772 
12773 	this(Widget target) { super(EventString, target); }
12774 
12775 	override bool propagates() const { return false; }
12776 	override bool cancelable() const { return false; }
12777 }
12778 
12779 ///
12780 class BlurEvent : Event {
12781 	enum EventString = "blur";
12782 
12783 	// FIXME: related target?
12784 	this(Widget target) { super(EventString, target); }
12785 
12786 	override bool propagates() const { return false; }
12787 }
12788 
12789 ///
12790 class FocusEvent : Event {
12791 	enum EventString = "focus";
12792 
12793 	// FIXME: related target?
12794 	this(Widget target) { super(EventString, target); }
12795 
12796 	override bool propagates() const { return false; }
12797 }
12798 
12799 /++
12800 	FocusInEvent is a FocusEvent that propagates, while FocusOutEvent is a BlurEvent that propagates.
12801 
12802 	History:
12803 		Added July 3, 2021
12804 +/
12805 class FocusInEvent : Event {
12806 	enum EventString = "focusin";
12807 
12808 	// FIXME: related target?
12809 	this(Widget target) { super(EventString, target); }
12810 
12811 	override bool cancelable() const { return false; }
12812 }
12813 
12814 /// ditto
12815 class FocusOutEvent : Event {
12816 	enum EventString = "focusout";
12817 
12818 	// FIXME: related target?
12819 	this(Widget target) { super(EventString, target); }
12820 
12821 	override bool cancelable() const { return false; }
12822 }
12823 
12824 ///
12825 class ScrollEvent : Event {
12826 	enum EventString = "scroll";
12827 	this(Widget target) { super(EventString, target); }
12828 
12829 	override bool cancelable() const { return false; }
12830 }
12831 
12832 /++
12833 	Indicates that a character has been typed by the user. Normally dispatched to the currently focused widget.
12834 
12835 	History:
12836 		Added May 2, 2021. Previously, this was simply a "char" event and `character` as a member of the [Event] base class.
12837 +/
12838 class CharEvent : Event {
12839 	enum EventString = "char";
12840 	this(Widget target, dchar ch) {
12841 		character = ch;
12842 		super(EventString, target);
12843 	}
12844 
12845 	immutable dchar character;
12846 }
12847 
12848 /++
12849 	You should generally use a `ChangeEvent!Type` instead of this directly. See [ChangeEvent] for more information.
12850 +/
12851 abstract class ChangeEventBase : Event {
12852 	enum EventString = "change";
12853 	this(Widget target) {
12854 		super(EventString, target);
12855 	}
12856 
12857 	/+
12858 		// idk where or how exactly i want to do this.
12859 		// i might come back to it later.
12860 
12861 	// If a widget itself broadcasts one of theses itself, it stops propagation going down
12862 	// this way the source doesn't get too confused (think of a nested scroll widget)
12863 	//
12864 	// the idea is like the scroll bar emits a command event saying like "scroll left one line"
12865 	// then you consume that command and change you scroll x position to whatever. then you do
12866 	// some kind of change event that is broadcast back to the children and any horizontal scroll
12867 	// listeners are now able to update, without having an explicit connection between them.
12868 	void broadcastToChildren(string fieldName) {
12869 
12870 	}
12871 	+/
12872 }
12873 
12874 /++
12875 	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.
12876 
12877 
12878 	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).
12879 
12880 	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);`
12881 
12882 	Since it is emitted after the value has already changed, [preventDefault] is unlikely to do anything.
12883 
12884 	History:
12885 		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.
12886 +/
12887 class ChangeEvent(T) : ChangeEventBase {
12888 	this(Widget target, T delegate() getNewValue) {
12889 		assert(getNewValue !is null);
12890 		this.getNewValue = getNewValue;
12891 		super(target);
12892 	}
12893 
12894 	private T delegate() getNewValue;
12895 
12896 	/++
12897 		Gets the new value that just changed.
12898 	+/
12899 	@property T value() {
12900 		return getNewValue();
12901 	}
12902 
12903 	/// compatibility method for old generic Events
12904 	static if(is(immutable T == immutable int))
12905 		override int intValue() { return value; }
12906 	/// ditto
12907 	static if(is(immutable T == immutable string))
12908 		override string stringValue() { return value; }
12909 }
12910 
12911 /++
12912 	Contains shared properties for [KeyDownEvent]s and [KeyUpEvent]s.
12913 
12914 
12915 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
12916 
12917 	History:
12918 		Added May 2, 2021. Previously, its properties were members of the [Event] base class.
12919 +/
12920 abstract class KeyEventBase : Event {
12921 	this(string name, Widget target) {
12922 		super(name, target);
12923 	}
12924 
12925 	// for key events
12926 	Key key; ///
12927 
12928 	KeyEvent originalKeyEvent;
12929 
12930 	/++
12931 		Indicates the current state of the given keyboard modifier keys.
12932 
12933 		History:
12934 			Added to events on April 15, 2020.
12935 	+/
12936 	bool ctrlKey;
12937 
12938 	/// ditto
12939 	bool altKey;
12940 
12941 	/// ditto
12942 	bool shiftKey;
12943 
12944 	/++
12945 		The raw bitflags that are parsed out into [ctrlKey], [altKey], and [shiftKey].
12946 
12947 		See [arsd.simpledisplay.ModifierState] for other possible flags.
12948 	+/
12949 	int state;
12950 
12951 	mixin Register;
12952 }
12953 
12954 /++
12955 	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].
12956 
12957 
12958 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
12959 
12960 	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.
12961 
12962 	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.
12963 
12964 	See_Also: [KeyUpEvent], [CharEvent]
12965 
12966 	History:
12967 		Added May 2, 2021. Previously, it was only seen as the base [Event] class on "keydown" event listeners.
12968 +/
12969 class KeyDownEvent : KeyEventBase {
12970 	enum EventString = "keydown";
12971 	this(Widget target) { super(EventString, target); }
12972 }
12973 
12974 /++
12975 	Indicates that the user has released a key on the keyboard. For available properties, see [KeyEventBase].
12976 
12977 
12978 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
12979 
12980 	See_Also: [KeyDownEvent], [CharEvent]
12981 
12982 	History:
12983 		Added May 2, 2021. Previously, it was only seen as the base [Event] class on "keyup" event listeners.
12984 +/
12985 class KeyUpEvent : KeyEventBase {
12986 	enum EventString = "keyup";
12987 	this(Widget target) { super(EventString, target); }
12988 }
12989 
12990 /++
12991 	Contains shared properties for various mouse events;
12992 
12993 
12994 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
12995 
12996 	History:
12997 		Added May 2, 2021. Previously, its properties were members of the [Event] base class.
12998 +/
12999 abstract class MouseEventBase : Event {
13000 	this(string name, Widget target) {
13001 		super(name, target);
13002 	}
13003 
13004 	// for mouse events
13005 	int clientX; /// The mouse event location relative to the target widget
13006 	int clientY; /// ditto
13007 
13008 	int viewportX; /// The mouse event location relative to the window origin
13009 	int viewportY; /// ditto
13010 
13011 	int button; /// See: [MouseEvent.button]
13012 	int buttonLinear; /// See: [MouseEvent.buttonLinear]
13013 
13014 	/++
13015 		Indicates the current state of the given keyboard modifier keys.
13016 
13017 		History:
13018 			Added to mouse events on September 28, 2010.
13019 	+/
13020 	bool ctrlKey;
13021 
13022 	/// ditto
13023 	bool altKey;
13024 
13025 	/// ditto
13026 	bool shiftKey;
13027 
13028 
13029 
13030 	int state; ///
13031 
13032 	/++
13033 		for consistent names with key event. 
13034 
13035 		History:
13036 			Added September 28, 2021 (dub v10.3)
13037 	+/
13038 	alias modifierState = state;
13039 
13040 	/++
13041 		Mouse wheel movement sends down/up/click events just like other buttons clicking. This method is to help you filter that out.
13042 
13043 		History:
13044 			Added May 15, 2021
13045 	+/
13046 	bool isMouseWheel() {
13047 		return button == MouseButton.wheelUp || button == MouseButton.wheelDown;
13048 	}
13049 
13050 	// private
13051 	override void adjustClientCoordinates(int deltaX, int deltaY) {
13052 		clientX += deltaX;
13053 		clientY += deltaY;
13054 	}
13055 
13056 	override void adjustScrolling() {
13057 	version(custom_widgets) { // TEMP
13058 		viewportX = clientX;
13059 		viewportY = clientY;
13060 		if(auto se = cast(ScrollableWidget) srcElement) {
13061 			clientX += se.scrollOrigin.x;
13062 			clientY += se.scrollOrigin.y;
13063 		} else if(auto se = cast(ScrollableContainerWidget) srcElement) {
13064 			//clientX += se.scrollX_;
13065 			//clientY += se.scrollY_;
13066 		}
13067 	}
13068 	}
13069 
13070 	mixin Register;
13071 }
13072 
13073 /++
13074 	Indicates that the user has worked with the mouse over your widget. For available properties, see [MouseEventBase].
13075 
13076 
13077 	$(WARNING
13078 		Important: MouseDownEvent, MouseUpEvent, ClickEvent, and DoubleClickEvent are all sent for all mouse buttons and
13079 		for wheel movement! You should check the [MouseEventBase.button|button] property in most your handlers to get correct
13080 		behavior.
13081 	)
13082 
13083 	[MouseDownEvent] is sent when the user presses a mouse button. It is also sent on mouse wheel movement.
13084 
13085 	[MouseUpEvent] is sent when the user releases a mouse button.
13086 
13087 	[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.)
13088 
13089 	[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.
13090 
13091 	[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.
13092 
13093 	[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.
13094 
13095 	[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.
13096 
13097 	[MouseEnterEvent] is sent when the mouse enters the bounding box of a widget.
13098 
13099 	[MouseLeaveEvent] is sent when the mouse leaves the bounding box of a widget.
13100 
13101 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
13102 
13103 	Rationale:
13104 
13105 		If you only want to do drag, mousedown/up works just fine being consistently sent.
13106 
13107 		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).
13108 
13109 		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.
13110 
13111 	History:
13112 		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.
13113 +/
13114 class MouseUpEvent : MouseEventBase {
13115 	enum EventString = "mouseup"; ///
13116 	this(Widget target) { super(EventString, target); }
13117 }
13118 /// ditto
13119 class MouseDownEvent : MouseEventBase {
13120 	enum EventString = "mousedown"; ///
13121 	this(Widget target) { super(EventString, target); }
13122 }
13123 /// ditto
13124 class MouseMoveEvent : MouseEventBase {
13125 	enum EventString = "mousemove"; ///
13126 	this(Widget target) { super(EventString, target); }
13127 }
13128 /// ditto
13129 class ClickEvent : MouseEventBase {
13130 	enum EventString = "click"; ///
13131 	this(Widget target) { super(EventString, target); }
13132 }
13133 /// ditto
13134 class DoubleClickEvent : MouseEventBase {
13135 	enum EventString = "dblclick"; ///
13136 	this(Widget target) { super(EventString, target); }
13137 }
13138 /// ditto
13139 class MouseOverEvent : Event {
13140 	enum EventString = "mouseover"; ///
13141 	this(Widget target) { super(EventString, target); }
13142 }
13143 /// ditto
13144 class MouseOutEvent : Event {
13145 	enum EventString = "mouseout"; ///
13146 	this(Widget target) { super(EventString, target); }
13147 }
13148 /// ditto
13149 class MouseEnterEvent : Event {
13150 	enum EventString = "mouseenter"; ///
13151 	this(Widget target) { super(EventString, target); }
13152 
13153 	override bool propagates() const { return false; }
13154 }
13155 /// ditto
13156 class MouseLeaveEvent : Event {
13157 	enum EventString = "mouseleave"; ///
13158 	this(Widget target) { super(EventString, target); }
13159 
13160 	override bool propagates() const { return false; }
13161 }
13162 
13163 private bool isAParentOf(Widget a, Widget b) {
13164 	if(a is null || b is null)
13165 		return false;
13166 
13167 	while(b !is null) {
13168 		if(a is b)
13169 			return true;
13170 		b = b.parent;
13171 	}
13172 
13173 	return false;
13174 }
13175 
13176 private struct WidgetAtPointResponse {
13177 	Widget widget;
13178 
13179 	// x, y relative to the widget in the response.
13180 	int x;
13181 	int y;
13182 }
13183 
13184 private WidgetAtPointResponse widgetAtPoint(Widget starting, int x, int y) {
13185 	assert(starting !is null);
13186 
13187 	starting.addScrollPosition(x, y);
13188 
13189 	auto child = starting.getChildAtPosition(x, y);
13190 	while(child) {
13191 		if(child.hidden)
13192 			continue;
13193 		starting = child;
13194 		x -= child.x;
13195 		y -= child.y;
13196 		auto r = starting.widgetAtPoint(x, y);//starting.getChildAtPosition(x, y);
13197 		child = r.widget;
13198 		if(child is starting)
13199 			break;
13200 	}
13201 	return WidgetAtPointResponse(starting, x, y);
13202 }
13203 
13204 version(win32_widgets) {
13205 private:
13206 	import core.sys.windows.commctrl;
13207 
13208 	pragma(lib, "comctl32");
13209 	shared static this() {
13210 		// http://msdn.microsoft.com/en-us/library/windows/desktop/bb775507(v=vs.85).aspx
13211 		INITCOMMONCONTROLSEX ic;
13212 		ic.dwSize = cast(DWORD) ic.sizeof;
13213 		ic.dwICC = ICC_UPDOWN_CLASS | ICC_WIN95_CLASSES | ICC_BAR_CLASSES | ICC_PROGRESS_CLASS | ICC_COOL_CLASSES | ICC_STANDARD_CLASSES | ICC_USEREX_CLASSES;
13214 		if(!InitCommonControlsEx(&ic)) {
13215 			//import std.stdio; writeln("ICC failed");
13216 		}
13217 	}
13218 
13219 
13220 	// everything from here is just win32 headers copy pasta
13221 private:
13222 extern(Windows):
13223 
13224 	alias HANDLE HMENU;
13225 	HMENU CreateMenu();
13226 	bool SetMenu(HWND, HMENU);
13227 	HMENU CreatePopupMenu();
13228 	enum MF_POPUP = 0x10;
13229 	enum MF_STRING = 0;
13230 
13231 
13232 	BOOL InitCommonControlsEx(const INITCOMMONCONTROLSEX*);
13233 	struct INITCOMMONCONTROLSEX {
13234 		DWORD dwSize;
13235 		DWORD dwICC;
13236 	}
13237 	enum HINST_COMMCTRL = cast(HINSTANCE) (-1);
13238 enum {
13239         IDB_STD_SMALL_COLOR,
13240         IDB_STD_LARGE_COLOR,
13241         IDB_VIEW_SMALL_COLOR = 4,
13242         IDB_VIEW_LARGE_COLOR = 5
13243 }
13244 enum {
13245         STD_CUT,
13246         STD_COPY,
13247         STD_PASTE,
13248         STD_UNDO,
13249         STD_REDOW,
13250         STD_DELETE,
13251         STD_FILENEW,
13252         STD_FILEOPEN,
13253         STD_FILESAVE,
13254         STD_PRINTPRE,
13255         STD_PROPERTIES,
13256         STD_HELP,
13257         STD_FIND,
13258         STD_REPLACE,
13259         STD_PRINT // = 14
13260 }
13261 
13262 alias HANDLE HIMAGELIST;
13263 	HIMAGELIST ImageList_Create(int, int, UINT, int, int);
13264 	int ImageList_Add(HIMAGELIST, HBITMAP, HBITMAP);
13265         BOOL ImageList_Destroy(HIMAGELIST);
13266 
13267 uint MAKELONG(ushort a, ushort b) {
13268         return cast(uint) ((b << 16) | a);
13269 }
13270 
13271 
13272 struct TBBUTTON {
13273 	int   iBitmap;
13274 	int   idCommand;
13275 	BYTE  fsState;
13276 	BYTE  fsStyle;
13277 	version(Win64)
13278 	BYTE[6] bReserved;
13279 	else
13280 	BYTE[2]  bReserved;
13281 	DWORD dwData;
13282 	INT_PTR   iString;
13283 }
13284 
13285 	enum {
13286 		TB_ADDBUTTONSA   = WM_USER + 20,
13287 		TB_INSERTBUTTONA = WM_USER + 21,
13288 		TB_GETIDEALSIZE = WM_USER + 99,
13289 	}
13290 
13291 struct SIZE {
13292 	LONG cx;
13293 	LONG cy;
13294 }
13295 
13296 
13297 enum {
13298 	TBSTATE_CHECKED       = 1,
13299 	TBSTATE_PRESSED       = 2,
13300 	TBSTATE_ENABLED       = 4,
13301 	TBSTATE_HIDDEN        = 8,
13302 	TBSTATE_INDETERMINATE = 16,
13303 	TBSTATE_WRAP          = 32
13304 }
13305 
13306 
13307 
13308 enum {
13309 	ILC_COLOR    = 0,
13310 	ILC_COLOR4   = 4,
13311 	ILC_COLOR8   = 8,
13312 	ILC_COLOR16  = 16,
13313 	ILC_COLOR24  = 24,
13314 	ILC_COLOR32  = 32,
13315 	ILC_COLORDDB = 254,
13316 	ILC_MASK     = 1,
13317 	ILC_PALETTE  = 2048
13318 }
13319 
13320 
13321 alias TBBUTTON*       PTBBUTTON, LPTBBUTTON;
13322 
13323 
13324 enum {
13325 	TB_ENABLEBUTTON          = WM_USER + 1,
13326 	TB_CHECKBUTTON,
13327 	TB_PRESSBUTTON,
13328 	TB_HIDEBUTTON,
13329 	TB_INDETERMINATE, //     = WM_USER + 5,
13330 	TB_ISBUTTONENABLED       = WM_USER + 9,
13331 	TB_ISBUTTONCHECKED,
13332 	TB_ISBUTTONPRESSED,
13333 	TB_ISBUTTONHIDDEN,
13334 	TB_ISBUTTONINDETERMINATE, // = WM_USER + 13,
13335 	TB_SETSTATE              = WM_USER + 17,
13336 	TB_GETSTATE              = WM_USER + 18,
13337 	TB_ADDBITMAP             = WM_USER + 19,
13338 	TB_DELETEBUTTON          = WM_USER + 22,
13339 	TB_GETBUTTON,
13340 	TB_BUTTONCOUNT,
13341 	TB_COMMANDTOINDEX,
13342 	TB_SAVERESTOREA,
13343 	TB_CUSTOMIZE,
13344 	TB_ADDSTRINGA,
13345 	TB_GETITEMRECT,
13346 	TB_BUTTONSTRUCTSIZE,
13347 	TB_SETBUTTONSIZE,
13348 	TB_SETBITMAPSIZE,
13349 	TB_AUTOSIZE, //          = WM_USER + 33,
13350 	TB_GETTOOLTIPS           = WM_USER + 35,
13351 	TB_SETTOOLTIPS           = WM_USER + 36,
13352 	TB_SETPARENT             = WM_USER + 37,
13353 	TB_SETROWS               = WM_USER + 39,
13354 	TB_GETROWS,
13355 	TB_GETBITMAPFLAGS,
13356 	TB_SETCMDID,
13357 	TB_CHANGEBITMAP,
13358 	TB_GETBITMAP,
13359 	TB_GETBUTTONTEXTA,
13360 	TB_REPLACEBITMAP, //     = WM_USER + 46,
13361 	TB_GETBUTTONSIZE         = WM_USER + 58,
13362 	TB_SETBUTTONWIDTH        = WM_USER + 59,
13363 	TB_GETBUTTONTEXTW        = WM_USER + 75,
13364 	TB_SAVERESTOREW          = WM_USER + 76,
13365 	TB_ADDSTRINGW            = WM_USER + 77,
13366 }
13367 
13368 extern(Windows)
13369 BOOL EnumChildWindows(HWND, WNDENUMPROC, LPARAM);
13370 
13371 alias extern(Windows) BOOL function (HWND, LPARAM) WNDENUMPROC;
13372 
13373 
13374 	enum {
13375 		TB_SETINDENT = WM_USER + 47,
13376 		TB_SETIMAGELIST,
13377 		TB_GETIMAGELIST,
13378 		TB_LOADIMAGES,
13379 		TB_GETRECT,
13380 		TB_SETHOTIMAGELIST,
13381 		TB_GETHOTIMAGELIST,
13382 		TB_SETDISABLEDIMAGELIST,
13383 		TB_GETDISABLEDIMAGELIST,
13384 		TB_SETSTYLE,
13385 		TB_GETSTYLE,
13386 		//TB_GETBUTTONSIZE,
13387 		//TB_SETBUTTONWIDTH,
13388 		TB_SETMAXTEXTROWS,
13389 		TB_GETTEXTROWS // = WM_USER + 61
13390 	}
13391 
13392 enum {
13393 	CCM_FIRST            = 0x2000,
13394 	CCM_LAST             = CCM_FIRST + 0x200,
13395 	CCM_SETBKCOLOR       = 8193,
13396 	CCM_SETCOLORSCHEME   = 8194,
13397 	CCM_GETCOLORSCHEME   = 8195,
13398 	CCM_GETDROPTARGET    = 8196,
13399 	CCM_SETUNICODEFORMAT = 8197,
13400 	CCM_GETUNICODEFORMAT = 8198,
13401 	CCM_SETVERSION       = 0x2007,
13402 	CCM_GETVERSION       = 0x2008,
13403 	CCM_SETNOTIFYWINDOW  = 0x2009
13404 }
13405 
13406 
13407 enum {
13408 	PBM_SETRANGE     = WM_USER + 1,
13409 	PBM_SETPOS,
13410 	PBM_DELTAPOS,
13411 	PBM_SETSTEP,
13412 	PBM_STEPIT,   // = WM_USER + 5
13413 	PBM_SETRANGE32   = 1030,
13414 	PBM_GETRANGE,
13415 	PBM_GETPOS,
13416 	PBM_SETBARCOLOR, // = 1033
13417 	PBM_SETBKCOLOR   = CCM_SETBKCOLOR
13418 }
13419 
13420 enum {
13421 	PBS_SMOOTH   = 1,
13422 	PBS_VERTICAL = 4
13423 }
13424 
13425 enum {
13426         ICC_LISTVIEW_CLASSES = 1,
13427         ICC_TREEVIEW_CLASSES = 2,
13428         ICC_BAR_CLASSES      = 4,
13429         ICC_TAB_CLASSES      = 8,
13430         ICC_UPDOWN_CLASS     = 16,
13431         ICC_PROGRESS_CLASS   = 32,
13432         ICC_HOTKEY_CLASS     = 64,
13433         ICC_ANIMATE_CLASS    = 128,
13434         ICC_WIN95_CLASSES    = 255,
13435         ICC_DATE_CLASSES     = 256,
13436         ICC_USEREX_CLASSES   = 512,
13437         ICC_COOL_CLASSES     = 1024,
13438 	ICC_STANDARD_CLASSES = 0x00004000,
13439 }
13440 
13441 	enum WM_USER = 1024;
13442 }
13443 
13444 version(win32_widgets)
13445 	pragma(lib, "comdlg32");
13446 
13447 
13448 ///
13449 enum GenericIcons : ushort {
13450 	None, ///
13451 	// these happen to match the win32 std icons numerically if you just subtract one from the value
13452 	Cut, ///
13453 	Copy, ///
13454 	Paste, ///
13455 	Undo, ///
13456 	Redo, ///
13457 	Delete, ///
13458 	New, ///
13459 	Open, ///
13460 	Save, ///
13461 	PrintPreview, ///
13462 	Properties, ///
13463 	Help, ///
13464 	Find, ///
13465 	Replace, ///
13466 	Print, ///
13467 }
13468 
13469 enum FileDialogType {
13470 	Automatic,
13471 	Open,
13472 	Save
13473 }
13474 string previousFileReferenced;
13475 
13476 /++
13477 	Used in automatic menu functions to indicate that the user should be able to browse for a file.
13478 
13479 	Params:
13480 		storage = an alias to a `static string` variable that stores the last file referenced. It will
13481 		use this to pre-fill the dialog with a suggestion.
13482 
13483 		Please note that it MUST be `static` or you will get compile errors.
13484 
13485 		filters = the filters param to [getFileName]
13486 
13487 		type = the type if dialog to show. If `FileDialogType.Automatic`, it the driver code will
13488 		guess based on the function name. If it has the word "Save" or "Export" in it, it will show
13489 		a save dialog box. Otherwise, it will show an open dialog box.
13490 +/
13491 struct FileName(alias storage = previousFileReferenced, string[] filters = null, FileDialogType type = FileDialogType.Automatic) {
13492 	string name;
13493 	alias name this;
13494 }
13495 
13496 /++
13497 	History:
13498 		The dialog itself on Linux was modified on December 2, 2021 to include
13499 		a directory picker in addition to the command line completion view.
13500 	Future_directions:
13501 		I want to add some kind of custom preview and maybe thumbnail thing in the future,
13502 		at least on Linux, maybe on Windows too.
13503 +/
13504 void getOpenFileName(
13505 	void delegate(string) onOK,
13506 	string prefilledName = null,
13507 	string[] filters = null,
13508 	void delegate() onCancel = null,
13509 )
13510 {
13511 	return getFileName(true, onOK, prefilledName, filters, onCancel);
13512 }
13513 
13514 /++
13515 	History:
13516 		onCancel was added November 6, 2021.
13517 +/
13518 void getSaveFileName(
13519 	void delegate(string) onOK,
13520 	string prefilledName = null,
13521 	string[] filters = null,
13522 	void delegate() onCancel = null,
13523 )
13524 {
13525 	return getFileName(false, onOK, prefilledName, filters, onCancel);
13526 }
13527 
13528 void getFileName(
13529 	bool openOrSave,
13530 	void delegate(string) onOK,
13531 	string prefilledName = null,
13532 	string[] filters = null, // format here is like ["Text files\0*.txt;*.text", "Image files\0*.png;*.jpg"]
13533 	void delegate() onCancel = null,
13534 )
13535 {
13536 
13537 	version(win32_widgets) {
13538 		import core.sys.windows.commdlg;
13539 	/*
13540 	Ofn.lStructSize = sizeof(OPENFILENAME); 
13541 	Ofn.hwndOwner = hWnd; 
13542 	Ofn.lpstrFilter = szFilter; 
13543 	Ofn.lpstrFile= szFile; 
13544 	Ofn.nMaxFile = sizeof(szFile)/ sizeof(*szFile); 
13545 	Ofn.lpstrFileTitle = szFileTitle; 
13546 	Ofn.nMaxFileTitle = sizeof(szFileTitle); 
13547 	Ofn.lpstrInitialDir = (LPSTR)NULL; 
13548 	Ofn.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT; 
13549 	Ofn.lpstrTitle = szTitle; 
13550 	 */
13551 
13552 
13553 		wchar[1024] file = 0;
13554 		wchar[1024] filterBuffer = 0;
13555 		makeWindowsString(prefilledName, file[]);
13556 		OPENFILENAME ofn;
13557 		ofn.lStructSize = ofn.sizeof;
13558 		if(filters.length) {
13559 			string filter;
13560 			foreach(i, f; filters) {
13561 				filter ~= f;
13562 				filter ~= "\0";
13563 			}
13564 			filter ~= "\0";
13565 			ofn.lpstrFilter = makeWindowsString(filter, filterBuffer[], 0 /* already terminated */).ptr;
13566 		}
13567 		ofn.lpstrFile = file.ptr;
13568 		ofn.nMaxFile = file.length;
13569 		if(openOrSave ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn)) 
13570 		{
13571 			string okString = makeUtf8StringFromWindowsString(ofn.lpstrFile);
13572 			if(okString.length && okString[$-1] == '\0')
13573 				okString = okString[0..$-1];
13574 			onOK(okString);
13575 		} else {
13576 			if(onCancel)
13577 				onCancel();
13578 		}
13579 	} else version(custom_widgets) {
13580 		if(filters.length == 0)
13581 			filters = ["All Files\0*.*"];
13582 		auto picker = new FilePicker(prefilledName, filters);
13583 		picker.onOK = onOK;
13584 		picker.onCancel = onCancel;
13585 		picker.show();
13586 	}
13587 }
13588 
13589 version(custom_widgets)
13590 private
13591 class FilePicker : Dialog {
13592 	void delegate(string) onOK;
13593 	void delegate() onCancel;
13594 	LineEdit lineEdit;
13595 
13596 	enum GetFilesResult {
13597 		success,
13598 		fileNotFound
13599 	}
13600 	static GetFilesResult getFiles(string cwd, scope void delegate(string name, bool isDirectory) dg) {
13601 		version(Windows) {
13602 			WIN32_FIND_DATA data;
13603 			WCharzBuffer search = WCharzBuffer(cwd ~ "/*");
13604 			auto handle = FindFirstFileW(search.ptr, &data);
13605 			scope(exit) if(handle !is INVALID_HANDLE_VALUE) FindClose(handle);
13606 			if(handle is INVALID_HANDLE_VALUE) {
13607 				if(GetLastError() == ERROR_FILE_NOT_FOUND)
13608 					return GetFilesResult.fileNotFound;
13609 				throw new WindowsApiException("FindFirstFileW");
13610 			}
13611 
13612 			try_more:
13613 
13614 			string name = makeUtf8StringFromWindowsString(data.cFileName[0 .. findIndexOfZero(data.cFileName[])]);
13615 
13616 			dg(name, (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? true : false);
13617 
13618 			auto ret = FindNextFileW(handle, &data);
13619 			if(ret == 0) {
13620 				if(GetLastError() == ERROR_NO_MORE_FILES)
13621 					return GetFilesResult.success;
13622 				throw new WindowsApiException("FindNextFileW");
13623 			}
13624 
13625 			goto try_more;
13626 
13627 		} else version(Posix) {
13628 			import core.sys.posix.dirent;
13629 			auto dir = opendir((cwd ~ "\0").ptr);
13630 			scope(exit)
13631 				if(dir) closedir(dir);
13632 			if(dir is null)
13633 				throw new ErrnoApiException("opendir [" ~ cwd ~ "]");
13634 
13635 			auto dirent = readdir(dir);
13636 			if(dirent is null)
13637 				return GetFilesResult.fileNotFound;
13638 
13639 			try_more:
13640 
13641 			string name = dirent.d_name[0 .. findIndexOfZero(dirent.d_name[])].idup;
13642 
13643 			dg(name, dirent.d_type == DT_DIR);
13644 
13645 			dirent = readdir(dir);
13646 			if(dirent is null)
13647 				return GetFilesResult.success;
13648 
13649 			goto try_more;
13650 		} else static assert(0);
13651 	}
13652 
13653 	// returns common prefix
13654 	string loadFiles(string cwd, string[] filters...) {
13655 		string[] files;
13656 		string[] dirs;
13657 
13658 		string commonPrefix;
13659 
13660 		getFiles(cwd, (string name, bool isDirectory) {
13661 			if(name == ".")
13662 				return; // skip this as unnecessary
13663 			if(isDirectory)
13664 				dirs ~= name;
13665 			else {
13666 				foreach(filter; filters)
13667 				if(
13668 					filter.length <= 1 ||
13669 					filter == "*.*" ||
13670 					(filter[0] == '*' && name.endsWith(filter[1 .. $])) ||
13671 					(filter[$-1] == '*' && name.startsWith(filter[0 .. $ - 1]))
13672 				)
13673 				{
13674 					files ~= name;
13675 
13676 					if(filter.length > 0 && filter[$-1] == '*') {
13677 						if(commonPrefix is null) {
13678 							commonPrefix = name;
13679 						} else {
13680 							foreach(idx, char i; name) {
13681 								if(idx >= commonPrefix.length || i != commonPrefix[idx]) {
13682 									commonPrefix = commonPrefix[0 .. idx];
13683 									break;
13684 								}
13685 							}
13686 						}
13687 					}
13688 
13689 					break;
13690 				}
13691 			}
13692 		});
13693 
13694 		extern(C) static int comparator(scope const void* a, scope const void* b) {
13695 			auto sa = *cast(string*) a;
13696 			auto sb = *cast(string*) b;
13697 
13698 			for(int i = 0; i < sa.length; i++) {
13699 				if(i == sb.length)
13700 					return 1;
13701 				return sa[i] - sb[i];
13702 			}
13703 
13704 			return 0;
13705 		}
13706 
13707 		nonPhobosSort(files, &comparator);
13708 		nonPhobosSort(dirs, &comparator);
13709 
13710 		listWidget.clear();
13711 		dirWidget.clear();
13712 		foreach(name; dirs)
13713 			dirWidget.addOption(name);
13714 		foreach(name; files)
13715 			listWidget.addOption(name);
13716 
13717 		return commonPrefix;
13718 	}
13719 
13720 	ListWidget listWidget;
13721 	ListWidget dirWidget;
13722 
13723 	string currentDirectory;
13724 	string[] processedFilters;
13725 
13726 	//string[] filters = null, // format here is like ["Text files\0*.txt;*.text", "Image files\n*.png;*.jpg"]
13727 	this(string prefilledName, string[] filters, Window owner = null) {
13728 		super(300, 200, "Choose File..."); // owner);
13729 
13730 		foreach(filter; filters) {
13731 			while(filter.length && filter[0] != 0) {
13732 				filter = filter[1 .. $];
13733 			}
13734 			if(filter.length)
13735 				filter = filter[1 .. $]; // trim off the 0
13736 
13737 			while(filter.length) {
13738 				int idx = 0;
13739 				while(idx < filter.length && filter[idx] != ';') {
13740 					idx++;
13741 				}
13742 
13743 				processedFilters ~= filter[0 .. idx];
13744 				if(idx < filter.length)
13745 					idx++; // skip the ;
13746 				filter = filter[idx .. $];
13747 			}
13748 		}
13749 
13750 		currentDirectory = ".";
13751 
13752 		{
13753 			auto hl = new HorizontalLayout(this);
13754 			dirWidget = new ListWidget(hl);
13755 			listWidget = new ListWidget(hl);
13756 
13757 			// double click events normally trigger something else but
13758 			// here user might be clicking kinda fast and we'd rather just
13759 			// keep it
13760 			dirWidget.addEventListener((scope DoubleClickEvent dev) {
13761 				auto ce = new ChangeEvent!void(dirWidget, () {});
13762 				ce.dispatch();
13763 			});
13764 
13765 			dirWidget.addEventListener((scope ChangeEvent!void sce) {
13766 				string v;
13767 				foreach(o; dirWidget.options)
13768 					if(o.selected) {
13769 						v = o.label;
13770 						break;
13771 					}
13772 				if(v.length) {
13773 					currentDirectory ~= "/" ~ v;
13774 					loadFiles(currentDirectory, processedFilters);
13775 				}
13776 			});
13777 
13778 			// double click here, on the other hand, selects the file
13779 			// and moves on
13780 			listWidget.addEventListener((scope DoubleClickEvent dev) {
13781 				OK();
13782 			});
13783 		}
13784 
13785 		lineEdit = new LineEdit(this);
13786 		lineEdit.focus();
13787 		lineEdit.addEventListener(delegate(CharEvent event) {
13788 			if(event.character == '\t' || event.character == '\n')
13789 				event.preventDefault();
13790 		});
13791 
13792 		listWidget.addEventListener(EventType.change, () {
13793 			foreach(o; listWidget.options)
13794 				if(o.selected)
13795 					lineEdit.content = o.label;
13796 		});
13797 
13798 		loadFiles(currentDirectory, processedFilters);
13799 
13800 		lineEdit.addEventListener((KeyDownEvent event) {
13801 			if(event.key == Key.Tab) {
13802 
13803 				auto current = lineEdit.content;
13804 				if(current.length >= 2 && current[0 ..2] == "./")
13805 					current = current[2 .. $];
13806 
13807 				auto commonPrefix = loadFiles(".", current ~ "*");
13808 
13809 				if(commonPrefix.length)
13810 					lineEdit.content = commonPrefix;
13811 
13812 				// FIXME: if that is a directory, add the slash? or even go inside?
13813 
13814 				event.preventDefault();
13815 			}
13816 		});
13817 
13818 		lineEdit.content = prefilledName;
13819 
13820 		auto hl = new HorizontalLayout(60, this);
13821 		auto cancelButton = new Button("Cancel", hl);
13822 		auto okButton = new Button("OK", hl);
13823 
13824 		cancelButton.addEventListener(EventType.triggered, &Cancel);
13825 		okButton.addEventListener(EventType.triggered, &OK);
13826 
13827 		this.addEventListener((KeyDownEvent event) {
13828 			if(event.key == Key.Enter || event.key == Key.PadEnter) {
13829 				event.preventDefault();
13830 				OK();
13831 			}
13832 			if(event.key == Key.Escape)
13833 				Cancel();
13834 		});
13835 
13836 	}
13837 
13838 	override void OK() {
13839 		if(lineEdit.content.length) {
13840 			string accepted;
13841 			auto c = lineEdit.content;
13842 			if(c.length && c[0] == '/')
13843 				accepted = c;
13844 			else
13845 				accepted = currentDirectory ~ "/" ~ lineEdit.content;
13846 
13847 			if(isDir(accepted)) {
13848 				// FIXME: would be kinda nice to support ~ and collapse these paths too
13849 				// FIXME: would also be nice to actually show the "Looking in..." directory and maybe the filters but later.
13850 				currentDirectory = accepted;
13851 				loadFiles(currentDirectory, processedFilters);
13852 				lineEdit.content = "";
13853 				return;
13854 			}
13855 
13856 			if(onOK)
13857 				onOK(accepted);
13858 		}
13859 		close();
13860 	}
13861 
13862 	override void Cancel() {
13863 		if(onCancel)
13864 			onCancel();
13865 		close();
13866 	}
13867 }
13868 
13869 private bool isDir(string name) {
13870 	version(Windows) {
13871 		auto ws = WCharzBuffer(name);
13872 		auto ret = GetFileAttributesW(ws.ptr);
13873 		if(ret == INVALID_FILE_ATTRIBUTES)
13874 			return false;
13875 		return (ret & FILE_ATTRIBUTE_DIRECTORY) != 0;
13876 	} else version(Posix) {
13877 		import core.sys.posix.sys.stat;
13878 		stat_t buf;
13879 		auto ret = stat((name ~ '\0').ptr, &buf);
13880 		if(ret == -1)
13881 			return false; // I could probably check more specific errors tbh
13882 		return (buf.st_mode & S_IFMT) == S_IFDIR;
13883 	} else return false;
13884 }
13885 
13886 /*
13887 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775947%28v=vs.85%29.aspx#check_boxes
13888 http://msdn.microsoft.com/en-us/library/windows/desktop/ms633574%28v=vs.85%29.aspx
13889 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775943%28v=vs.85%29.aspx
13890 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775951%28v=vs.85%29.aspx
13891 http://msdn.microsoft.com/en-us/library/windows/desktop/ms632680%28v=vs.85%29.aspx
13892 http://msdn.microsoft.com/en-us/library/windows/desktop/ms644996%28v=vs.85%29.aspx#message_box
13893 http://www.sbin.org/doc/Xlib/chapt_03.html
13894 
13895 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760433%28v=vs.85%29.aspx
13896 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760446%28v=vs.85%29.aspx
13897 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760443%28v=vs.85%29.aspx
13898 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760476%28v=vs.85%29.aspx
13899 */
13900 
13901 
13902 // These are all for setMenuAndToolbarFromAnnotatedCode
13903 /// This item in the menu will be preceded by a separator line
13904 /// Group: generating_from_code
13905 struct separator {}
13906 deprecated("It was misspelled, use separator instead") alias seperator = separator;
13907 /// Program-wide keyboard shortcut to trigger the action
13908 /// Group: generating_from_code
13909 struct accelerator { string keyString; }
13910 /// tells which menu the action will be on
13911 /// Group: generating_from_code
13912 struct menu { string name; }
13913 /// Describes which toolbar section the action appears on
13914 /// Group: generating_from_code
13915 struct toolbar { string groupName; }
13916 ///
13917 /// Group: generating_from_code
13918 struct icon { ushort id; }
13919 ///
13920 /// Group: generating_from_code
13921 struct label { string label; }
13922 ///
13923 /// Group: generating_from_code
13924 struct hotkey { dchar ch; }
13925 ///
13926 /// Group: generating_from_code
13927 struct tip { string tip; }
13928 
13929 
13930 /++
13931 	Observes and allows inspection of an object via automatic gui
13932 +/
13933 /// Group: generating_from_code
13934 ObjectInspectionWindow objectInspectionWindow(T)(T t) if(is(T == class)) {
13935 	return new ObjectInspectionWindowImpl!(T)(t);
13936 }
13937 
13938 class ObjectInspectionWindow : Window {
13939 	this(int a, int b, string c) {
13940 		super(a, b, c);
13941 	}
13942 
13943 	abstract void readUpdatesFromObject();
13944 }
13945 
13946 class ObjectInspectionWindowImpl(T) : ObjectInspectionWindow {
13947 	T t;
13948 	this(T t) {
13949 		this.t = t;
13950 
13951 		super(300, 400, "ObjectInspectionWindow - " ~ T.stringof);
13952 
13953 		foreach(memberName; __traits(derivedMembers, T)) {{
13954 			alias member = I!(__traits(getMember, t, memberName))[0];
13955 			alias type = typeof(member);
13956 			static if(is(type == int)) {
13957 				auto le = new LabeledLineEdit(memberName ~ ": ", this);
13958 				//le.addEventListener("char", (Event ev) {
13959 					//if((ev.character < '0' || ev.character > '9') && ev.character != '-')
13960 						//ev.preventDefault();
13961 				//});
13962 				le.addEventListener(EventType.change, (Event ev) {
13963 					__traits(getMember, t, memberName) = cast(type) stringToLong(ev.stringValue);
13964 				});
13965 
13966 				updateMemberDelegates[memberName] = () {
13967 					le.content = toInternal!string(__traits(getMember, t, memberName));
13968 				};
13969 			}
13970 		}}
13971 	}
13972 
13973 	void delegate()[string] updateMemberDelegates;
13974 
13975 	override void readUpdatesFromObject() {
13976 		foreach(k, v; updateMemberDelegates)
13977 			v();
13978 	}
13979 }
13980 
13981 /++
13982 	Creates a dialog based on a data structure.
13983 
13984 	---
13985 	dialog((YourStructure value) {
13986 		// the user filled in the struct and clicked OK,
13987 		// you can check the members now
13988 	});
13989 	---
13990 
13991 	Params:
13992 		initialData = the initial value to show in the dialog. It will not modify this unless
13993 		it is a class then it might, no promises.
13994 
13995 	History:
13996 		The overload that lets you specify `initialData` was added on December 30, 2021 (dub v10.5)
13997 +/
13998 /// Group: generating_from_code
13999 void dialog(T)(void delegate(T) onOK, void delegate() onCancel = null, string title = T.stringof) {
14000 	dialog(T.init, onOK, onCancel, title);
14001 }
14002 /// ditto
14003 void dialog(T)(T initialData, void delegate(T) onOK, void delegate() onCancel = null, string title = T.stringof) {
14004 	auto dg = new AutomaticDialog!T(initialData, onOK, onCancel, title);
14005 	dg.show();
14006 }
14007 
14008 private static template I(T...) { alias I = T; }
14009 
14010 
14011 private string beautify(string name, char space = ' ', bool allLowerCase = false) {
14012 	if(name == "id")
14013 		return allLowerCase ? name : "ID";
14014 
14015 	char[160] buffer;
14016 	int bufferIndex = 0;
14017 	bool shouldCap = true;
14018 	bool shouldSpace;
14019 	bool lastWasCap;
14020 	foreach(idx, char ch; name) {
14021 		if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
14022 
14023 		if((ch >= 'A' && ch <= 'Z') || ch == '_') {
14024 			if(lastWasCap) {
14025 				// two caps in a row, don't change. Prolly acronym.
14026 			} else {
14027 				if(idx)
14028 					shouldSpace = true; // new word, add space
14029 			}
14030 
14031 			lastWasCap = true;
14032 		} else {
14033 			lastWasCap = false;
14034 		}
14035 
14036 		if(shouldSpace) {
14037 			buffer[bufferIndex++] = space;
14038 			if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
14039 			shouldSpace = false;
14040 		}
14041 		if(shouldCap) {
14042 			if(ch >= 'a' && ch <= 'z')
14043 				ch -= 32;
14044 			shouldCap = false;
14045 		}
14046 		if(allLowerCase && ch >= 'A' && ch <= 'Z')
14047 			ch += 32;
14048 		buffer[bufferIndex++] = ch;
14049 	}
14050 	return buffer[0 .. bufferIndex].idup;
14051 }
14052 
14053 /++
14054 	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.
14055 +/
14056 class AutomaticDialog(T) : Dialog {
14057 	T t;
14058 
14059 	void delegate(T) onOK;
14060 	void delegate() onCancel;
14061 
14062 	override int paddingTop() { return defaultLineHeight; }
14063 	override int paddingBottom() { return defaultLineHeight; }
14064 	override int paddingRight() { return defaultLineHeight; }
14065 	override int paddingLeft() { return defaultLineHeight; }
14066 
14067 	this(T initialData, void delegate(T) onOK, void delegate() onCancel, string title) {
14068 		assert(onOK !is null);
14069 
14070 		t = initialData;
14071 
14072 		static if(is(T == class)) {
14073 			if(t is null)
14074 				t = new T();
14075 		}
14076 		this.onOK = onOK;
14077 		this.onCancel = onCancel;
14078 		super(400, cast(int)(__traits(allMembers, T).length * 2) * (defaultLineHeight + 4 + 2) + Window.lineHeight + 56, title);
14079 
14080 		static if(is(T == class))
14081 			this.addDataControllerWidget(t);
14082 		else
14083 			this.addDataControllerWidget(&t);
14084 
14085 		auto hl = new HorizontalLayout(this);
14086 		auto stretch = new HorizontalSpacer(hl); // to right align
14087 		auto ok = new CommandButton("OK", hl);
14088 		auto cancel = new CommandButton("Cancel", hl);
14089 		ok.addEventListener(EventType.triggered, &OK);
14090 		cancel.addEventListener(EventType.triggered, &Cancel);
14091 
14092 		this.addEventListener((KeyDownEvent ev) {
14093 			if(ev.key == Key.Enter || ev.key == Key.PadEnter) {
14094 				ok.focus();
14095 				OK();
14096 				ev.preventDefault();
14097 			}
14098 			if(ev.key == Key.Escape) {
14099 				Cancel();
14100 				ev.preventDefault();
14101 			}
14102 		});
14103 
14104 		//this.children[0].focus();
14105 	}
14106 
14107 	override void OK() {
14108 		onOK(t);
14109 		close();
14110 	}
14111 
14112 	override void Cancel() {
14113 		if(onCancel)
14114 			onCancel();
14115 		close();
14116 	}
14117 }
14118 
14119 private template baseClassCount(Class) {
14120 	private int helper() {
14121 		int count = 0;
14122 		static if(is(Class bases == super)) {
14123 			foreach(base; bases)
14124 				static if(is(base == class))
14125 					count += 1 + baseClassCount!base;
14126 		}
14127 		return count;
14128 	}
14129 
14130 	enum int baseClassCount = helper();
14131 }
14132 
14133 private long stringToLong(string s) {
14134 	long ret;
14135 	if(s.length == 0)
14136 		return ret;
14137 	bool negative = s[0] == '-';
14138 	if(negative)
14139 		s = s[1 .. $];
14140 	foreach(ch; s) {
14141 		if(ch >= '0' && ch <= '9') {
14142 			ret *= 10;
14143 			ret += ch - '0';
14144 		}
14145 	}
14146 	if(negative)
14147 		ret = -ret;
14148 	return ret;
14149 }
14150 
14151 
14152 interface ReflectableProperties {
14153 	/++
14154 		Iterates the event's properties as strings. Note that keys may be repeated and a get property request may
14155 		call your sink with `null`. It it does, it means the key either doesn't request or cannot be represented by
14156 		json in the current implementation.
14157 
14158 		This is auto-implemented for you if you mixin [RegisterGetters] in your child classes and only have
14159 		properties of type `bool`, `int`, `double`, or `string`. For other ones, you will need to do it yourself
14160 		as of the June 2, 2021 release.
14161 
14162 		History:
14163 			Added June 2, 2021.
14164 
14165 		See_Also: [getPropertyAsString], [setPropertyFromString]
14166 	+/
14167 	void getPropertiesList(scope void delegate(string name) sink) const;// @nogc pure nothrow;
14168 	/++
14169 		Requests a property to be delivered to you as a string, through your `sink` delegate.
14170 
14171 		If the `value` is null, it means the property could not be retreived. If `valueIsJson`, it should
14172 		be interpreted as json, otherwise, it is just a plain string.
14173 
14174 		The sink should always be called exactly once for each call (it is basically a return value, but it might
14175 		use a local buffer it maintains instead of allocating a return value).
14176 
14177 		History:
14178 			Added June 2, 2021.
14179 
14180 		See_Also: [getPropertiesList], [setPropertyFromString]
14181 	+/
14182 	void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink);
14183 	/++
14184 		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.
14185 
14186 		History:
14187 			Added June 2, 2021.
14188 
14189 		See_Also: [getPropertiesList], [getPropertyAsString], [SetPropertyResult]
14190 	+/
14191 	SetPropertyResult setPropertyFromString(string name, scope const(char)[] str, bool strIsJson);
14192 
14193 	/// [setPropertyFromString] possible return values
14194 	enum SetPropertyResult {
14195 		success = 0, /// the property has been successfully set to the request value
14196 		notPermitted = -1, /// the property exists but it cannot be changed at this time
14197 		notImplemented = -2, /// the set function is not implemented for the given property (which may or may not exist)
14198 		noSuchProperty = -3, /// there is no property by that name
14199 		wrongFormat = -4, /// the string was given in the wrong format, e.g. passing "two" for an int value
14200 		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)
14201 	}
14202 
14203 	/++
14204 		You can mix this in to get an implementation in child classes. This does [setPropertyFromString].
14205 
14206 		Your original base class, however, must implement its own methods. I recommend doing the initial ones by hand.
14207 
14208 		For [Widget] and [Event], the library provides [Widget.Register] and [Event.Register] that call these for you, so you should
14209 		rarely need to use these building blocks directly.
14210 	+/
14211 	mixin template RegisterSetters() {
14212 		override SetPropertyResult setPropertyFromString(string name, scope const(char)[] value, bool valueIsJson) {
14213 			switch(name) {
14214 				foreach(memberName; __traits(derivedMembers, typeof(this))) {
14215 					case memberName:
14216 						static if(is(typeof(__traits(getMember, this, memberName)) : const bool)) {
14217 							if(value != "true" && value != "false")
14218 								return SetPropertyResult.wrongFormat;
14219 							__traits(getMember, this, memberName) = value == "true" ? true : false;
14220 							return SetPropertyResult.success;
14221 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const long)) {
14222 							import core.stdc.stdlib;
14223 							char[128] zero = 0;
14224 							if(buffer.length + 1 >= zero.length)
14225 								return SetPropertyResult.wrongFormat;
14226 							zero[0 .. buffer.length] = buffer[];
14227 							__traits(getMember, this, memberName) = strtol(buffer.ptr, null, 10);
14228 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const double)) {
14229 							import core.stdc.stdlib;
14230 							char[128] zero = 0;
14231 							if(buffer.length + 1 >= zero.length)
14232 								return SetPropertyResult.wrongFormat;
14233 							zero[0 .. buffer.length] = buffer[];
14234 							__traits(getMember, this, memberName) = strtod(buffer.ptr, null, 10);
14235 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const string)) {
14236 							__traits(getMember, this, memberName) = value.idup;
14237 						} else {
14238 							return SetPropertyResult.notImplemented;
14239 						}
14240 
14241 				}
14242 				default:
14243 					return super.setPropertyFromString(name, value, valueIsJson);
14244 			}
14245 		}
14246 	}
14247 
14248 	/++
14249 		You can mix this in to get an implementation in child classes. This does [getPropertyAsString] and [getPropertiesList].
14250 
14251 		Your original base class, however, must implement its own methods. I recommend doing the initial ones by hand.
14252 
14253 		For [Widget] and [Event], the library provides [Widget.Register] and [Event.Register] that call these for you, so you should
14254 		rarely need to use these building blocks directly.
14255 	+/
14256 	mixin template RegisterGetters() {
14257 		override void getPropertiesList(scope void delegate(string name) sink) const {
14258 			super.getPropertiesList(sink);
14259 
14260 			foreach(memberName; __traits(derivedMembers, typeof(this))) {
14261 				sink(memberName);
14262 			}
14263 		}
14264 		override void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink) {
14265 			switch(name) {
14266 				foreach(memberName; __traits(derivedMembers, typeof(this))) {
14267 					case memberName:
14268 						static if(is(typeof(__traits(getMember, this, memberName)) : const bool)) {
14269 							sink(name, __traits(getMember, this, memberName) ? "true" : "false", true);
14270 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const long)) {
14271 							import core.stdc.stdio;
14272 							char[32] buffer;
14273 							auto len = snprintf(buffer.ptr, buffer.length, "%lld", cast(long) __traits(getMember, this, memberName));
14274 							sink(name, buffer[0 .. len], true);
14275 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const double)) {
14276 							import core.stdc.stdio;
14277 							char[32] buffer;
14278 							auto len = snprintf(buffer.ptr, buffer.length, "%f", cast(double) __traits(getMember, this, memberName));
14279 							sink(name, buffer[0 .. len], true);
14280 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const string)) {
14281 							sink(name, __traits(getMember, this, memberName), false);
14282 							//sinkJsonString(memberName, __traits(getMember, this, memberName), sink);
14283 						} else {
14284 							sink(name, null, true);
14285 						}
14286 
14287 					return;
14288 				}
14289 				default:
14290 					return super.getPropertyAsString(name, sink);
14291 			}
14292 		}
14293 	}
14294 }
14295 
14296 private struct Stack(T) {
14297 	this(int maxSize) {
14298 		internalLength = 0;
14299 		arr = initialBuffer[];
14300 	}
14301 
14302 	///.
14303 	void push(T t) {
14304 		if(internalLength >= arr.length) {
14305 			auto oldarr = arr;
14306 			if(arr.length < 4096)
14307 				arr = new T[arr.length * 2];
14308 			else
14309 				arr = new T[arr.length + 4096];
14310 			arr[0 .. oldarr.length] = oldarr[];
14311 		}
14312 
14313 		arr[internalLength] = t;
14314 		internalLength++;
14315 	}
14316 
14317 	///.
14318 	T pop() {
14319 		assert(internalLength);
14320 		internalLength--;
14321 		return arr[internalLength];
14322 	}
14323 
14324 	///.
14325 	T peek() {
14326 		assert(internalLength);
14327 		return arr[internalLength - 1];
14328 	}
14329 
14330 	///.
14331 	@property bool empty() {
14332 		return internalLength ? false : true;
14333 	}
14334 
14335 	///.
14336 	private T[] arr;
14337 	private size_t internalLength;
14338 	private T[64] initialBuffer;
14339 	// 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),
14340 	// using this saves us a bunch of trips to the GC. In my last profiling, I got about a 50x improvement in the push()
14341 	// function thanks to this, and push() was actually one of the slowest individual functions in the code!
14342 }
14343 
14344 /// 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.
14345 private struct WidgetStream {
14346 
14347 	///.
14348 	@property Widget front() {
14349 		return current.widget;
14350 	}
14351 
14352 	/// Use Widget.tree instead.
14353 	this(Widget start) {
14354 		current.widget = start;
14355 		current.childPosition = -1;
14356 		isEmpty = false;
14357 		stack = typeof(stack)(0);
14358 	}
14359 
14360 	/*
14361 		Handle it
14362 		handle its children
14363 
14364 	*/
14365 
14366 	///.
14367 	void popFront() {
14368 	    more:
14369 	    	if(isEmpty) return;
14370 
14371 		// FIXME: the profiler says this function is somewhat slow (noticeable because it can be called a lot of times)
14372 
14373 		current.childPosition++;
14374 		if(current.childPosition >= current.widget.children.length) {
14375 			if(stack.empty())
14376 				isEmpty = true;
14377 			else {
14378 				current = stack.pop();
14379 				goto more;
14380 			}
14381 		} else {
14382 			stack.push(current);
14383 			current.widget = current.widget.children[current.childPosition];
14384 			current.childPosition = -1;
14385 		}
14386 	}
14387 
14388 	///.
14389 	@property bool empty() {
14390 		return isEmpty;
14391 	}
14392 
14393 	private:
14394 
14395 	struct Current {
14396 		Widget widget;
14397 		int childPosition;
14398 	}
14399 
14400 	Current current;
14401 
14402 	Stack!(Current) stack;
14403 
14404 	bool isEmpty;
14405 }
14406 
14407 
14408 /+
14409 
14410 	I could fix up the hierarchy kinda like this
14411 
14412 	class Widget {
14413 		Widget[] children() { return null; }
14414 	}
14415 	interface WidgetContainer {
14416 		Widget asWidget();
14417 		void addChild(Widget w);
14418 
14419 		// alias asWidget this; // but meh
14420 	}
14421 
14422 	Widget can keep a (Widget parent) ctor, but it should prolly deprecate and tell people to instead change their ctors to take WidgetContainer instead.
14423 
14424 	class Layout : Widget, WidgetContainer {}
14425 
14426 	class Window : WidgetContainer {}
14427 
14428 
14429 	All constructors that previously took Widgets should now take WidgetContainers instead
14430 
14431 
14432 
14433 	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".
14434 +/
14435 
14436 /+
14437 	LAYOUTS 2.0
14438 
14439 	can just be assigned as a function. assigning a new one will cause it to be immediately called.
14440 
14441 	they simply are responsible for the recomputeChildLayout. If this pointer is null, it uses the default virtual one.
14442 
14443 	recomputeChildLayout only really needs a property accessor proxy... just the layout info too.
14444 
14445 	and even Paint can just use computedStyle...
14446 
14447 		background color
14448 		font
14449 		border color and style
14450 
14451 	And actually the style proxy can offer some helper routines to draw these like the draw 3d box
14452 		please note that many widgets and in some modes will completely ignore properties as they will.
14453 		they are just hints you set, not promises.
14454 
14455 
14456 
14457 
14458 
14459 	So generally the existing virtual functions are just the default for the class. But individual objects
14460 	or stylesheets can override this. The virtual ones count as tag-level specificity in css.
14461 +/
14462 
14463 /++
14464 	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.
14465 
14466 	History:
14467 		Added May 24, 2021.
14468 +/
14469 struct WidgetBackground {
14470 	/++
14471 		A background with the given solid color.
14472 	+/
14473 	this(Color color) {
14474 		this.color = color;
14475 	}
14476 
14477 	this(WidgetBackground bg) {
14478 		this = bg;
14479 	}
14480 
14481 	/++
14482 		Creates a widget from the string.
14483 
14484 		Currently, it only supports solid colors via [Color.fromString], but it will likely be expanded in the future to something more like css.
14485 	+/
14486 	static WidgetBackground fromString(string s) {
14487 		return WidgetBackground(Color.fromString(s));
14488 	}
14489 
14490 	private Color color;
14491 }
14492 
14493 /++
14494 	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!)
14495 
14496 	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.
14497 
14498 	You should not inherit from this directly, but instead use [VisualTheme].
14499 
14500 	History:
14501 		Added May 8, 2021
14502 +/
14503 abstract class BaseVisualTheme {
14504 	/// Don't implement this, instead use [VisualTheme] and implement `paint` methods on specific subclasses you want to override.
14505 	abstract void doPaint(Widget widget, WidgetPainter painter);
14506 
14507 	/+
14508 	/// Don't implement this, instead use [VisualTheme] and implement `StyleOverride` aliases on specific subclasses you want to override.
14509 	abstract void useStyleProperties(Widget w, scope void delegate(scope Widget.Style props) dg);
14510 	+/
14511 
14512 	/++
14513 		Returns the property as a string, or null if it was not overridden in the style definition. The idea here is something like css,
14514 		where the interpretation of the string varies for each property and may include things like measurement units.
14515 	+/
14516 	abstract string getPropertyString(Widget widget, string propertyName);
14517 
14518 	/++
14519 		Default background color of the window. Widgets also use this to simulate transparency.
14520 
14521 		Probably some shade of grey.
14522 	+/
14523 	abstract Color windowBackgroundColor();
14524 	abstract Color widgetBackgroundColor();
14525 	abstract Color foregroundColor();
14526 	abstract Color lightAccentColor();
14527 	abstract Color darkAccentColor();
14528 
14529 	/++
14530 		Color used to indicate active selections in lists and text boxes, etc.
14531 	+/
14532 	abstract Color selectionColor();
14533 
14534 	abstract OperatingSystemFont defaultFont();
14535 
14536 	private OperatingSystemFont defaultFontCache_;
14537 	private bool defaultFontCachePopulated;
14538 	private OperatingSystemFont defaultFontCached() {
14539 		if(!defaultFontCachePopulated) {
14540 			// FIXME: set this to false if X disconnect or if visual theme changes
14541 			defaultFontCache_ = defaultFont();
14542 			defaultFontCachePopulated = true;
14543 		}
14544 		return defaultFontCache_;
14545 	}
14546 }
14547 
14548 /+
14549 	A widget should have:
14550 		classList
14551 		dataset
14552 		attributes
14553 		computedStyles
14554 		state (persistent)
14555 		dynamic state (focused, hover, etc)
14556 +/
14557 
14558 // visualTheme.computedStyle(this).paddingLeft
14559 
14560 
14561 /++
14562 	This is your entry point to create your own visual theme for custom widgets.
14563 +/
14564 abstract class VisualTheme(CRTP) : BaseVisualTheme {
14565 	override string getPropertyString(Widget widget, string propertyName) {
14566 		return null;
14567 	}
14568 
14569 	/+
14570 		mixin StyleOverride!Widget
14571 	final override void useStyleProperties(Widget w, scope void delegate(scope Widget.Style props) dg) {
14572 		w.useStyleProperties(dg);
14573 	}
14574 	+/
14575 
14576 	final override void doPaint(Widget widget, WidgetPainter painter) {
14577 		auto derived = cast(CRTP) cast(void*) this;
14578 
14579 		scope void delegate(Widget, WidgetPainter) bestMatch;
14580 		int bestMatchScore;
14581 
14582 		static if(__traits(hasMember, CRTP, "paint"))
14583 		foreach(overload; __traits(getOverloads, CRTP, "paint")) {
14584 			static if(is(typeof(overload) Params == __parameters)) {
14585 				static assert(Params.length == 2);
14586 				static assert(is(Params[0] : Widget));
14587 				static assert(is(Params[1] == WidgetPainter));
14588 				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);
14589 
14590 				alias type = Params[0];
14591 				if(cast(type) widget) {
14592 					auto score = baseClassCount!type;
14593 
14594 					if(score > bestMatchScore) {
14595 						bestMatch = cast(typeof(bestMatch)) &__traits(child, derived, overload);
14596 						bestMatchScore = score;
14597 					}
14598 				}
14599 			} else static assert(0, "paint should be a method.");
14600 		}
14601 
14602 		if(bestMatch)
14603 			bestMatch(widget, painter);
14604 		else
14605 			widget.paint(painter);
14606 	}
14607 
14608 	// 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
14609 	override Color windowBackgroundColor() { return Color(212, 212, 212); }
14610 	override Color widgetBackgroundColor() { return Color.white; }
14611 	override Color foregroundColor() { return Color.black; }
14612 	override Color darkAccentColor() { return Color(172, 172, 172); }
14613 	override Color lightAccentColor() { return Color(223, 223, 223); }
14614 	override Color selectionColor() { return Color(0, 0, 128); }
14615 	override OperatingSystemFont defaultFont() { return null; } // will just use the default out of simpledisplay's xfontstr
14616 
14617 	private static struct Cached {
14618 		// i prolly want to do this
14619 	}
14620 }
14621 
14622 final class DefaultVisualTheme : VisualTheme!DefaultVisualTheme {
14623 	/+
14624 	OperatingSystemFont defaultFont() { return new OperatingSystemFont("Times New Roman", 8, FontWeight.medium); }
14625 	Color windowBackgroundColor() { return Color(242, 242, 242); }
14626 	Color darkAccentColor() { return windowBackgroundColor; }
14627 	Color lightAccentColor() { return windowBackgroundColor; }
14628 	+/
14629 }
14630 
14631 /++
14632 	Event fired when an [Observeable] variable changes. You will want to add an event listener referencing
14633 	the field like `widget.addEventListener((scope StateChanged!(Whatever.field) ev) { });`
14634 
14635 	History:
14636 		Moved from minigui_addons.webview to main minigui on November 27, 2021 (dub v10.4)
14637 +/
14638 class StateChanged(alias field) : Event {
14639 	enum EventString = __traits(identifier, __traits(parent, field)) ~ "." ~ __traits(identifier, field) ~ ":change";
14640 	override bool cancelable() const { return false; }
14641 	this(Widget target, typeof(field) newValue) {
14642 		this.newValue = newValue;
14643 		super(EventString, target);
14644 	}
14645 
14646 	typeof(field) newValue;
14647 }
14648 
14649 /++
14650 	Convenience function to add a `triggered` event listener.
14651 
14652 	Its implementation is simply `w.addEventListener("triggered", dg);`
14653 
14654 	History:
14655 		Added November 27, 2021 (dub v10.4)
14656 +/
14657 void addWhenTriggered(Widget w, void delegate() dg) {
14658 	w.addEventListener("triggered", dg);
14659 }
14660 
14661 /++
14662 	Observable varables can be added to widgets and when they are changed, it fires
14663 	off a [StateChanged] event so you can react to it.
14664 
14665 	It is implemented as a getter and setter property, along with another helper you
14666 	can use to subscribe whith is `name_changed`. You can also subscribe to the [StateChanged]
14667 	event through the usual means. Just give the name of the variable. See [StateChanged] for an
14668 	example.
14669 
14670 	History:
14671 		Moved from minigui_addons.webview to main minigui on November 27, 2021 (dub v10.4)
14672 +/
14673 mixin template Observable(T, string name) {
14674 	private T backing;
14675 
14676 	mixin(q{
14677 		void } ~ name ~ q{_changed (void delegate(T) dg) {
14678 			this.addEventListener((StateChanged!this_thing ev) {
14679 				dg(ev.newValue);
14680 			});
14681 		}
14682 
14683 		@property T } ~ name ~ q{ () {
14684 			return backing;
14685 		}
14686 
14687 		@property void } ~ name ~ q{ (T t) {
14688 			backing = t;
14689 			auto event = new StateChanged!this_thing(this, t);
14690 			event.dispatch();
14691 		}
14692 	});
14693 
14694 	mixin("private alias this_thing = " ~ name ~ ";");
14695 }
14696 
14697 
14698 private bool startsWith(string test, string thing) {
14699 	if(test.length < thing.length)
14700 		return false;
14701 	return test[0 .. thing.length] == thing;
14702 }
14703 
14704 private bool endsWith(string test, string thing) {
14705 	if(test.length < thing.length)
14706 		return false;
14707 	return test[$ - thing.length .. $] == thing;
14708 }
14709 
14710 // still do layout delegation
14711 // and... split off Window from Widget.
14712 
14713 version(minigui_screenshots)
14714 struct Screenshot {
14715 	string name;
14716 }
14717 
14718 version(minigui_screenshots)
14719 static if(__VERSION__ > 2092)
14720 mixin(q{
14721 shared static this() {
14722 	import core.runtime;
14723 
14724 	static UnitTestResult screenshotMagic() {
14725 		string name;
14726 
14727 		import arsd.png;
14728 
14729 		auto results = new Window();
14730 		auto button = new Button("do it", results);
14731 
14732 		Window.newWindowCreated = delegate(Window w) {
14733 			Timer timer;
14734 			timer = new Timer(250, {
14735 				auto img = w.win.takeScreenshot();
14736 				timer.destroy();
14737 
14738 				version(Windows)
14739 					writePng("/var/www/htdocs/minigui-screenshots/windows/" ~ name ~ ".png", img);
14740 				else
14741 					writePng("/var/www/htdocs/minigui-screenshots/linux/" ~ name ~ ".png", img);
14742 
14743 				w.close();
14744 			});
14745 		};
14746 
14747 		button.addWhenTriggered( {
14748 
14749 		foreach(test; __traits(getUnitTests, mixin(__MODULE__))) {
14750 			name = null;
14751 			static foreach(attr; __traits(getAttributes, test)) {
14752 				static if(is(typeof(attr) == Screenshot))
14753 					name = attr.name;
14754 			}
14755 			if(name.length) {
14756 				test();
14757 			}
14758 		}
14759 
14760 		});
14761 
14762 		results.loop();
14763 
14764 		return UnitTestResult(0, 0, false, false);
14765 	}
14766 
14767 
14768 	Runtime.extendedModuleUnitTester = &screenshotMagic;
14769 }
14770 });
14771 version(minigui_screenshots) {
14772 	version(unittest)
14773 		void main() {}
14774 	else static assert(0, "dont forget the -unittest flag to dmd");
14775 }
14776 
14777 // FIXME: i called hotkey accelerator in some places. hotkey = key when menu is active like E&xit. accelerator = global shortcut.
14778 // FIXME: make multiple accelerators disambiguate based ona rgs
14779 // FIXME: MainWindow ctor should have same arg order as Window
14780 // FIXME: mainwindow ctor w/ client area size instead of total size.
14781 // 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.
14782 // FIXME: tri-state checkbox
14783 // FIXME: subordinate controls grouping...