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 		EXPERIMENTAL. subject to change.
3490 
3491 		When you draw a cursor, you can draw this to notify your window of where it is,
3492 		for IME systems to use.
3493 	+/
3494 	void notifyCursorPosition(int x, int y, int width, int height) {
3495 		if(auto a = drawingUpon.parentWindow)
3496 		if(auto w = a.inputProxy) {
3497 			w.setIMEPopupLocation(x + screenPainter.originX + width, y + screenPainter.originY + height);
3498 		}
3499 	}
3500 
3501 
3502 	///
3503 	ScreenPainter screenPainter;
3504 	/// Forward to the screen painter for other methods
3505 	alias screenPainter this;
3506 
3507 	private Widget drawingUpon;
3508 
3509 	/++
3510 		This is the list of rectangles that actually need to be redrawn.
3511 
3512 		Not actually implemented yet.
3513 	+/
3514 	Rectangle[] invalidatedRectangles;
3515 
3516 	private static BaseVisualTheme _visualTheme;
3517 
3518 	/++
3519 		Functions to access the visual theme and helpers to easily use it.
3520 
3521 		These are aware of the current widget's computed style out of the theme.
3522 	+/
3523 	static @property BaseVisualTheme visualTheme() {
3524 		if(_visualTheme is null)
3525 			_visualTheme = new DefaultVisualTheme();
3526 		return _visualTheme;
3527 	}
3528 
3529 	/// ditto
3530 	static @property void visualTheme(BaseVisualTheme theme) {
3531 		_visualTheme = theme;
3532 	}
3533 
3534 	/// ditto
3535 	Color themeForeground() {
3536 		return drawingUpon.getComputedStyle().foregroundColor();
3537 	}
3538 
3539 	/// ditto
3540 	Color themeBackground() {
3541 		return drawingUpon.getComputedStyle().background.color;
3542 	}
3543 
3544 	int isDarkTheme() {
3545 		return 0; // unspecified, yes, no as enum. FIXME
3546 	}
3547 
3548 	/++
3549 		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.
3550 
3551 		It gives your draw delegate a [Rectangle] representing the coordinates inside your border and padding.
3552 
3553 		If you change teh clip rectangle, you should change it back before you return.
3554 
3555 
3556 		The sequence it uses is:
3557 			background
3558 			content (delegated to you)
3559 			border
3560 			focused outline
3561 			selected overlay
3562 
3563 		Example code:
3564 
3565 		---
3566 		void paint(WidgetPainter painter) {
3567 			painter.drawThemed((bounds) {
3568 				return bounds; // if the selection overlay should be contained, you can return it here.
3569 			});
3570 		}
3571 		---
3572 	+/
3573 	void drawThemed(scope Rectangle delegate(const Rectangle bounds) drawBody) {
3574 		drawThemed((WidgetPainter painter, const Rectangle bounds) {
3575 			return drawBody(bounds);
3576 		});
3577 	}
3578 	// this overload is actually mroe for setting the delegate to a virtual function
3579 	void drawThemed(scope Rectangle delegate(WidgetPainter painter, const Rectangle bounds) drawBody) {
3580 		Rectangle rect = Rectangle(0, 0, drawingUpon.width, drawingUpon.height);
3581 
3582 		auto cs = drawingUpon.getComputedStyle();
3583 
3584 		auto bg = cs.background.color;
3585 
3586 		auto borderWidth = draw3dFrame(0, 0, drawingUpon.width, drawingUpon.height, this, cs.borderStyle, bg, cs.borderColor);
3587 
3588 		rect.left += borderWidth;
3589 		rect.right -= borderWidth;
3590 		rect.top += borderWidth;
3591 		rect.bottom -= borderWidth;
3592 
3593 		auto insideBorderRect = rect;
3594 
3595 		rect.left += cs.paddingLeft;
3596 		rect.right -= cs.paddingRight;
3597 		rect.top += cs.paddingTop;
3598 		rect.bottom += cs.paddingBottom;
3599 
3600 		this.outlineColor = this.themeForeground;
3601 		this.fillColor = bg;
3602 
3603 		auto widgetFont = cs.fontCached;
3604 		if(widgetFont !is null)
3605 			this.setFont(widgetFont);
3606 
3607 		rect = drawBody(this, rect);
3608 
3609 		if(widgetFont !is null) {
3610 			if(auto vtFont = visualTheme.defaultFontCached)
3611 				this.setFont(vtFont);
3612 			else
3613 				this.setFont(null);
3614 		}
3615 
3616 		if(auto os = cs.outlineStyle()) {
3617 			this.pen = Pen(cs.outlineColor(), 1, os == FrameStyle.dotted ? Pen.Style.Dotted : Pen.Style.Solid);
3618 			this.fillColor = Color.transparent;
3619 			this.drawRectangle(insideBorderRect);
3620 		}
3621 	}
3622 
3623 	/++
3624 		First, draw the background.
3625 		Then draw your content.
3626 		Next, draw the border.
3627 		And the focused indicator.
3628 		And the is-selected box.
3629 
3630 		If it is focused i can draw the outline too...
3631 
3632 		If selected i can even do the xor action but that's at the end.
3633 	+/
3634 	void drawThemeBackground() {
3635 
3636 	}
3637 
3638 	void drawThemeBorder() {
3639 
3640 	}
3641 
3642 	// all this stuff is a dangerous experiment....
3643 	static class ScriptableVersion {
3644 		ScreenPainterImplementation* p;
3645 		int originX, originY;
3646 
3647 		@scriptable:
3648 		void drawRectangle(int x, int y, int width, int height) {
3649 			p.drawRectangle(x + originX, y + originY, width, height);
3650 		}
3651 		void drawLine(int x1, int y1, int x2, int y2) {
3652 			p.drawLine(x1 + originX, y1 + originY, x2 + originX, y2 + originY);
3653 		}
3654 		void drawText(int x, int y, string text) {
3655 			p.drawText(x + originX, y + originY, 100000, 100000, text, 0);
3656 		}
3657 		void setOutlineColor(int r, int g, int b) {
3658 			p.pen = Pen(Color(r,g,b), 1);
3659 		}
3660 		void setFillColor(int r, int g, int b) {
3661 			p.fillColor = Color(r,g,b);
3662 		}
3663 	}
3664 
3665 	ScriptableVersion toArsdJsvar() {
3666 		auto sv = new ScriptableVersion;
3667 		sv.p = this.screenPainter.impl;
3668 		sv.originX = this.screenPainter.originX;
3669 		sv.originY = this.screenPainter.originY;
3670 		return sv;
3671 	}
3672 
3673 	static WidgetPainter fromJsVar(T)(T t) {
3674 		return WidgetPainter.init;
3675 	}
3676 	// done..........
3677 }
3678 
3679 
3680 struct Style {
3681 	static struct helper(string m, T) {
3682 		enum method = m;
3683 		T v;
3684 
3685 		mixin template MethodOverride(typeof(this) v) {
3686 			mixin("override typeof(v.v) "~v.method~"() { return v.v; }");
3687 		}
3688 	}
3689 
3690 	static auto opDispatch(string method, T)(T value) {
3691 		return helper!(method, T)(value);
3692 	}
3693 }
3694 
3695 /++
3696 	Implementation detail of the [ControlledBy] UDA.
3697 
3698 	History:
3699 		Added Oct 28, 2020
3700 +/
3701 struct ControlledBy_(T, Args...) {
3702 	Args args;
3703 
3704 	static if(Args.length)
3705 	this(Args args) {
3706 		this.args = args;
3707 	}
3708 
3709 	private T construct(Widget parent) {
3710 		return new T(args, parent);
3711 	}
3712 }
3713 
3714 /++
3715 	User-defined attribute you can add to struct members contrlled by [addDataControllerWidget] or [dialog] to tell which widget you want created for them.
3716 
3717 	History:
3718 		Added Oct 28, 2020
3719 +/
3720 auto ControlledBy(T, Args...)(Args args) {
3721 	return ControlledBy_!(T, Args)(args);
3722 }
3723 
3724 struct ContainerMeta {
3725 	string name;
3726 	ContainerMeta[] children;
3727 	Widget function(Widget parent) factory;
3728 
3729 	Widget instantiate(Widget parent) {
3730 		auto n = factory(parent);
3731 		n.name = name;
3732 		foreach(child; children)
3733 			child.instantiate(n);
3734 		return n;
3735 	}
3736 }
3737 
3738 /++
3739 	This is a helper for [addDataControllerWidget]. You can use it as a UDA on the type. See
3740 	http://dpldocs.info/this-week-in-d/Blog.Posted_2020_11_02.html for more information.
3741 
3742 	Please note that as of May 28, 2021, a dmd bug prevents this from compiling on module-level
3743 	structures. It works fine on structs declared inside functions though.
3744 
3745 	See: https://issues.dlang.org/show_bug.cgi?id=21984
3746 +/
3747 template Container(CArgs...) {
3748 	static if(CArgs.length && is(CArgs[0] : Widget)) {
3749 		private alias Super = CArgs[0];
3750 		private alias CArgs2 = CArgs[1 .. $];
3751 	} else {
3752 		private alias Super = Layout;
3753 		private alias CArgs2 = CArgs;
3754 	}
3755 
3756 	class Container : Super {
3757 		this(Widget parent) { super(parent); }
3758 
3759 		// just to partially support old gdc versions
3760 		version(GNU) {
3761 			static if(CArgs2.length >= 1) { enum tmp0 = CArgs2[0]; mixin typeof(tmp0).MethodOverride!(CArgs2[0]); }
3762 			static if(CArgs2.length >= 2) { enum tmp1 = CArgs2[1]; mixin typeof(tmp1).MethodOverride!(CArgs2[1]); }
3763 			static if(CArgs2.length >= 3) { enum tmp2 = CArgs2[2]; mixin typeof(tmp2).MethodOverride!(CArgs2[2]); }
3764 			static if(CArgs2.length > 3) static assert(0, "only a few overrides like this supported on your compiler version at this time");
3765 		} else mixin(q{
3766 			static foreach(Arg; CArgs2) {
3767 				mixin Arg.MethodOverride!(Arg);
3768 			}
3769 		});
3770 
3771 		static ContainerMeta opCall(string name, ContainerMeta[] children...) {
3772 			return ContainerMeta(
3773 				name,
3774 				children.dup,
3775 				function (Widget parent) { return new typeof(this)(parent); }
3776 			);
3777 		}
3778 
3779 		static ContainerMeta opCall(ContainerMeta[] children...) {
3780 			return opCall(null, children);
3781 		}
3782 	}
3783 }
3784 
3785 /++
3786 	The data controller widget is created by reflecting over the given
3787 	data type. You can use [ControlledBy] as a UDA on a struct or
3788 	just let it create things automatically.
3789 
3790 	Unlike [dialog], this uses real-time updating of the data and
3791 	you add it to another window yourself.
3792 
3793 	---
3794 		struct Test {
3795 			int x;
3796 			int y;
3797 		}
3798 
3799 		auto window = new Window();
3800 		auto dcw = new DataControllerWidget!Test(new Test, window);
3801 	---
3802 
3803 	The way it works is any public members are given a widget based
3804 	on their data type, and public methods trigger an action button
3805 	if no relevant parameters or a dialog action if it does have
3806 	parameters, similar to the [menu] facility.
3807 
3808 	If you change data programmatically, without going through the
3809 	DataControllerWidget methods, you will have to tell it something
3810 	has changed and it needs to redraw. This is done with the `invalidate`
3811 	method.
3812 
3813 	History:
3814 		Added Oct 28, 2020
3815 +/
3816 /// Group: generating_from_code
3817 class DataControllerWidget(T) : WidgetContainer {
3818 	static if(is(T == class) || is(T : const E[], E))
3819 		private alias Tref = T;
3820 	else
3821 		private alias Tref = T*;
3822 
3823 	Tref datum;
3824 
3825 	/++
3826 		See_also: [addDataControllerWidget]
3827 	+/
3828 	this(Tref datum, Widget parent) {
3829 		this.datum = datum;
3830 
3831 		Widget cp = this;
3832 
3833 		super(parent);
3834 
3835 		foreach(attr; __traits(getAttributes, T))
3836 			static if(is(typeof(attr) == ContainerMeta)) {
3837 				cp = attr.instantiate(this);
3838 			}
3839 
3840 		auto def = this.getByName("default");
3841 		if(def !is null)
3842 			cp = def;
3843 
3844 		Widget helper(string name) {
3845 			auto maybe = this.getByName(name);
3846 			if(maybe is null)
3847 				return cp;
3848 			return maybe;
3849 
3850 		}
3851 
3852 		foreach(member; __traits(allMembers, T))
3853 		static if(member != "this") // wtf https://issues.dlang.org/show_bug.cgi?id=22011
3854 		static if(is(typeof(__traits(getMember, this.datum, member))))
3855 		static if(__traits(getProtection, __traits(getMember, this.datum, member)) == "public") {
3856 			void delegate() update;
3857 
3858 			auto w = widgetFor!(__traits(getMember, T, member))(&__traits(getMember, this.datum, member), helper(member), update);
3859 
3860 			if(update)
3861 				updaters ~= update;
3862 
3863 			static if(is(typeof(__traits(getMember, this.datum, member)) == function)) {
3864 				w.addEventListener("triggered", delegate() {
3865 					makeAutomaticHandler!(__traits(getMember, this.datum, member))(&__traits(getMember, this.datum, member))();
3866 					notifyDataUpdated();
3867 				});
3868 			} else static if(is(typeof(w.isChecked) == bool)) {
3869 				w.addEventListener(EventType.change, (Event ev) {
3870 					__traits(getMember, this.datum, member) = w.isChecked;
3871 				});
3872 			} else static if(is(typeof(w.value) == string) || is(typeof(w.content) == string)) {
3873 				w.addEventListener("change", (Event e) { genericSetValue(&__traits(getMember, this.datum, member), e.stringValue); } );
3874 			} else static if(is(typeof(w.value) == int)) {
3875 				w.addEventListener("change", (Event e) { genericSetValue(&__traits(getMember, this.datum, member), e.intValue); } );
3876 			} else static if(is(typeof(w) == DropDownSelection)) {
3877 				// special case for this to kinda support enums and such. coudl be better though
3878 				w.addEventListener("change", (Event e) { genericSetValue(&__traits(getMember, this.datum, member), e.intValue); } );
3879 			} else {
3880 				static assert(0, "unsupported type " ~ typeof(__traits(getMember, this.datum, member)).stringof ~ " " ~ typeof(w).stringof);
3881 			}
3882 		}
3883 	}
3884 
3885 	/++
3886 		If you modify the data in the structure directly, you need to call this to update the UI and propagate any change messages.
3887 
3888 		History:
3889 			Added May 28, 2021
3890 	+/
3891 	void notifyDataUpdated() {
3892 		foreach(updater; updaters)
3893 			updater();
3894 
3895 		this.emit!(ChangeEvent!void)(delegate{});
3896 	}
3897 
3898 	private Widget[string] memberWidgets;
3899 	private void delegate()[] updaters;
3900 
3901 	mixin Emits!(ChangeEvent!void);
3902 }
3903 
3904 private int saturatedSum(int[] values...) {
3905 	int sum;
3906 	foreach(value; values) {
3907 		if(value == int.max)
3908 			return int.max;
3909 		sum += value;
3910 	}
3911 	return sum;
3912 }
3913 
3914 void genericSetValue(T, W)(T* where, W what) {
3915 	import std.conv;
3916 	*where = to!T(what);
3917 	//*where = cast(T) stringToLong(what);
3918 }
3919 
3920 /++
3921 	Creates a widget for the value `tt`, which is pointed to at runtime by `valptr`, with the given parent.
3922 
3923 	The `update` delegate can be called if you change `*valptr` to reflect those changes in the widget.
3924 
3925 	Note that this creates the widget but does not attach any event handlers to it.
3926 +/
3927 private static auto widgetFor(alias tt, P)(P valptr, Widget parent, out void delegate() update) {
3928 
3929 	string displayName = __traits(identifier, tt).beautify;
3930 
3931 	static if(controlledByCount!tt == 1) {
3932 		foreach(i, attr; __traits(getAttributes, tt)) {
3933 			static if(is(typeof(attr) == ControlledBy_!(T, Args), T, Args...)) {
3934 				auto w = attr.construct(parent);
3935 				static if(__traits(compiles, w.setPosition(*valptr)))
3936 					update = () { w.setPosition(*valptr); };
3937 				else static if(__traits(compiles, w.setValue(*valptr)))
3938 					update = () { w.setValue(*valptr); };
3939 
3940 				if(update)
3941 					update();
3942 				return w;
3943 			}
3944 		}
3945 	} else static if(controlledByCount!tt == 0) {
3946 		static if(is(typeof(tt) == enum)) {
3947 			// FIXME: update
3948 			auto dds = new DropDownSelection(parent);
3949 			foreach(idx, option; __traits(allMembers, typeof(tt))) {
3950 				dds.addOption(option);
3951 				if(__traits(getMember, typeof(tt), option) == *valptr)
3952 					dds.setSelection(cast(int) idx);
3953 			}
3954 			return dds;
3955 		} else static if(is(typeof(tt) == bool)) {
3956 			auto box = new Checkbox(displayName, parent);
3957 			update = () { box.isChecked = *valptr; };
3958 			update();
3959 			return box;
3960 		} else static if(is(typeof(tt) : const long)) {
3961 			auto le = new LabeledLineEdit(displayName, parent);
3962 			update = () { le.content = toInternal!string(*valptr); };
3963 			update();
3964 			return le;
3965 		} else static if(is(typeof(tt) : const string)) {
3966 			auto le = new LabeledLineEdit(displayName, parent);
3967 			update = () { le.content = *valptr; };
3968 			update();
3969 			return le;
3970 		} else static if(is(typeof(tt) == function)) {
3971 			auto w = new Button(displayName, parent);
3972 			return w;
3973 		}
3974 	} else static assert(0, "multiple controllers not yet supported");
3975 }
3976 
3977 private template controlledByCount(alias tt) {
3978 	static int helper() {
3979 		int count;
3980 		foreach(i, attr; __traits(getAttributes, tt))
3981 			static if(is(typeof(attr) == ControlledBy_!(T, Args), T, Args...))
3982 				count++;
3983 		return count;
3984 	}
3985 
3986 	enum controlledByCount = helper;
3987 }
3988 
3989 /++
3990 	Intended for UFCS action like `window.addDataControllerWidget(new MyObject());`
3991 
3992 	If you provide a `redrawOnChange` widget, it will automatically register a change event handler that calls that widget's redraw method.
3993 
3994 	History:
3995 		The `redrawOnChange` parameter was added on May 28, 2021.
3996 +/
3997 DataControllerWidget!T addDataControllerWidget(T)(Widget parent, T t, Widget redrawOnChange = null) if(is(T == class)) {
3998 	auto dcw = new DataControllerWidget!T(t, parent);
3999 	initializeDataControllerWidget(dcw, redrawOnChange);
4000 	return dcw;
4001 }
4002 
4003 /// ditto
4004 DataControllerWidget!T addDataControllerWidget(T)(Widget parent, T* t, Widget redrawOnChange = null) if(is(T == struct)) {
4005 	auto dcw = new DataControllerWidget!T(t, parent);
4006 	initializeDataControllerWidget(dcw, redrawOnChange);
4007 	return dcw;
4008 }
4009 
4010 private void initializeDataControllerWidget(Widget w, Widget redrawOnChange) {
4011 	if(redrawOnChange !is null)
4012 		w.addEventListener("change", delegate() { redrawOnChange.redraw(); });
4013 }
4014 
4015 /++
4016 	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.
4017 
4018 	History:
4019 		Finalized on June 3, 2021 for the dub v10.0 release
4020 +/
4021 struct StyleInformation {
4022 	private Widget w;
4023 	private BaseVisualTheme visualTheme;
4024 
4025 	private this(Widget w) {
4026 		this.w = w;
4027 		this.visualTheme = WidgetPainter.visualTheme;
4028 	}
4029 
4030 	/++
4031 		Forwards to [Widget.Style]
4032 
4033 		Bugs:
4034 			It is supposed to fall back to the [VisualTheme] if
4035 			the style doesn't override the default, but that is
4036 			not generally implemented. Many of them may end up
4037 			being explicit overloads instead of the generic
4038 			opDispatch fallback, like [font] is now.
4039 	+/
4040 	public @property opDispatch(string name)() {
4041 		typeof(__traits(getMember, Widget.Style.init, name)()) prop;
4042 		w.useStyleProperties((scope Widget.Style props) {
4043 		//visualTheme.useStyleProperties(w, (props) {
4044 			prop = __traits(getMember, props, name);
4045 		});
4046 		return prop;
4047 	}
4048 
4049 	/++
4050 		Returns the cached font object associated with the widget,
4051 		if overridden by the [Widget.Style|Style], or the [VisualTheme] if not.
4052 
4053 		History:
4054 			Prior to March 21, 2022 (dub v10.7), `font` went through
4055 			[opDispatch], which did not use the cache. You can now call it
4056 			repeatedly without guilt.
4057 	+/
4058 	public @property OperatingSystemFont font() {
4059 		OperatingSystemFont prop;
4060 		w.useStyleProperties((scope Widget.Style props) {
4061 			prop = props.fontCached;
4062 		});
4063 		if(prop is null)
4064 			prop = visualTheme.defaultFontCached;
4065 		return prop;
4066 	}
4067 
4068 	@property {
4069 		// Layout helpers. Currently just forwarding since I haven't made up my mind on a better way.
4070 		/** */ int paddingLeft() { return w.paddingLeft(); }
4071 		/** */ int paddingRight() { return w.paddingRight(); }
4072 		/** */ int paddingTop() { return w.paddingTop(); }
4073 		/** */ int paddingBottom() { return w.paddingBottom(); }
4074 
4075 		/** */ int marginLeft() { return w.marginLeft(); }
4076 		/** */ int marginRight() { return w.marginRight(); }
4077 		/** */ int marginTop() { return w.marginTop(); }
4078 		/** */ int marginBottom() { return w.marginBottom(); }
4079 
4080 		/** */ int maxHeight() { return w.maxHeight(); }
4081 		/** */ int minHeight() { return w.minHeight(); }
4082 
4083 		/** */ int maxWidth() { return w.maxWidth(); }
4084 		/** */ int minWidth() { return w.minWidth(); }
4085 
4086 		/** */ int flexBasisWidth() { return w.flexBasisWidth(); }
4087 		/** */ int flexBasisHeight() { return w.flexBasisHeight(); }
4088 
4089 		/** */ int heightStretchiness() { return w.heightStretchiness(); }
4090 		/** */ int widthStretchiness() { return w.widthStretchiness(); }
4091 
4092 		/** */ int heightShrinkiness() { return w.heightShrinkiness(); }
4093 		/** */ int widthShrinkiness() { return w.widthShrinkiness(); }
4094 
4095 		// Global helpers some of these are unstable.
4096 		static:
4097 		/** */ Color windowBackgroundColor() { return WidgetPainter.visualTheme.windowBackgroundColor(); }
4098 		/** */ Color widgetBackgroundColor() { return WidgetPainter.visualTheme.widgetBackgroundColor(); }
4099 		/** */ Color lightAccentColor() { return WidgetPainter.visualTheme.lightAccentColor(); }
4100 		/** */ Color darkAccentColor() { return WidgetPainter.visualTheme.darkAccentColor(); }
4101 
4102 		/** */ Color activeTabColor() { return lightAccentColor; }
4103 		/** */ Color buttonColor() { return windowBackgroundColor; }
4104 		/** */ Color depressedButtonColor() { return darkAccentColor; }
4105 		/** */ Color hoveringColor() { return Color(228, 228, 228); }
4106 		/** */ Color activeListXorColor() {
4107 			auto c = WidgetPainter.visualTheme.selectionColor();
4108 			return Color(c.r ^ 255, c.g ^ 255, c.b ^ 255, c.a);
4109 		}
4110 		/** */ Color progressBarColor() { return WidgetPainter.visualTheme.selectionColor(); }
4111 		/** */ Color activeMenuItemColor() { return WidgetPainter.visualTheme.selectionColor(); }
4112 	}
4113 
4114 
4115 
4116 	/+
4117 
4118 	private static auto extractStyleProperty(string name)(Widget w) {
4119 		typeof(__traits(getMember, Widget.Style.init, name)()) prop;
4120 		w.useStyleProperties((props) {
4121 			prop = __traits(getMember, props, name);
4122 		});
4123 		return prop;
4124 	}
4125 
4126 	// FIXME: clear this upon a X server disconnect
4127 	private static OperatingSystemFont[string] fontCache;
4128 
4129 	T getProperty(T)(string name, lazy T default_) {
4130 		if(visualTheme !is null) {
4131 			auto str = visualTheme.getPropertyString(w, name);
4132 			if(str is null)
4133 				return default_;
4134 			static if(is(T == Color))
4135 				return Color.fromString(str);
4136 			else static if(is(T == Measurement))
4137 				return Measurement(cast(int) toInternal!int(str));
4138 			else static if(is(T == WidgetBackground))
4139 				return WidgetBackground.fromString(str);
4140 			else static if(is(T == OperatingSystemFont)) {
4141 				if(auto f = str in fontCache)
4142 					return *f;
4143 				else
4144 					return fontCache[str] = new OperatingSystemFont(str);
4145 			} else static if(is(T == FrameStyle)) {
4146 				switch(str) {
4147 					default:
4148 						return FrameStyle.none;
4149 					foreach(style; __traits(allMembers, FrameStyle))
4150 					case style:
4151 						return __traits(getMember, FrameStyle, style);
4152 				}
4153 			} else static assert(0);
4154 		} else
4155 			return default_;
4156 	}
4157 
4158 	static struct Measurement {
4159 		int value;
4160 		alias value this;
4161 	}
4162 
4163 	@property:
4164 
4165 	int paddingLeft() { return getProperty("padding-left", Measurement(w.paddingLeft())); }
4166 	int paddingRight() { return getProperty("padding-right", Measurement(w.paddingRight())); }
4167 	int paddingTop() { return getProperty("padding-top", Measurement(w.paddingTop())); }
4168 	int paddingBottom() { return getProperty("padding-bottom", Measurement(w.paddingBottom())); }
4169 
4170 	int marginLeft() { return getProperty("margin-left", Measurement(w.marginLeft())); }
4171 	int marginRight() { return getProperty("margin-right", Measurement(w.marginRight())); }
4172 	int marginTop() { return getProperty("margin-top", Measurement(w.marginTop())); }
4173 	int marginBottom() { return getProperty("margin-bottom", Measurement(w.marginBottom())); }
4174 
4175 	int maxHeight() { return getProperty("max-height", Measurement(w.maxHeight())); }
4176 	int minHeight() { return getProperty("min-height", Measurement(w.minHeight())); }
4177 
4178 	int maxWidth() { return getProperty("max-width", Measurement(w.maxWidth())); }
4179 	int minWidth() { return getProperty("min-width", Measurement(w.minWidth())); }
4180 
4181 
4182 	WidgetBackground background() { return getProperty("background", extractStyleProperty!"background"(w)); }
4183 	Color foregroundColor() { return getProperty("foreground-color", extractStyleProperty!"foregroundColor"(w)); }
4184 
4185 	OperatingSystemFont font() { return getProperty("font", extractStyleProperty!"fontCached"(w)); }
4186 
4187 	FrameStyle borderStyle() { return getProperty("border-style", extractStyleProperty!"borderStyle"(w)); }
4188 	Color borderColor() { return getProperty("border-color", extractStyleProperty!"borderColor"(w)); }
4189 
4190 	FrameStyle outlineStyle() { return getProperty("outline-style", extractStyleProperty!"outlineStyle"(w)); }
4191 	Color outlineColor() { return getProperty("outline-color", extractStyleProperty!"outlineColor"(w)); }
4192 
4193 
4194 	Color windowBackgroundColor() { return WidgetPainter.visualTheme.windowBackgroundColor(); }
4195 	Color widgetBackgroundColor() { return WidgetPainter.visualTheme.widgetBackgroundColor(); }
4196 	Color lightAccentColor() { return WidgetPainter.visualTheme.lightAccentColor(); }
4197 	Color darkAccentColor() { return WidgetPainter.visualTheme.darkAccentColor(); }
4198 
4199 	Color activeTabColor() { return lightAccentColor; }
4200 	Color buttonColor() { return windowBackgroundColor; }
4201 	Color depressedButtonColor() { return darkAccentColor; }
4202 	Color hoveringColor() { return Color(228, 228, 228); }
4203 	Color activeListXorColor() {
4204 		auto c = WidgetPainter.visualTheme.selectionColor();
4205 		return Color(c.r ^ 255, c.g ^ 255, c.b ^ 255, c.a);
4206 	}
4207 	Color progressBarColor() { return WidgetPainter.visualTheme.selectionColor(); }
4208 	Color activeMenuItemColor() { return WidgetPainter.visualTheme.selectionColor(); }
4209 	+/
4210 }
4211 
4212 
4213 
4214 // pragma(msg, __traits(classInstanceSize, Widget));
4215 
4216 /*private*/ template EventString(E) {
4217 	static if(is(typeof(E.EventString)))
4218 		enum EventString = E.EventString;
4219 	else
4220 		enum EventString = E.mangleof; // FIXME fqn? or something more user friendly
4221 }
4222 
4223 /*private*/ template EventStringIdentifier(E) {
4224 	string helper() {
4225 		auto es = EventString!E;
4226 		char[] id = new char[](es.length * 2);
4227 		size_t idx;
4228 		foreach(char ch; es) {
4229 			id[idx++] = cast(char)('a' + (ch >> 4));
4230 			id[idx++] = cast(char)('a' + (ch & 0x0f));
4231 		}
4232 		return cast(string) id;
4233 	}
4234 
4235 	enum EventStringIdentifier = helper();
4236 }
4237 
4238 
4239 template classStaticallyEmits(This, EventType) {
4240 	static if(is(This Base == super))
4241 		static if(is(Base : Widget))
4242 			enum baseEmits = classStaticallyEmits!(Base, EventType);
4243 		else
4244 			enum baseEmits = false;
4245 	else
4246 		enum baseEmits = false;
4247 
4248 	enum thisEmits = is(typeof(__traits(getMember, This, "emits_" ~ EventStringIdentifier!EventType)) == EventType[0]);
4249 
4250 	enum classStaticallyEmits = thisEmits || baseEmits;
4251 }
4252 
4253 /++
4254 	A helper to make widgets out of other native windows.
4255 
4256 	History:
4257 		Factored out of OpenGlWidget on November 5, 2021
4258 +/
4259 class NestedChildWindowWidget : Widget {
4260 	SimpleWindow win;
4261 
4262 	/++
4263 		Used on X to send focus to the appropriate child window when requested by the window manager.
4264 
4265 		Normally returns its own nested window. Can also return another child or null to revert to the parent
4266 		if you override it in a child class.
4267 
4268 		History:
4269 			Added April 2, 2022 (dub v10.8)
4270 	+/
4271 	SimpleWindow focusableWindow() {
4272 		return win;
4273 	}
4274 
4275 	///
4276 	// win = new SimpleWindow(640, 480, null, OpenGlOptions.yes, Resizability.automaticallyScaleIfPossible, WindowTypes.nestedChild, WindowFlags.normal, getParentWindow(parent));
4277 	this(SimpleWindow win, Widget parent) {
4278 		this.parentWindow = parent.parentWindow;
4279 		this.win = win;
4280 
4281 		super(parent);
4282 		windowsetup(win);
4283 	}
4284 
4285 	static protected SimpleWindow getParentWindow(Widget parent) {
4286 		assert(parent !is null);
4287 		SimpleWindow pwin = parent.parentWindow.win;
4288 
4289 		version(win32_widgets) {
4290 			HWND phwnd;
4291 			auto wtf = parent;
4292 			while(wtf) {
4293 				if(wtf.hwnd) {
4294 					phwnd = wtf.hwnd;
4295 					break;
4296 				}
4297 				wtf = wtf.parent;
4298 			}
4299 			// kinda a hack here just because the ctor below just needs a SimpleWindow wrapper....
4300 			if(phwnd)
4301 				pwin = new SimpleWindow(phwnd);
4302 		}
4303 
4304 		return pwin;
4305 	}
4306 
4307 	/++
4308 		Called upon the nested window being destroyed.
4309 		Remember the window has already been destroyed at
4310 		this point, so don't use the native handle for anything.
4311 
4312 		History:
4313 			Added April 3, 2022 (dub v10.8)
4314 	+/
4315 	protected void dispose() {
4316 
4317 	}
4318 
4319 	protected void windowsetup(SimpleWindow w) {
4320 		/*
4321 		win.onFocusChange = (bool getting) {
4322 			if(getting)
4323 				this.focus();
4324 		};
4325 		*/
4326 
4327 		/+
4328 		win.onFocusChange = (bool getting) {
4329 			if(getting) {
4330 				this.parentWindow.focusedWidget = this;
4331 				this.emit!FocusEvent();
4332 				this.emit!FocusInEvent();
4333 			} else {
4334 				this.emit!BlurEvent();
4335 				this.emit!FocusOutEvent();
4336 			}
4337 		};
4338 		+/
4339 
4340 		win.onDestroyed = () {
4341 			this.dispose();
4342 		};
4343 
4344 		version(win32_widgets) {
4345 			Widget.nativeMapping[win.hwnd] = this;
4346 			this.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(win.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc);
4347 		} else {
4348 			win.setEventHandlers(
4349 				(MouseEvent e) {
4350 					Widget p = this;
4351 					while(p ! is parentWindow) {
4352 						e.x += p.x;
4353 						e.y += p.y;
4354 						p = p.parent;
4355 					}
4356 					parentWindow.dispatchMouseEvent(e);
4357 				},
4358 				(KeyEvent e) {
4359 					//import std.stdio; writefln("%s %x   %s", cast(void*) win, cast(uint) e.key, e.key);
4360 					parentWindow.dispatchKeyEvent(e);
4361 				},
4362 				(dchar e) {
4363 					parentWindow.dispatchCharEvent(e);
4364 				},
4365 			);
4366 		}
4367 
4368 	}
4369 
4370 	override void showing(bool s, bool recalc) {
4371 		auto cur = hidden;
4372 		win.hidden = !s;
4373 		if(cur != s && s)
4374 			redraw();
4375 	}
4376 
4377 	/// OpenGL widgets cannot have child widgets. Do not call this.
4378 	/* @disable */ final override void addChild(Widget, int) {
4379 		throw new Error("cannot add children to OpenGL widgets");
4380 	}
4381 
4382 	/// When an opengl widget is laid out, it will adjust the glViewport for you automatically.
4383 	/// Keep in mind that events like mouse coordinates are still relative to your size.
4384 	override void registerMovement() {
4385 		//import std.stdio; writefln("%d %d %d %d", x,y,width,height);
4386 		version(win32_widgets)
4387 			auto pos = getChildPositionRelativeToParentHwnd(this);
4388 		else
4389 			auto pos = getChildPositionRelativeToParentOrigin(this);
4390 		win.moveResize(pos[0], pos[1], width, height);
4391 
4392 		registerMovementAdditionalWork();
4393 		sendResizeEvent();
4394 	}
4395 
4396 	abstract void registerMovementAdditionalWork();
4397 }
4398 
4399 /++
4400 	Nests an opengl capable window inside this window as a widget.
4401 
4402 	You may also just want to create an additional [SimpleWindow] with
4403 	[OpenGlOptions.yes] yourself.
4404 
4405 	An OpenGL widget cannot have child widgets. It will throw if you try.
4406 +/
4407 static if(OpenGlEnabled)
4408 class OpenGlWidget : NestedChildWindowWidget {
4409 
4410 	override void registerMovementAdditionalWork() {
4411 		win.setAsCurrentOpenGlContext();
4412 	}
4413 
4414 	///
4415 	this(Widget parent) {
4416 		auto win = new SimpleWindow(640, 480, null, OpenGlOptions.yes, Resizability.automaticallyScaleIfPossible, WindowTypes.nestedChild, WindowFlags.normal, getParentWindow(parent));
4417 		super(win, parent);
4418 	}
4419 
4420 	override void paint(WidgetPainter painter) {
4421 		win.setAsCurrentOpenGlContext();
4422 		glViewport(0, 0, this.width, this.height);
4423 		win.redrawOpenGlSceneNow();
4424 	}
4425 
4426 	void redrawOpenGlScene(void delegate() dg) {
4427 		win.redrawOpenGlScene = dg;
4428 	}
4429 }
4430 
4431 /++
4432 	This demo shows how to draw text in an opengl scene.
4433 +/
4434 unittest {
4435 	import arsd.minigui;
4436 	import arsd.ttf;
4437 
4438 	void main() {
4439 		auto window = new Window();
4440 
4441 		auto widget = new OpenGlWidget(window);
4442 
4443 		// old means non-shader code so compatible with glBegin etc.
4444 		// tbh I haven't implemented new one in font yet...
4445 		// anyway, declaring here, will construct soon.
4446 		OpenGlLimitedFont!(OpenGlFontGLVersion.old) glfont;
4447 
4448 		// this is a little bit awkward, calling some methods through
4449 		// the underlying SimpleWindow `win` method, and you can't do this
4450 		// on a nanovega widget due to conflicts so I should probably fix
4451 		// the api to be a bit easier. But here it will work.
4452 		//
4453 		// Alternatively, you could load the font on the first draw, inside
4454 		// the redrawOpenGlScene, and keep a flag so you don't do it every
4455 		// time. That'd be a bit easier since the lib sets up the context
4456 		// by then guaranteed.
4457 		//
4458 		// But still, I wanna show this.
4459 		widget.win.visibleForTheFirstTime = delegate {
4460 			// must set the opengl context
4461 			widget.win.setAsCurrentOpenGlContext();
4462 
4463 			// if you were doing a OpenGL 3+ shader, this
4464 			// gets especially important to do in order. With
4465 			// old-style opengl, I think you can even do it
4466 			// in main(), but meh, let's show it more correctly.
4467 
4468 			// Anyway, now it is time to load the font from the
4469 			// OS (you can alternatively load one from a .ttf file
4470 			// you bundle with the application), then load the
4471 			// font into texture for drawing.
4472 
4473 			auto osfont = new OperatingSystemFont("DejaVu Sans", 18);
4474 
4475 			assert(!osfont.isNull()); // make sure it actually loaded
4476 
4477 			// using typeof to avoid repeating the long name lol
4478 			glfont = new typeof(glfont)(
4479 				// get the raw data from the font for loading in here
4480 				// since it doesn't use the OS function to draw the
4481 				// text, we gotta treat it more as a file than as
4482 				// a drawing api.
4483 				osfont.getTtfBytes(),
4484 				18, // need to respecify size since opengl world is different coordinate system
4485 
4486 				// these last two numbers are why it is called
4487 				// "Limited" font. It only loads the characters
4488 				// in the given range, since the texture atlas
4489 				// it references is all a big image generated ahead
4490 				// of time. You could maybe do the whole thing but
4491 				// idk how much memory that is.
4492 				//
4493 				// But here, 0-128 represents the ASCII range, so
4494 				// good enough for most English things, numeric labels,
4495 				// etc.
4496 				0,
4497 				128
4498 			);
4499 		};
4500 
4501 		widget.redrawOpenGlScene = () {
4502 			// now we can use the glfont's drawString function
4503 
4504 			// first some opengl setup. You can do this in one place
4505 			// on window first visible too in many cases, just showing
4506 			// here cuz it is easier for me.
4507 
4508 			// gonna need some alpha blending or it just looks awful
4509 			glEnable(GL_BLEND);
4510 			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
4511 			glClearColor(0,0,0,0);
4512 			glDepthFunc(GL_LEQUAL);
4513 
4514 			// Also need to enable 2d textures, since it draws the
4515 			// font characters as images baked in
4516 			glMatrixMode(GL_MODELVIEW);
4517 			glLoadIdentity();
4518 			glDisable(GL_DEPTH_TEST);
4519 			glEnable(GL_TEXTURE_2D);
4520 
4521 			// the orthographic matrix is best for 2d things like text
4522 			// so let's set that up. This matrix makes the coordinates
4523 			// in the opengl scene be one-to-one with the actual pixels
4524 			// on screen. (Not necessarily best, you may wish to scale
4525 			// things, but it does help keep fonts looking normal.)
4526 			glMatrixMode(GL_PROJECTION);
4527 			glLoadIdentity();
4528 			glOrtho(0, widget.width, widget.height, 0, 0, 1);
4529 
4530 			// you can do other glScale, glRotate, glTranslate, etc
4531 			// to the matrix here of course if you want.
4532 
4533 			// note the x,y coordinates here are for the text baseline
4534 			// NOT the upper-left corner. The baseline is like the line
4535 			// in the notebook you write on. Most the letters are actually
4536 			// above it, but some, like p and q, dip a bit below it.
4537 			//
4538 			// So if you're used to the upper left coordinate like the
4539 			// rest of simpledisplay/minigui usually do, do the
4540 			// y + glfont.ascent to bring it down a little. So this
4541 			// example puts the string in the upper left of the window.
4542 			glfont.drawString(0, 0 + glfont.ascent, "Hello!!", Color.green);
4543 
4544 			// re color btw: the function sets a solid color internally,
4545 			// but you actually COULD do your own thing for rainbow effects
4546 			// and the sort if you wanted too, by pulling its guts out.
4547 			// Just view its source for an idea of how it actually draws:
4548 			// http://arsd-official.dpldocs.info/source/arsd.ttf.d.html#L332
4549 
4550 			// it gets a bit complicated with the character positioning,
4551 			// but the opengl parts are fairly simple: bind a texture,
4552 			// set the color, draw a quad for each letter.
4553 
4554 
4555 			// the last optional argument there btw is a bounding box
4556 			// it will/ use to word wrap and return an object you can
4557 			// use to implement scrolling or pagination; it tells how
4558 			// much of the string didn't fit in the box. But for simple
4559 			// labels we can just ignore that.
4560 
4561 
4562 			// I'd suggest drawing text as the last step, after you
4563 			// do your other drawing. You might use the push/pop matrix
4564 			// stuff to keep your place. You, in theory, should be able
4565 			// to do text in a 3d space but I've never actually tried
4566 			// that....
4567 		};
4568 
4569 		window.loop();
4570 	}
4571 }
4572 
4573 version(custom_widgets)
4574 	private alias ListWidgetBase = ScrollableWidget;
4575 else
4576 	private alias ListWidgetBase = Widget;
4577 
4578 /++
4579 	A list widget contains a list of strings that the user can examine and select.
4580 
4581 
4582 	In the future, items in the list may be possible to be more than just strings.
4583 
4584 	See_Also:
4585 		[TableView]
4586 +/
4587 class ListWidget : ListWidgetBase {
4588 	/// 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.
4589 	mixin Emits!(ChangeEvent!void);
4590 
4591 	static struct Option {
4592 		string label;
4593 		bool selected;
4594 		void* tag;
4595 	}
4596 
4597 	/++
4598 		Sets the current selection to the `y`th item in the list. Will emit [ChangeEvent] when complete.
4599 	+/
4600 	void setSelection(int y) {
4601 		if(!multiSelect)
4602 			foreach(ref opt; options)
4603 				opt.selected = false;
4604 		if(y >= 0 && y < options.length)
4605 			options[y].selected = !options[y].selected;
4606 
4607 		this.emit!(ChangeEvent!void)(delegate {});
4608 
4609 		version(custom_widgets)
4610 			redraw();
4611 	}
4612 
4613 	/++
4614 		Gets the index of the selected item. In case of multi select, the index of the first selected item is returned.
4615 		Returns -1 if nothing is selected.
4616 	+/
4617 	int getSelection()
4618 	{
4619 		foreach(i, opt; options) {
4620 			if (opt.selected)
4621 				return cast(int) i;
4622 		}
4623 		return -1;
4624 	}
4625 
4626 	version(custom_widgets)
4627 	override void defaultEventHandler_click(ClickEvent event) {
4628 		this.focus();
4629 		if(event.button == MouseButton.left) {
4630 			auto y = (event.clientY - 4) / defaultLineHeight;
4631 			if(y >= 0 && y < options.length) {
4632 				setSelection(y);
4633 			}
4634 		}
4635 		super.defaultEventHandler_click(event);
4636 	}
4637 
4638 	this(Widget parent) {
4639 		tabStop = false;
4640 		super(parent);
4641 		version(win32_widgets)
4642 			createWin32Window(this, WC_LISTBOX, "", 
4643 				0|WS_CHILD|WS_VISIBLE|LBS_NOTIFY, 0);
4644 	}
4645 
4646 	version(win32_widgets)
4647 	override void handleWmCommand(ushort code, ushort id) {
4648 		switch(code) {
4649 			case LBN_SELCHANGE:
4650 				auto sel = SendMessageW(hwnd, LB_GETCURSEL, 0, 0);
4651 				setSelection(cast(int) sel);
4652 			break;
4653 			default:
4654 		}
4655 	}
4656 
4657 
4658 	version(custom_widgets)
4659 	override void paintFrameAndBackground(WidgetPainter painter) {
4660 		draw3dFrame(this, painter, FrameStyle.sunk, Color.white);
4661 	}
4662 
4663 	version(custom_widgets)
4664 	override void paint(WidgetPainter painter) {
4665 		auto cs = getComputedStyle();
4666 		auto pos = Point(4, 4);
4667 		foreach(idx, option; options) {
4668 			painter.fillColor = Color.white;
4669 			painter.outlineColor = Color.white;
4670 			painter.drawRectangle(pos, width - 8, defaultLineHeight);
4671 			painter.outlineColor = cs.foregroundColor;
4672 			painter.drawText(pos, option.label);
4673 			if(option.selected) {
4674 				painter.rasterOp = RasterOp.xor;
4675 				painter.outlineColor = Color.white;
4676 				painter.fillColor = cs.activeListXorColor;
4677 				painter.drawRectangle(pos, width - 8, defaultLineHeight);
4678 				painter.rasterOp = RasterOp.normal;
4679 			}
4680 			pos.y += defaultLineHeight;
4681 		}
4682 	}
4683 
4684 	static class Style : Widget.Style {
4685 		override WidgetBackground background() {
4686 			return WidgetBackground(WidgetPainter.visualTheme.widgetBackgroundColor);
4687 		}
4688 	}
4689 	mixin OverrideStyle!Style;
4690 	//mixin Padding!q{2};
4691 
4692 	void addOption(string text, void* tag = null) {
4693 		options ~= Option(text, false, tag);
4694 		version(win32_widgets) {
4695 			WCharzBuffer buffer = WCharzBuffer(text);
4696 			SendMessageW(hwnd, LB_ADDSTRING, 0, cast(LPARAM) buffer.ptr);
4697 		}
4698 		version(custom_widgets) {
4699 			setContentSize(width, cast(int) (options.length * defaultLineHeight));
4700 			redraw();
4701 		}
4702 	}
4703 
4704 	void clear() {
4705 		options = null;
4706 		version(win32_widgets) {
4707 			while(SendMessageW(hwnd, LB_DELETESTRING, 0, 0) > 0)
4708 				{}
4709 
4710 		} else version(custom_widgets) {
4711 			scrollTo(Point(0, 0));
4712 			redraw();
4713 		}
4714 	}
4715 
4716 	Option[] options;
4717 	version(win32_widgets)
4718 		enum multiSelect = false; /// not implemented yet
4719 	else
4720 		bool multiSelect;
4721 
4722 	override int heightStretchiness() { return 6; }
4723 }
4724 
4725 
4726 
4727 /// For [ScrollableWidget], determines when to show the scroll bar to the user.
4728 enum ScrollBarShowPolicy {
4729 	automatic, /// automatically show the scroll bar if it is necessary
4730 	never, /// never show the scroll bar (scrolling must be done programmatically)
4731 	always /// always show the scroll bar, even if it is disabled
4732 }
4733 
4734 /++
4735 	A widget that tries (with, at best, limited success) to offer scrolling that is transparent to the inner.
4736 
4737 	It isn't very good and will very likely be removed. Try [ScrollMessageWidget] or [ScrollableContainerWidget] instead for new code.
4738 +/
4739 // FIXME ScrollBarShowPolicy
4740 // FIXME: use the ScrollMessageWidget in here now that it exists
4741 class ScrollableWidget : Widget {
4742 	// FIXME: make line size configurable
4743 	// FIXME: add keyboard controls
4744 	version(win32_widgets) {
4745 		override int hookedWndProc(UINT msg, WPARAM wParam, LPARAM lParam) {
4746 			if(msg == WM_VSCROLL || msg == WM_HSCROLL) {
4747 				auto pos = HIWORD(wParam);
4748 				auto m = LOWORD(wParam);
4749 
4750 				// FIXME: I can reintroduce the
4751 				// scroll bars now by using this
4752 				// in the top-level window handler
4753 				// to forward comamnds
4754 				auto scrollbarHwnd = lParam;
4755 				switch(m) {
4756 					case SB_BOTTOM:
4757 						if(msg == WM_HSCROLL)
4758 							horizontalScrollTo(contentWidth_);
4759 						else
4760 							verticalScrollTo(contentHeight_);
4761 					break;
4762 					case SB_TOP:
4763 						if(msg == WM_HSCROLL)
4764 							horizontalScrollTo(0);
4765 						else
4766 							verticalScrollTo(0);
4767 					break;
4768 					case SB_ENDSCROLL:
4769 						// idk
4770 					break;
4771 					case SB_LINEDOWN:
4772 						if(msg == WM_HSCROLL)
4773 							horizontalScroll(scaleWithDpi(16));
4774 						else
4775 							verticalScroll(scaleWithDpi(16));
4776 					break;
4777 					case SB_LINEUP:
4778 						if(msg == WM_HSCROLL)
4779 							horizontalScroll(scaleWithDpi(-16));
4780 						else
4781 							verticalScroll(scaleWithDpi(-16));
4782 					break;
4783 					case SB_PAGEDOWN:
4784 						if(msg == WM_HSCROLL)
4785 							horizontalScroll(scaleWithDpi(100));
4786 						else
4787 							verticalScroll(scaleWithDpi(100));
4788 					break;
4789 					case SB_PAGEUP:
4790 						if(msg == WM_HSCROLL)
4791 							horizontalScroll(scaleWithDpi(-100));
4792 						else
4793 							verticalScroll(scaleWithDpi(-100));
4794 					break;
4795 					case SB_THUMBPOSITION:
4796 					case SB_THUMBTRACK:
4797 						if(msg == WM_HSCROLL)
4798 							horizontalScrollTo(pos);
4799 						else
4800 							verticalScrollTo(pos);
4801 
4802 						if(m == SB_THUMBTRACK) {
4803 							// the event loop doesn't seem to carry on with a requested redraw..
4804 							// so we request it to get our dirty bit set...
4805 							redraw();
4806 
4807 							// then we need to immediately actually redraw it too for instant feedback to user
4808 
4809 							SimpleWindow.processAllCustomEvents();
4810 							//if(parentWindow)
4811 								//parentWindow.actualRedraw();
4812 						}
4813 					break;
4814 					default:
4815 				}
4816 			}
4817 			return super.hookedWndProc(msg, wParam, lParam);
4818 		}
4819 	}
4820 	///
4821 	this(Widget parent) {
4822 		this.parentWindow = parent.parentWindow;
4823 
4824 		version(win32_widgets) {
4825 			createWin32Window(this, Win32Class!"arsd_minigui_ScrollableWidget"w, "", 
4826 				0|WS_CHILD|WS_VISIBLE|WS_HSCROLL|WS_VSCROLL, 0);
4827 			super(parent);
4828 		} else version(custom_widgets) {
4829 			outerContainer = new InternalScrollableContainerWidget(this, parent);
4830 			super(outerContainer);
4831 		} else static assert(0);
4832 	}
4833 
4834 	version(custom_widgets)
4835 		InternalScrollableContainerWidget outerContainer;
4836 
4837 	override void defaultEventHandler_click(ClickEvent event) {
4838 		if(event.button == MouseButton.wheelUp)
4839 			verticalScroll(scaleWithDpi(-16));
4840 		if(event.button == MouseButton.wheelDown)
4841 			verticalScroll(scaleWithDpi(16));
4842 		super.defaultEventHandler_click(event);
4843 	}
4844 
4845 	override void defaultEventHandler_keydown(KeyDownEvent event) {
4846 		switch(event.key) {
4847 			case Key.Left:
4848 				horizontalScroll(scaleWithDpi(-16));
4849 			break;
4850 			case Key.Right:
4851 				horizontalScroll(scaleWithDpi(16));
4852 			break;
4853 			case Key.Up:
4854 				verticalScroll(scaleWithDpi(-16));
4855 			break;
4856 			case Key.Down:
4857 				verticalScroll(scaleWithDpi(16));
4858 			break;
4859 			case Key.Home:
4860 				verticalScrollTo(0);
4861 			break;
4862 			case Key.End:
4863 				verticalScrollTo(contentHeight);
4864 			break;
4865 			case Key.PageUp:
4866 				verticalScroll(scaleWithDpi(-160));
4867 			break;
4868 			case Key.PageDown:
4869 				verticalScroll(scaleWithDpi(160));
4870 			break;
4871 			default:
4872 		}
4873 		super.defaultEventHandler_keydown(event);
4874 	}
4875 
4876 
4877 	version(win32_widgets)
4878 	override void recomputeChildLayout() {
4879 		super.recomputeChildLayout();
4880 		SCROLLINFO info;
4881 		info.cbSize = info.sizeof;
4882 		info.nPage = viewportHeight;
4883 		info.fMask = SIF_PAGE | SIF_RANGE;
4884 		info.nMin = 0;
4885 		info.nMax = contentHeight_;
4886 		SetScrollInfo(hwnd, SB_VERT, &info, true);
4887 
4888 		info.cbSize = info.sizeof;
4889 		info.nPage = viewportWidth;
4890 		info.fMask = SIF_PAGE | SIF_RANGE;
4891 		info.nMin = 0;
4892 		info.nMax = contentWidth_;
4893 		SetScrollInfo(hwnd, SB_HORZ, &info, true);
4894 	}
4895 
4896 	/*
4897 		Scrolling
4898 		------------
4899 
4900 		You are assigned a width and a height by the layout engine, which
4901 		is your viewport box. However, you may draw more than that by setting
4902 		a contentWidth and contentHeight.
4903 
4904 		If these can be contained by the viewport, no scrollbar is displayed.
4905 		If they cannot fit though, it will automatically show scroll as necessary.
4906 
4907 		If contentWidth == 0, no horizontal scrolling is performed. If contentHeight
4908 		is zero, no vertical scrolling is performed.
4909 
4910 		If scrolling is necessary, the lib will automatically work with the bars.
4911 		When you redraw, the origin and clipping info in the painter is set so if
4912 		you just draw everything, it will work, but you can be more efficient by checking
4913 		the viewportWidth, viewportHeight, and scrollOrigin members.
4914 	*/
4915 
4916 	///
4917 	final @property int viewportWidth() {
4918 		return width - (showingVerticalScroll ? scaleWithDpi(16) : 0);
4919 	}
4920 	///
4921 	final @property int viewportHeight() {
4922 		return height - (showingHorizontalScroll ? scaleWithDpi(16) : 0);
4923 	}
4924 
4925 	// FIXME property
4926 	Point scrollOrigin_;
4927 
4928 	///
4929 	final const(Point) scrollOrigin() {
4930 		return scrollOrigin_;
4931 	}
4932 
4933 	// the user sets these two
4934 	private int contentWidth_ = 0;
4935 	private int contentHeight_ = 0;
4936 
4937 	///
4938 	int contentWidth() { return contentWidth_; }
4939 	///
4940 	int contentHeight() { return contentHeight_; }
4941 
4942 	///
4943 	void setContentSize(int width, int height) {
4944 		contentWidth_ = width;
4945 		contentHeight_ = height;
4946 
4947 		version(custom_widgets) {
4948 			if(showingVerticalScroll || showingHorizontalScroll) {
4949 				outerContainer.recomputeChildLayout();
4950 			}
4951 
4952 			if(showingVerticalScroll())
4953 				outerContainer.verticalScrollBar.redraw();
4954 			if(showingHorizontalScroll())
4955 				outerContainer.horizontalScrollBar.redraw();
4956 		} else version(win32_widgets) {
4957 			recomputeChildLayout();
4958 		} else static assert(0);
4959 	}
4960 
4961 	///
4962 	void verticalScroll(int delta) {
4963 		verticalScrollTo(scrollOrigin.y + delta);
4964 	}
4965 	///
4966 	void verticalScrollTo(int pos) {
4967 		scrollOrigin_.y = pos;
4968 		if(pos == int.max || (scrollOrigin_.y + viewportHeight > contentHeight))
4969 			scrollOrigin_.y = contentHeight - viewportHeight;
4970 
4971 		if(scrollOrigin_.y < 0)
4972 			scrollOrigin_.y = 0;
4973 
4974 		version(win32_widgets) {
4975 			SCROLLINFO info;
4976 			info.cbSize = info.sizeof;
4977 			info.fMask = SIF_POS;
4978 			info.nPos = scrollOrigin_.y;
4979 			SetScrollInfo(hwnd, SB_VERT, &info, true);
4980 		} else version(custom_widgets) {
4981 			outerContainer.verticalScrollBar.setPosition(scrollOrigin_.y);
4982 		} else static assert(0);
4983 
4984 		redraw();
4985 	}
4986 
4987 	///
4988 	void horizontalScroll(int delta) {
4989 		horizontalScrollTo(scrollOrigin.x + delta);
4990 	}
4991 	///
4992 	void horizontalScrollTo(int pos) {
4993 		scrollOrigin_.x = pos;
4994 		if(pos == int.max || (scrollOrigin_.x + viewportWidth > contentWidth))
4995 			scrollOrigin_.x = contentWidth - viewportWidth;
4996 
4997 		if(scrollOrigin_.x < 0)
4998 			scrollOrigin_.x = 0;
4999 
5000 		version(win32_widgets) {
5001 			SCROLLINFO info;
5002 			info.cbSize = info.sizeof;
5003 			info.fMask = SIF_POS;
5004 			info.nPos = scrollOrigin_.x;
5005 			SetScrollInfo(hwnd, SB_HORZ, &info, true);
5006 		} else version(custom_widgets) {
5007 			outerContainer.horizontalScrollBar.setPosition(scrollOrigin_.x);
5008 		} else static assert(0);
5009 
5010 		redraw();
5011 	}
5012 	///
5013 	void scrollTo(Point p) {
5014 		verticalScrollTo(p.y);
5015 		horizontalScrollTo(p.x);
5016 	}
5017 
5018 	///
5019 	void ensureVisibleInScroll(Point p) {
5020 		auto rect = viewportRectangle();
5021 		if(rect.contains(p))
5022 			return;
5023 		if(p.x < rect.left)
5024 			horizontalScroll(p.x - rect.left);
5025 		else if(p.x > rect.right)
5026 			horizontalScroll(p.x - rect.right);
5027 
5028 		if(p.y < rect.top)
5029 			verticalScroll(p.y - rect.top);
5030 		else if(p.y > rect.bottom)
5031 			verticalScroll(p.y - rect.bottom);
5032 	}
5033 
5034 	///
5035 	void ensureVisibleInScroll(Rectangle rect) {
5036 		ensureVisibleInScroll(rect.upperLeft);
5037 		ensureVisibleInScroll(rect.lowerRight);
5038 	}
5039 
5040 	///
5041 	Rectangle viewportRectangle() {
5042 		return Rectangle(scrollOrigin, Size(viewportWidth, viewportHeight));
5043 	}
5044 
5045 	///
5046 	bool showingHorizontalScroll() {
5047 		return contentWidth > width;
5048 	}
5049 	///
5050 	bool showingVerticalScroll() {
5051 		return contentHeight > height;
5052 	}
5053 
5054 	/// This is called before the ordinary paint delegate,
5055 	/// giving you a chance to draw the window frame, etc,
5056 	/// before the scroll clip takes effect
5057 	void paintFrameAndBackground(WidgetPainter painter) {
5058 		version(win32_widgets) {
5059 			auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
5060 			auto p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
5061 			// since the pen is null, to fill the whole space, we need the +1 on both.
5062 			gdi.Rectangle(painter.impl.hdc, 0, 0, this.width + 1, this.height + 1);
5063 			SelectObject(painter.impl.hdc, p);
5064 			SelectObject(painter.impl.hdc, b);
5065 		}
5066 
5067 	}
5068 
5069 	// make space for the scroll bar, and that's it.
5070 	final override int paddingRight() { return scaleWithDpi(16); }
5071 	final override int paddingBottom() { return scaleWithDpi(16); }
5072 
5073 	/*
5074 		END SCROLLING
5075 	*/
5076 
5077 	override WidgetPainter draw() {
5078 		int x = this.x, y = this.y;
5079 		auto parent = this.parent;
5080 		while(parent) {
5081 			x += parent.x;
5082 			y += parent.y;
5083 			parent = parent.parent;
5084 		}
5085 
5086 		//version(win32_widgets) {
5087 			//auto painter = simpleWindowWrappingHwnd ? simpleWindowWrappingHwnd.draw(true) : parentWindow.win.draw(true);
5088 		//} else {
5089 			auto painter = parentWindow.win.draw(true);
5090 		//}
5091 		painter.originX = x;
5092 		painter.originY = y;
5093 
5094 		painter.originX = painter.originX - scrollOrigin.x;
5095 		painter.originY = painter.originY - scrollOrigin.y;
5096 		painter.setClipRectangle(scrollOrigin, viewportWidth(), viewportHeight());
5097 
5098 		return WidgetPainter(painter, this);
5099 	}
5100 
5101 	mixin ScrollableChildren;
5102 }
5103 
5104 // you need to have a Point scrollOrigin in the class somewhere
5105 // and a paintFrameAndBackground
5106 private mixin template ScrollableChildren() {
5107 	override protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force, bool invalidate) {
5108 		if(hidden)
5109 			return;
5110 
5111 		//version(win32_widgets)
5112 			//painter = simpleWindowWrappingHwnd ? simpleWindowWrappingHwnd.draw(true) : parentWindow.win.draw(true);
5113 
5114 		painter.originX = lox + x;
5115 		painter.originY = loy + y;
5116 
5117 		bool actuallyPainted = false;
5118 
5119 		const clip = containment.intersectionOf(Rectangle(Point(lox + x, loy + y), Size(width, height)));
5120 		if(clip == Rectangle.init)
5121 			return;
5122 
5123 		if(force || redrawRequested) {
5124 			//painter.setClipRectangle(scrollOrigin, width, height);
5125 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
5126 			paintFrameAndBackground(painter);
5127 		}
5128 
5129 		painter.originX = painter.originX - scrollOrigin.x;
5130 		painter.originY = painter.originY - scrollOrigin.y;
5131 		if(force || redrawRequested) {
5132 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY) + Point(2, 2) /* border */, clip.width - 4, clip.height - 4);
5133 			//painter.setClipRectangle(scrollOrigin + Point(2, 2) /* border */, width - 4, height - 4);
5134 
5135 			//erase(painter); // we paintFrameAndBackground above so no need
5136 			if(painter.visualTheme)
5137 				painter.visualTheme.doPaint(this, painter);
5138 			else
5139 				paint(painter);
5140 
5141 			if(invalidate) {
5142 				painter.invalidateRect(Rectangle(Point(clip.upperLeft.x - painter.originX, clip.upperRight.y - painter.originY), Size(clip.width, clip.height)));
5143 				// children are contained inside this, so no need to do extra work
5144 				invalidate = false;
5145 			}
5146 
5147 
5148 			actuallyPainted = true;
5149 			redrawRequested = false;
5150 		}
5151 		foreach(child; children) {
5152 			if(cast(FixedPosition) child)
5153 				child.privatePaint(painter, painter.originX + scrollOrigin.x, painter.originY + scrollOrigin.y, clip, actuallyPainted, invalidate);
5154 			else
5155 				child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted, invalidate);
5156 		}
5157 	}
5158 }
5159 
5160 private class InternalScrollableContainerInsideWidget : ContainerWidget {
5161 	ScrollableContainerWidget scw;
5162 
5163 	this(ScrollableContainerWidget parent) {
5164 		scw = parent;
5165 		super(parent);
5166 	}
5167 
5168 	version(custom_widgets)
5169 	override protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force, bool invalidate) {
5170 		if(hidden)
5171 			return;
5172 
5173 		bool actuallyPainted = false;
5174 
5175 		auto scrollOrigin = Point(scw.scrollX_, scw.scrollY_);
5176 
5177 		const clip = containment.intersectionOf(Rectangle(Point(lox + x, loy + y), Size(width + scw.scrollX_, height + scw.scrollY_)));
5178 		if(clip == Rectangle.init)
5179 			return;
5180 
5181 		painter.originX = lox + x - scrollOrigin.x;
5182 		painter.originY = loy + y - scrollOrigin.y;
5183 		if(force || redrawRequested) {
5184 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
5185 
5186 			erase(painter);
5187 			if(painter.visualTheme)
5188 				painter.visualTheme.doPaint(this, painter);
5189 			else
5190 				paint(painter);
5191 
5192 			if(invalidate) {
5193 				painter.invalidateRect(Rectangle(Point(clip.upperLeft.x - painter.originX, clip.upperRight.y - painter.originY), Size(clip.width, clip.height)));
5194 				// children are contained inside this, so no need to do extra work
5195 				invalidate = false;
5196 			}
5197 
5198 			actuallyPainted = true;
5199 			redrawRequested = false;
5200 		}
5201 		foreach(child; children) {
5202 			if(cast(FixedPosition) child)
5203 				child.privatePaint(painter, painter.originX + scrollOrigin.x, painter.originY + scrollOrigin.y, clip, actuallyPainted, invalidate);
5204 			else
5205 				child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted, invalidate);
5206 		}
5207 	}
5208 
5209 	version(custom_widgets)
5210 	override protected void addScrollPosition(ref int x, ref int y) {
5211 		x += scw.scrollX_;
5212 		y += scw.scrollY_;
5213 	}
5214 }
5215 
5216 /++
5217 	A widget meant to contain other widgets that may need to scroll.
5218 
5219 	Currently buggy.
5220 
5221 	History:
5222 		Added July 1, 2021 (dub v10.2)
5223 
5224 		On January 3, 2022, I tried to use it in a few other cases
5225 		and found it only worked well in the original test case. Since
5226 		it still sucks, I think I'm going to rewrite it again.
5227 +/
5228 class ScrollableContainerWidget : ContainerWidget {
5229 	///
5230 	this(Widget parent) {
5231 		super(parent);
5232 
5233 		container = new InternalScrollableContainerInsideWidget(this);
5234 		hsb = new HorizontalScrollbar(this);
5235 		vsb = new VerticalScrollbar(this);
5236 
5237 		tabStop = false;
5238 		container.tabStop = false;
5239 		magic = true;
5240 
5241 
5242 		vsb.addEventListener("scrolltonextline", () {
5243 			scrollBy(0, scaleWithDpi(16));
5244 		});
5245 		vsb.addEventListener("scrolltopreviousline", () {
5246 			scrollBy(0,scaleWithDpi( -16));
5247 		});
5248 		vsb.addEventListener("scrolltonextpage", () {
5249 			scrollBy(0, container.height);
5250 		});
5251 		vsb.addEventListener("scrolltopreviouspage", () {
5252 			scrollBy(0, -container.height);
5253 		});
5254 		vsb.addEventListener((scope ScrollToPositionEvent spe) {
5255 			scrollTo(scrollX_, spe.value);
5256 		});
5257 
5258 		this.addEventListener(delegate (scope ClickEvent e) {
5259 			if(e.button == MouseButton.wheelUp) {
5260 				if(!e.defaultPrevented)
5261 					scrollBy(0, scaleWithDpi(-16));
5262 				e.stopPropagation();
5263 			} else if(e.button == MouseButton.wheelDown) {
5264 				if(!e.defaultPrevented)
5265 					scrollBy(0, scaleWithDpi(16));
5266 				e.stopPropagation();
5267 			}
5268 		});
5269 	}
5270 
5271 	/+
5272 	override void defaultEventHandler_click(ClickEvent e) {
5273 	}
5274 	+/
5275 
5276 	override void removeAllChildren() {
5277 		container.removeAllChildren();
5278 	}
5279 
5280 	void scrollTo(int x, int y) {
5281 		scrollBy(x - scrollX_, y - scrollY_);
5282 	}
5283 
5284 	void scrollBy(int x, int y) {
5285 		auto ox = scrollX_;
5286 		auto oy = scrollY_;
5287 
5288 		auto nx = ox + x;
5289 		auto ny = oy + y;
5290 
5291 		if(nx < 0)
5292 			nx = 0;
5293 		if(ny < 0)
5294 			ny = 0;
5295 
5296 		auto maxX = hsb.max - container.width;
5297 		if(maxX < 0) maxX = 0;
5298 		auto maxY = vsb.max - container.height;
5299 		if(maxY < 0) maxY = 0;
5300 
5301 		if(nx > maxX)
5302 			nx = maxX;
5303 		if(ny > maxY)
5304 			ny = maxY;
5305 
5306 		auto dx = nx - ox;
5307 		auto dy = ny - oy;
5308 
5309 		if(dx || dy) {
5310 			version(win32_widgets)
5311 				ScrollWindowEx(container.hwnd, -dx, -dy, null, null, null, null, SW_SCROLLCHILDREN | SW_INVALIDATE | SW_ERASE);
5312 			else {
5313 				redraw();
5314 			}
5315 
5316 			hsb.setPosition = nx;
5317 			vsb.setPosition = ny;
5318 
5319 			scrollX_ = nx;
5320 			scrollY_ = ny;
5321 		}
5322 	}
5323 
5324 	private int scrollX_;
5325 	private int scrollY_;
5326 
5327 	void setTotalArea(int width, int height) {
5328 		hsb.setMax(width);
5329 		vsb.setMax(height);
5330 	}
5331 
5332 	///
5333 	void setViewableArea(int width, int height) {
5334 		hsb.setViewableArea(width);
5335 		vsb.setViewableArea(height);
5336 	}
5337 
5338 	private bool magic;
5339 	override void addChild(Widget w, int position = int.max) {
5340 		if(magic)
5341 			container.addChild(w, position);
5342 		else
5343 			super.addChild(w, position);
5344 	}
5345 
5346 	override void recomputeChildLayout() {
5347 		if(hsb is null || vsb is null || container is null) return;
5348 
5349 		/+
5350 		import std.stdio; writeln(x, " ", y , " ", width, " ", height);
5351 		writeln(this.ContainerWidget.minWidth(), "x", this.ContainerWidget.minHeight());
5352 		+/
5353 
5354 		registerMovement();
5355 
5356 		hsb.height = scaleWithDpi(16); // FIXME? are tese 16s sane?
5357 		hsb.x = 0;
5358 		hsb.y = this.height - hsb.height;
5359 		hsb.width = this.width - scaleWithDpi(16);
5360 		hsb.recomputeChildLayout();
5361 
5362 		vsb.width = scaleWithDpi(16); // FIXME?
5363 		vsb.x = this.width - vsb.width;
5364 		vsb.y = 0;
5365 		vsb.height = this.height - scaleWithDpi(16);
5366 		vsb.recomputeChildLayout();
5367 
5368 		container.x = 0;
5369 		container.y = 0;
5370 		container.width = this.width - vsb.width;
5371 		container.height = this.height - hsb.height;
5372 		container.recomputeChildLayout();
5373 
5374 		scrollX_ = 0;
5375 		scrollY_ = 0;
5376 
5377 		hsb.setPosition(0);
5378 		vsb.setPosition(0);
5379 
5380 		int mw, mh;
5381 		Widget c = container;
5382 		// FIXME: hack here to handle a layout inside...
5383 		if(c.children.length == 1 && cast(Layout) c.children[0])
5384 			c = c.children[0];
5385 		foreach(child; c.children) {
5386 			auto w = child.x + child.width;
5387 			auto h = child.y + child.height;
5388 
5389 			if(w > mw) mw = w;
5390 			if(h > mh) mh = h;
5391 		}
5392 
5393 		setTotalArea(mw, mh);
5394 		setViewableArea(width, height);
5395 	}
5396 
5397 	override int minHeight() { return scaleWithDpi(64); }
5398 
5399 	HorizontalScrollbar hsb;
5400 	VerticalScrollbar vsb;
5401 	ContainerWidget container;
5402 }
5403 
5404 
5405 version(custom_widgets)
5406 private class InternalScrollableContainerWidget : Widget {
5407 
5408 	ScrollableWidget sw;
5409 
5410 	VerticalScrollbar verticalScrollBar;
5411 	HorizontalScrollbar horizontalScrollBar;
5412 
5413 	this(ScrollableWidget sw, Widget parent) {
5414 		this.sw = sw;
5415 
5416 		this.tabStop = false;
5417 
5418 		horizontalScrollBar = new HorizontalScrollbar(this);
5419 		verticalScrollBar = new VerticalScrollbar(this);
5420 
5421 		horizontalScrollBar.showing_ = false;
5422 		verticalScrollBar.showing_ = false;
5423 
5424 		horizontalScrollBar.addEventListener("scrolltonextline", {
5425 			horizontalScrollBar.setPosition(horizontalScrollBar.position + 1);
5426 			sw.horizontalScrollTo(horizontalScrollBar.position);
5427 		});
5428 		horizontalScrollBar.addEventListener("scrolltopreviousline", {
5429 			horizontalScrollBar.setPosition(horizontalScrollBar.position - 1);
5430 			sw.horizontalScrollTo(horizontalScrollBar.position);
5431 		});
5432 		verticalScrollBar.addEventListener("scrolltonextline", {
5433 			verticalScrollBar.setPosition(verticalScrollBar.position + 1);
5434 			sw.verticalScrollTo(verticalScrollBar.position);
5435 		});
5436 		verticalScrollBar.addEventListener("scrolltopreviousline", {
5437 			verticalScrollBar.setPosition(verticalScrollBar.position - 1);
5438 			sw.verticalScrollTo(verticalScrollBar.position);
5439 		});
5440 		horizontalScrollBar.addEventListener("scrolltonextpage", {
5441 			horizontalScrollBar.setPosition(horizontalScrollBar.position + horizontalScrollBar.step_);
5442 			sw.horizontalScrollTo(horizontalScrollBar.position);
5443 		});
5444 		horizontalScrollBar.addEventListener("scrolltopreviouspage", {
5445 			horizontalScrollBar.setPosition(horizontalScrollBar.position - horizontalScrollBar.step_);
5446 			sw.horizontalScrollTo(horizontalScrollBar.position);
5447 		});
5448 		verticalScrollBar.addEventListener("scrolltonextpage", {
5449 			verticalScrollBar.setPosition(verticalScrollBar.position + verticalScrollBar.step_);
5450 			sw.verticalScrollTo(verticalScrollBar.position);
5451 		});
5452 		verticalScrollBar.addEventListener("scrolltopreviouspage", {
5453 			verticalScrollBar.setPosition(verticalScrollBar.position - verticalScrollBar.step_);
5454 			sw.verticalScrollTo(verticalScrollBar.position);
5455 		});
5456 		horizontalScrollBar.addEventListener("scrolltoposition", (Event event) {
5457 			horizontalScrollBar.setPosition(event.intValue);
5458 			sw.horizontalScrollTo(horizontalScrollBar.position);
5459 		});
5460 		verticalScrollBar.addEventListener("scrolltoposition", (Event event) {
5461 			verticalScrollBar.setPosition(event.intValue);
5462 			sw.verticalScrollTo(verticalScrollBar.position);
5463 		});
5464 		horizontalScrollBar.addEventListener("scrolltrack", (Event event) {
5465 			horizontalScrollBar.setPosition(event.intValue);
5466 			sw.horizontalScrollTo(horizontalScrollBar.position);
5467 		});
5468 		verticalScrollBar.addEventListener("scrolltrack", (Event event) {
5469 			verticalScrollBar.setPosition(event.intValue);
5470 		});
5471 
5472 		super(parent);
5473 	}
5474 
5475 	// this is supposed to be basically invisible...
5476 	override int minWidth() { return sw.minWidth; }
5477 	override int minHeight() { return sw.minHeight; }
5478 	override int maxWidth() { return sw.maxWidth; }
5479 	override int maxHeight() { return sw.maxHeight; }
5480 	override int widthStretchiness() { return sw.widthStretchiness; }
5481 	override int heightStretchiness() { return sw.heightStretchiness; }
5482 	override int marginLeft() { return sw.marginLeft; }
5483 	override int marginRight() { return sw.marginRight; }
5484 	override int marginTop() { return sw.marginTop; }
5485 	override int marginBottom() { return sw.marginBottom; }
5486 	override int paddingLeft() { return sw.paddingLeft; }
5487 	override int paddingRight() { return sw.paddingRight; }
5488 	override int paddingTop() { return sw.paddingTop; }
5489 	override int paddingBottom() { return sw.paddingBottom; }
5490 	override void focus() { sw.focus(); }
5491 
5492 
5493 	override void recomputeChildLayout() {
5494 		// The stupid thing needs to calculate if a scroll bar is needed...
5495 		recomputeChildLayoutHelper();
5496 		// then running it again will position things correctly if the bar is NOT needed
5497 		recomputeChildLayoutHelper();
5498 
5499 		// this sucks but meh it barely works
5500 	}
5501 
5502 	private void recomputeChildLayoutHelper() {
5503 		if(sw is null) return;
5504 
5505 		bool both = sw.showingVerticalScroll && sw.showingHorizontalScroll;
5506 		if(horizontalScrollBar && verticalScrollBar) {
5507 			horizontalScrollBar.width = this.width - (both ? verticalScrollBar.minWidth() : 0);
5508 			horizontalScrollBar.height = horizontalScrollBar.minHeight();
5509 			horizontalScrollBar.x = 0;
5510 			horizontalScrollBar.y = this.height - horizontalScrollBar.minHeight();
5511 
5512 			verticalScrollBar.width = verticalScrollBar.minWidth();
5513 			verticalScrollBar.height = this.height - (both ? horizontalScrollBar.minHeight() : 0) - 2 - 2;
5514 			verticalScrollBar.x = this.width - verticalScrollBar.minWidth();
5515 			verticalScrollBar.y = 0 + 2;
5516 
5517 			sw.x = 0;
5518 			sw.y = 0;
5519 			sw.width = this.width - (verticalScrollBar.showing ? verticalScrollBar.width : 0);
5520 			sw.height = this.height - (horizontalScrollBar.showing ? horizontalScrollBar.height : 0);
5521 
5522 			if(sw.contentWidth_ <= this.width)
5523 				sw.scrollOrigin_.x = 0;
5524 			if(sw.contentHeight_ <= this.height)
5525 				sw.scrollOrigin_.y = 0;
5526 
5527 			horizontalScrollBar.recomputeChildLayout();
5528 			verticalScrollBar.recomputeChildLayout();
5529 			sw.recomputeChildLayout();
5530 		}
5531 
5532 		if(sw.contentWidth_ <= this.width)
5533 			sw.scrollOrigin_.x = 0;
5534 		if(sw.contentHeight_ <= this.height)
5535 			sw.scrollOrigin_.y = 0;
5536 
5537 		if(sw.showingHorizontalScroll())
5538 			horizontalScrollBar.showing(true, false);
5539 		else
5540 			horizontalScrollBar.showing(false, false);
5541 		if(sw.showingVerticalScroll())
5542 			verticalScrollBar.showing(true, false);
5543 		else
5544 			verticalScrollBar.showing(false, false);
5545 
5546 		verticalScrollBar.setViewableArea(sw.viewportHeight());
5547 		verticalScrollBar.setMax(sw.contentHeight);
5548 		verticalScrollBar.setPosition(sw.scrollOrigin.y);
5549 
5550 		horizontalScrollBar.setViewableArea(sw.viewportWidth());
5551 		horizontalScrollBar.setMax(sw.contentWidth);
5552 		horizontalScrollBar.setPosition(sw.scrollOrigin.x);
5553 	}
5554 }
5555 
5556 /*
5557 class ScrollableClientWidget : Widget {
5558 	this(Widget parent) {
5559 		super(parent);
5560 	}
5561 	override void paint(WidgetPainter p) {
5562 		parent.paint(p);
5563 	}
5564 }
5565 */
5566 
5567 /++
5568 	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.
5569 +/
5570 abstract class Slider : Widget {
5571 	this(int min, int max, int step, Widget parent) {
5572 		min_ = min;
5573 		max_ = max;
5574 		step_ = step;
5575 		page_ = step;
5576 		super(parent);
5577 	}
5578 
5579 	private int min_;
5580 	private int max_;
5581 	private int step_;
5582 	private int position_;
5583 	private int page_;
5584 
5585 	// selection start and selection end
5586 	// tics
5587 	// tooltip?
5588 	// some way to see and just type the value
5589 	// win32 buddy controls are labels
5590 
5591 	///
5592 	void setMin(int a) {
5593 		min_ = a;
5594 		version(custom_widgets)
5595 			redraw();
5596 		version(win32_widgets)
5597 			SendMessage(hwnd, TBM_SETRANGEMIN, true, a);
5598 	}
5599 	///
5600 	int min() {
5601 		return min_;
5602 	}
5603 	///
5604 	void setMax(int a) {
5605 		max_ = a;
5606 		version(custom_widgets)
5607 			redraw();
5608 		version(win32_widgets)
5609 			SendMessage(hwnd, TBM_SETRANGEMAX, true, a);
5610 	}
5611 	///
5612 	int max() {
5613 		return max_;
5614 	}
5615 	///
5616 	void setPosition(int a) {
5617 		if(a > max)
5618 			a = max;
5619 		if(a < min)
5620 			a = min;
5621 		position_ = a;
5622 		version(custom_widgets)
5623 			setPositionCustom(a);
5624 
5625 		version(win32_widgets)
5626 			setPositionWindows(a);
5627 	}
5628 	version(win32_widgets) {
5629 		protected abstract void setPositionWindows(int a);
5630 	}
5631 
5632 	protected abstract int win32direction();
5633 
5634 	/++
5635 		Alias for [position] for better compatibility with generic code.
5636 
5637 		History:
5638 			Added October 5, 2021
5639 	+/
5640 	@property int value() {
5641 		return position;
5642 	}
5643 
5644 	///
5645 	int position() {
5646 		return position_;
5647 	}
5648 	///
5649 	void setStep(int a) {
5650 		step_ = a;
5651 		version(win32_widgets)
5652 			SendMessage(hwnd, TBM_SETLINESIZE, 0, a);
5653 	}
5654 	///
5655 	int step() {
5656 		return step_;
5657 	}
5658 	///
5659 	void setPageSize(int a) {
5660 		page_ = a;
5661 		version(win32_widgets)
5662 			SendMessage(hwnd, TBM_SETPAGESIZE, 0, a);
5663 	}
5664 	///
5665 	int pageSize() {
5666 		return page_;
5667 	}
5668 
5669 	private void notify() {
5670 		auto event = new ChangeEvent!int(this, &this.position);
5671 		event.dispatch();
5672 	}
5673 
5674 	version(win32_widgets)
5675 	void win32Setup(int style) {
5676 		createWin32Window(this, TRACKBAR_CLASS, "", 
5677 			0|WS_CHILD|WS_VISIBLE|style|TBS_TOOLTIPS, 0);
5678 
5679 		// the trackbar sends the same messages as scroll, which
5680 		// our other layer sends as these... just gonna translate
5681 		// here
5682 		this.addDirectEventListener("scrolltoposition", (Event event) {
5683 			event.stopPropagation();
5684 			this.setPosition(this.win32direction > 0 ? event.intValue : max - event.intValue);
5685 			notify();
5686 		});
5687 		this.addDirectEventListener("scrolltonextline", (Event event) {
5688 			event.stopPropagation();
5689 			this.setPosition(this.position + this.step_ * this.win32direction);
5690 			notify();
5691 		});
5692 		this.addDirectEventListener("scrolltopreviousline", (Event event) {
5693 			event.stopPropagation();
5694 			this.setPosition(this.position - this.step_ * this.win32direction);
5695 			notify();
5696 		});
5697 		this.addDirectEventListener("scrolltonextpage", (Event event) {
5698 			event.stopPropagation();
5699 			this.setPosition(this.position + this.page_ * this.win32direction);
5700 			notify();
5701 		});
5702 		this.addDirectEventListener("scrolltopreviouspage", (Event event) {
5703 			event.stopPropagation();
5704 			this.setPosition(this.position - this.page_ * this.win32direction);
5705 			notify();
5706 		});
5707 
5708 		setMin(min_);
5709 		setMax(max_);
5710 		setStep(step_);
5711 		setPageSize(page_);
5712 	}
5713 
5714 	version(custom_widgets) {
5715 		protected MouseTrackingWidget thumb;
5716 
5717 		protected abstract void setPositionCustom(int a);
5718 
5719 		override void defaultEventHandler_keydown(KeyDownEvent event) {
5720 			switch(event.key) {
5721 				case Key.Up:
5722 				case Key.Right:
5723 					setPosition(position() - step() * win32direction);
5724 					changed();
5725 				break;
5726 				case Key.Down:
5727 				case Key.Left:
5728 					setPosition(position() + step() * win32direction);
5729 					changed();
5730 				break;
5731 				case Key.Home:
5732 					setPosition(win32direction > 0 ? min() : max());
5733 					changed();
5734 				break;
5735 				case Key.End:
5736 					setPosition(win32direction > 0 ? max() : min());
5737 					changed();
5738 				break;
5739 				case Key.PageUp:
5740 					setPosition(position() - pageSize() * win32direction);
5741 					changed();
5742 				break;
5743 				case Key.PageDown:
5744 					setPosition(position() + pageSize() * win32direction);
5745 					changed();
5746 				break;
5747 				default:
5748 			}
5749 			super.defaultEventHandler_keydown(event);
5750 		}
5751 
5752 		protected void changed() {
5753 			auto ev = new ChangeEvent!int(this, &position);
5754 			ev.dispatch();
5755 		}
5756 	}
5757 }
5758 
5759 /++
5760 
5761 +/
5762 class VerticalSlider : Slider {
5763 	this(int min, int max, int step, Widget parent) {
5764 		version(custom_widgets)
5765 			initialize();
5766 
5767 		super(min, max, step, parent);
5768 
5769 		version(win32_widgets)
5770 			win32Setup(TBS_VERT | 0x0200 /* TBS_REVERSED */);
5771 	}
5772 
5773 	protected override int win32direction() {
5774 		return -1;
5775 	}
5776 
5777 	version(win32_widgets)
5778 	protected override void setPositionWindows(int a) {
5779 		// the windows thing makes the top 0 and i don't like that.
5780 		SendMessage(hwnd, TBM_SETPOS, true, max - a);
5781 	}
5782 
5783 	version(custom_widgets)
5784 	private void initialize() {
5785 		thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.vertical, this);
5786 
5787 		thumb.tabStop = false;
5788 
5789 		thumb.thumbWidth = width;
5790 		thumb.thumbHeight = scaleWithDpi(16);
5791 
5792 		thumb.addEventListener(EventType.change, () {
5793 			auto sx = thumb.positionY * max() / (thumb.height - scaleWithDpi(16));
5794 			sx = max - sx;
5795 			//informProgramThatUserChangedPosition(sx);
5796 
5797 			position_ = sx;
5798 
5799 			changed();
5800 		});
5801 	}
5802 
5803 	version(custom_widgets)
5804 	override void recomputeChildLayout() {
5805 		thumb.thumbWidth = this.width;
5806 		super.recomputeChildLayout();
5807 		setPositionCustom(position_);
5808 	}
5809 
5810 	version(custom_widgets)
5811 	protected override void setPositionCustom(int a) {
5812 		if(max())
5813 			thumb.positionY = (max - a) * (thumb.height - scaleWithDpi(16)) / max();
5814 		redraw();
5815 	}
5816 }
5817 
5818 /++
5819 
5820 +/
5821 class HorizontalSlider : Slider {
5822 	this(int min, int max, int step, Widget parent) {
5823 		version(custom_widgets)
5824 			initialize();
5825 
5826 		super(min, max, step, parent);
5827 
5828 		version(win32_widgets)
5829 			win32Setup(TBS_HORZ);
5830 	}
5831 
5832 	version(win32_widgets)
5833 	protected override void setPositionWindows(int a) {
5834 		SendMessage(hwnd, TBM_SETPOS, true, a);
5835 	}
5836 
5837 	protected override int win32direction() {
5838 		return 1;
5839 	}
5840 
5841 	version(custom_widgets)
5842 	private void initialize() {
5843 		thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.horizontal, this);
5844 
5845 		thumb.tabStop = false;
5846 
5847 		thumb.thumbWidth = scaleWithDpi(16);
5848 		thumb.thumbHeight = height;
5849 
5850 		thumb.addEventListener(EventType.change, () {
5851 			auto sx = thumb.positionX * max() / (thumb.width - scaleWithDpi(16));
5852 			//informProgramThatUserChangedPosition(sx);
5853 
5854 			position_ = sx;
5855 
5856 			changed();
5857 		});
5858 	}
5859 
5860 	version(custom_widgets)
5861 	override void recomputeChildLayout() {
5862 		thumb.thumbHeight = this.height;
5863 		super.recomputeChildLayout();
5864 		setPositionCustom(position_);
5865 	}
5866 
5867 	version(custom_widgets)
5868 	protected override void setPositionCustom(int a) {
5869 		if(max())
5870 			thumb.positionX = a * (thumb.width - scaleWithDpi(16)) / max();
5871 		redraw();
5872 	}
5873 }
5874 
5875 
5876 ///
5877 abstract class ScrollbarBase : Widget {
5878 	///
5879 	this(Widget parent) {
5880 		super(parent);
5881 		tabStop = false;
5882 		step_ = scaleWithDpi(16);
5883 	}
5884 
5885 	private int viewableArea_;
5886 	private int max_;
5887 	private int step_;// = 16;
5888 	private int position_;
5889 
5890 	///
5891 	bool atEnd() {
5892 		return position_ + viewableArea_ >= max_;
5893 	}
5894 
5895 	///
5896 	bool atStart() {
5897 		return position_ == 0;
5898 	}
5899 
5900 	///
5901 	void setViewableArea(int a) {
5902 		viewableArea_ = a;
5903 		version(custom_widgets)
5904 			redraw();
5905 	}
5906 	///
5907 	void setMax(int a) {
5908 		max_ = a;
5909 		version(custom_widgets)
5910 			redraw();
5911 	}
5912 	///
5913 	int max() {
5914 		return max_;
5915 	}
5916 	///
5917 	void setPosition(int a) {
5918 		if(a == int.max)
5919 			a = max;
5920 		position_ = max ? a : 0;
5921 		if(position_ + viewableArea_ > max)
5922 			position_ = max - viewableArea_;
5923 		if(position_ < 0)
5924 			position_ = 0;
5925 		version(custom_widgets)
5926 			redraw();
5927 	}
5928 	///
5929 	int position() {
5930 		return position_;
5931 	}
5932 	///
5933 	void setStep(int a) {
5934 		step_ = a;
5935 	}
5936 	///
5937 	int step() {
5938 		return step_;
5939 	}
5940 
5941 	// FIXME: remove this.... maybe
5942 	/+
5943 	protected void informProgramThatUserChangedPosition(int n) {
5944 		position_ = n;
5945 		auto evt = new Event(EventType.change, this);
5946 		evt.intValue = n;
5947 		evt.dispatch();
5948 	}
5949 	+/
5950 
5951 	version(custom_widgets) {
5952 		abstract protected int getBarDim();
5953 		int thumbSize() {
5954 			if(viewableArea_ >= max_)
5955 				return getBarDim();
5956 
5957 			int res;
5958 			if(max_) {
5959 				res = getBarDim() * viewableArea_ / max_;
5960 			}
5961 			if(res < 6)
5962 				res = 6;
5963 
5964 			return res;
5965 		}
5966 
5967 		int thumbPosition() {
5968 			/*
5969 				viewableArea_ is the viewport height/width
5970 				position_ is where we are
5971 			*/
5972 			if(max_) {
5973 				if(position_ + viewableArea_ >= max_)
5974 					return getBarDim - thumbSize;
5975 				return getBarDim * position_ / max_;
5976 			}
5977 			return 0;
5978 		}
5979 	}
5980 }
5981 
5982 //public import mgt;
5983 
5984 /++
5985 	A mouse tracking widget is one that follows the mouse when dragged inside it.
5986 
5987 	Concrete subclasses may include a scrollbar thumb and a volume control.
5988 +/
5989 //version(custom_widgets)
5990 class MouseTrackingWidget : Widget {
5991 
5992 	///
5993 	int positionX() { return positionX_; }
5994 	///
5995 	int positionY() { return positionY_; }
5996 
5997 	///
5998 	void positionX(int p) { positionX_ = p; }
5999 	///
6000 	void positionY(int p) { positionY_ = p; }
6001 
6002 	private int positionX_;
6003 	private int positionY_;
6004 
6005 	///
6006 	enum Orientation {
6007 		horizontal, ///
6008 		vertical, ///
6009 		twoDimensional, ///
6010 	}
6011 
6012 	private int thumbWidth_;
6013 	private int thumbHeight_;
6014 
6015 	///
6016 	int thumbWidth() { return thumbWidth_; }
6017 	///
6018 	int thumbHeight() { return thumbHeight_; }
6019 	///
6020 	int thumbWidth(int a) { return thumbWidth_ = a; }
6021 	///
6022 	int thumbHeight(int a) { return thumbHeight_ = a; }
6023 
6024 	private bool dragging;
6025 	private bool hovering;
6026 	private int startMouseX, startMouseY;
6027 
6028 	///
6029 	this(Orientation orientation, Widget parent) {
6030 		super(parent);
6031 
6032 		//assert(parentWindow !is null);
6033 
6034 		addEventListener((MouseDownEvent event) {
6035 			if(event.clientX >= positionX && event.clientX < positionX + thumbWidth && event.clientY >= positionY && event.clientY < positionY + thumbHeight) {
6036 				dragging = true;
6037 				startMouseX = event.clientX - positionX;
6038 				startMouseY = event.clientY - positionY;
6039 				parentWindow.captureMouse(this);
6040 			} else {
6041 				if(orientation == Orientation.horizontal || orientation == Orientation.twoDimensional)
6042 					positionX = event.clientX - thumbWidth / 2;
6043 				if(orientation == Orientation.vertical || orientation == Orientation.twoDimensional)
6044 					positionY = event.clientY - thumbHeight / 2;
6045 
6046 				if(positionX + thumbWidth > this.width)
6047 					positionX = this.width - thumbWidth;
6048 				if(positionY + thumbHeight > this.height)
6049 					positionY = this.height - thumbHeight;
6050 
6051 				if(positionX < 0)
6052 					positionX = 0;
6053 				if(positionY < 0)
6054 					positionY = 0;
6055 
6056 
6057 				// this.emit!(ChangeEvent!void)();
6058 				auto evt = new Event(EventType.change, this);
6059 				evt.sendDirectly();
6060 
6061 				redraw();
6062 
6063 			}
6064 		});
6065 
6066 		addEventListener(EventType.mouseup, (Event event) {
6067 			dragging = false;
6068 			parentWindow.releaseMouseCapture();
6069 		});
6070 
6071 		addEventListener(EventType.mouseout, (Event event) {
6072 			if(!hovering)
6073 				return;
6074 			hovering = false;
6075 			redraw();
6076 		});
6077 
6078 		int lpx, lpy;
6079 
6080 		addEventListener((MouseMoveEvent event) {
6081 			auto oh = hovering;
6082 			if(event.clientX >= positionX && event.clientX < positionX + thumbWidth && event.clientY >= positionY && event.clientY < positionY + thumbHeight) {
6083 				hovering = true;
6084 			} else {
6085 				hovering = false;
6086 			}
6087 			if(!dragging) {
6088 				if(hovering != oh)
6089 					redraw();
6090 				return;
6091 			}
6092 
6093 			if(orientation == Orientation.horizontal || orientation == Orientation.twoDimensional)
6094 				positionX = event.clientX - startMouseX; // FIXME: click could be in the middle of it
6095 			if(orientation == Orientation.vertical || orientation == Orientation.twoDimensional)
6096 				positionY = event.clientY - startMouseY;
6097 
6098 			if(positionX + thumbWidth > this.width)
6099 				positionX = this.width - thumbWidth;
6100 			if(positionY + thumbHeight > this.height)
6101 				positionY = this.height - thumbHeight;
6102 
6103 			if(positionX < 0)
6104 				positionX = 0;
6105 			if(positionY < 0)
6106 				positionY = 0;
6107 
6108 			if(positionX != lpx || positionY != lpy) {
6109 				auto evt = new Event(EventType.change, this);
6110 				evt.sendDirectly();
6111 
6112 				lpx = positionX;
6113 				lpy = positionY;
6114 			}
6115 
6116 			redraw();
6117 		});
6118 	}
6119 
6120 	version(custom_widgets)
6121 	override void paint(WidgetPainter painter) {
6122 		auto cs = getComputedStyle();
6123 		auto c = darken(cs.windowBackgroundColor, 0.2);
6124 		painter.outlineColor = c;
6125 		painter.fillColor = c;
6126 		painter.drawRectangle(Point(0, 0), this.width, this.height);
6127 
6128 		auto color = hovering ? cs.hoveringColor : cs.windowBackgroundColor;
6129 		draw3dFrame(positionX, positionY, thumbWidth, thumbHeight, painter, FrameStyle.risen, color);
6130 	}
6131 }
6132 
6133 //version(custom_widgets)
6134 //private
6135 class HorizontalScrollbar : ScrollbarBase {
6136 
6137 	version(custom_widgets) {
6138 		private MouseTrackingWidget thumb;
6139 
6140 		override int getBarDim() {
6141 			return thumb.width;
6142 		}
6143 	}
6144 
6145 	override void setViewableArea(int a) {
6146 		super.setViewableArea(a);
6147 
6148 		version(win32_widgets) {
6149 			SCROLLINFO info;
6150 			info.cbSize = info.sizeof;
6151 			info.nPage = a + 1;
6152 			info.fMask = SIF_PAGE;
6153 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6154 		} else version(custom_widgets) {
6155 			thumb.positionX = thumbPosition;
6156 			thumb.thumbWidth = thumbSize;
6157 			thumb.redraw();
6158 		} else static assert(0);
6159 
6160 	}
6161 
6162 	override void setMax(int a) {
6163 		super.setMax(a);
6164 		version(win32_widgets) {
6165 			SCROLLINFO info;
6166 			info.cbSize = info.sizeof;
6167 			info.nMin = 0;
6168 			info.nMax = max;
6169 			info.fMask = SIF_RANGE;
6170 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6171 		} else version(custom_widgets) {
6172 			thumb.positionX = thumbPosition;
6173 			thumb.thumbWidth = thumbSize;
6174 			thumb.redraw();
6175 		}
6176 	}
6177 
6178 	override void setPosition(int a) {
6179 		super.setPosition(a);
6180 		version(win32_widgets) {
6181 			SCROLLINFO info;
6182 			info.cbSize = info.sizeof;
6183 			info.fMask = SIF_POS;
6184 			info.nPos = position;
6185 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6186 		} else version(custom_widgets) {
6187 			thumb.positionX = thumbPosition();
6188 			thumb.thumbWidth = thumbSize;
6189 			thumb.redraw();
6190 		} else static assert(0);
6191 	}
6192 
6193 	this(Widget parent) {
6194 		super(parent);
6195 
6196 		version(win32_widgets) {
6197 			createWin32Window(this, "Scrollbar"w, "", 
6198 				0|WS_CHILD|WS_VISIBLE|SBS_HORZ|SBS_BOTTOMALIGN, 0);
6199 		} else version(custom_widgets) {
6200 			auto vl = new HorizontalLayout(this);
6201 			auto leftButton = new ArrowButton(ArrowDirection.left, vl);
6202 			leftButton.setClickRepeat(scrollClickRepeatInterval);
6203 			thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.horizontal, vl);
6204 			auto rightButton = new ArrowButton(ArrowDirection.right, vl);
6205 			rightButton.setClickRepeat(scrollClickRepeatInterval);
6206 
6207 			leftButton.tabStop = false;
6208 			rightButton.tabStop = false;
6209 			thumb.tabStop = false;
6210 
6211 			leftButton.addEventListener(EventType.triggered, () {
6212 				this.emitCommand!"scrolltopreviousline"();
6213 				//informProgramThatUserChangedPosition(position - step());
6214 			});
6215 			rightButton.addEventListener(EventType.triggered, () {
6216 				this.emitCommand!"scrolltonextline"();
6217 				//informProgramThatUserChangedPosition(position + step());
6218 			});
6219 
6220 			thumb.thumbWidth = this.minWidth;
6221 			thumb.thumbHeight = scaleWithDpi(16);
6222 
6223 			thumb.addEventListener(EventType.change, () {
6224 				auto sx = thumb.positionX * max() / thumb.width;
6225 				//informProgramThatUserChangedPosition(sx);
6226 
6227 				auto ev = new ScrollToPositionEvent(this, sx);
6228 				ev.dispatch();
6229 			});
6230 		}
6231 	}
6232 
6233 	override int minHeight() { return scaleWithDpi(16); }
6234 	override int maxHeight() { return scaleWithDpi(16); }
6235 	override int minWidth() { return scaleWithDpi(48); }
6236 }
6237 
6238 class ScrollToPositionEvent : Event {
6239 	enum EventString = "scrolltoposition";
6240 
6241 	this(Widget target, int value) {
6242 		this.value = value;
6243 		super(EventString, target);
6244 	}
6245 
6246 	immutable int value;
6247 
6248 	override @property int intValue() {
6249 		return value;
6250 	}
6251 }
6252 
6253 //version(custom_widgets)
6254 //private
6255 class VerticalScrollbar : ScrollbarBase {
6256 
6257 	version(custom_widgets) {
6258 		override int getBarDim() {
6259 			return thumb.height;
6260 		}
6261 
6262 		private MouseTrackingWidget thumb;
6263 	}
6264 
6265 	override void setViewableArea(int a) {
6266 		super.setViewableArea(a);
6267 
6268 		version(win32_widgets) {
6269 			SCROLLINFO info;
6270 			info.cbSize = info.sizeof;
6271 			info.nPage = a + 1;
6272 			info.fMask = SIF_PAGE;
6273 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6274 		} else version(custom_widgets) {
6275 			thumb.positionY = thumbPosition;
6276 			thumb.thumbHeight = thumbSize;
6277 			thumb.redraw();
6278 		} else static assert(0);
6279 
6280 	}
6281 
6282 	override void setMax(int a) {
6283 		super.setMax(a);
6284 		version(win32_widgets) {
6285 			SCROLLINFO info;
6286 			info.cbSize = info.sizeof;
6287 			info.nMin = 0;
6288 			info.nMax = max;
6289 			info.fMask = SIF_RANGE;
6290 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6291 		} else version(custom_widgets) {
6292 			thumb.positionY = thumbPosition;
6293 			thumb.thumbHeight = thumbSize;
6294 			thumb.redraw();
6295 		}
6296 	}
6297 
6298 	override void setPosition(int a) {
6299 		super.setPosition(a);
6300 		version(win32_widgets) {
6301 			SCROLLINFO info;
6302 			info.cbSize = info.sizeof;
6303 			info.fMask = SIF_POS;
6304 			info.nPos = position;
6305 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6306 		} else version(custom_widgets) {
6307 			thumb.positionY = thumbPosition;
6308 			thumb.thumbHeight = thumbSize;
6309 			thumb.redraw();
6310 		} else static assert(0);
6311 	}
6312 
6313 	this(Widget parent) {
6314 		super(parent);
6315 
6316 		version(win32_widgets) {
6317 			createWin32Window(this, "Scrollbar"w, "", 
6318 				0|WS_CHILD|WS_VISIBLE|SBS_VERT|SBS_RIGHTALIGN, 0);
6319 		} else version(custom_widgets) {
6320 			auto vl = new VerticalLayout(this);
6321 			auto upButton = new ArrowButton(ArrowDirection.up, vl);
6322 			upButton.setClickRepeat(scrollClickRepeatInterval);
6323 			thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.vertical, vl);
6324 			auto downButton = new ArrowButton(ArrowDirection.down, vl);
6325 			downButton.setClickRepeat(scrollClickRepeatInterval);
6326 
6327 			upButton.addEventListener(EventType.triggered, () {
6328 				this.emitCommand!"scrolltopreviousline"();
6329 				//informProgramThatUserChangedPosition(position - step());
6330 			});
6331 			downButton.addEventListener(EventType.triggered, () {
6332 				this.emitCommand!"scrolltonextline"();
6333 				//informProgramThatUserChangedPosition(position + step());
6334 			});
6335 
6336 			thumb.thumbWidth = this.minWidth;
6337 			thumb.thumbHeight = scaleWithDpi(16);
6338 
6339 			thumb.addEventListener(EventType.change, () {
6340 				auto sy = thumb.positionY * max() / thumb.height;
6341 
6342 				auto ev = new ScrollToPositionEvent(this, sy);
6343 				ev.dispatch();
6344 
6345 				//informProgramThatUserChangedPosition(sy);
6346 			});
6347 
6348 			upButton.tabStop = false;
6349 			downButton.tabStop = false;
6350 			thumb.tabStop = false;
6351 		}
6352 	}
6353 
6354 	override int minWidth() { return scaleWithDpi(16); }
6355 	override int maxWidth() { return scaleWithDpi(16); }
6356 	override int minHeight() { return scaleWithDpi(48); }
6357 }
6358 
6359 
6360 /++
6361 	EXPERIMENTAL
6362 
6363 	A widget specialized for being a container for other widgets.
6364 
6365 	History:
6366 		Added May 29, 2021. Not stabilized at this time.
6367 +/
6368 class WidgetContainer : Widget {
6369 	this(Widget parent) {
6370 		tabStop = false;
6371 		super(parent);
6372 	}
6373 
6374 	override int maxHeight() {
6375 		if(this.children.length == 1) {
6376 			return saturatedSum(this.children[0].maxHeight, this.children[0].marginTop, this.children[0].marginBottom);
6377 		} else {
6378 			return int.max;
6379 		}
6380 	}
6381 
6382 	override int maxWidth() {
6383 		if(this.children.length == 1) {
6384 			return saturatedSum(this.children[0].maxWidth, this.children[0].marginLeft, this.children[0].marginRight);
6385 		} else {
6386 			return int.max;
6387 		}
6388 	}
6389 
6390 	/+
6391 
6392 	override int minHeight() {
6393 		int largest = 0;
6394 		int margins = 0;
6395 		int lastMargin = 0;
6396 		foreach(child; children) {
6397 			auto mh = child.minHeight();
6398 			if(mh > largest)
6399 				largest = mh;
6400 			margins += mymax(lastMargin, child.marginTop());
6401 			lastMargin = child.marginBottom();
6402 		}
6403 		return largest + margins;
6404 	}
6405 
6406 	override int maxHeight() {
6407 		int largest = 0;
6408 		int margins = 0;
6409 		int lastMargin = 0;
6410 		foreach(child; children) {
6411 			auto mh = child.maxHeight();
6412 			if(mh == int.max)
6413 				return int.max;
6414 			if(mh > largest)
6415 				largest = mh;
6416 			margins += mymax(lastMargin, child.marginTop());
6417 			lastMargin = child.marginBottom();
6418 		}
6419 		return largest + margins;
6420 	}
6421 
6422 	override int minWidth() {
6423 		int min;
6424 		foreach(child; children) {
6425 			auto cm = child.minWidth;
6426 			if(cm > min)
6427 				min = cm;
6428 		}
6429 		return min + paddingLeft + paddingRight;
6430 	}
6431 
6432 	override int minHeight() {
6433 		int min;
6434 		foreach(child; children) {
6435 			auto cm = child.minHeight;
6436 			if(cm > min)
6437 				min = cm;
6438 		}
6439 		return min + paddingTop + paddingBottom;
6440 	}
6441 
6442 	override int maxHeight() {
6443 		int largest = 0;
6444 		int margins = 0;
6445 		int lastMargin = 0;
6446 		foreach(child; children) {
6447 			auto mh = child.maxHeight();
6448 			if(mh == int.max)
6449 				return int.max;
6450 			if(mh > largest)
6451 				largest = mh;
6452 			margins += mymax(lastMargin, child.marginTop());
6453 			lastMargin = child.marginBottom();
6454 		}
6455 		return largest + margins;
6456 	}
6457 
6458 	override int heightStretchiness() {
6459 		int max;
6460 		foreach(child; children) {
6461 			auto c = child.heightStretchiness;
6462 			if(c > max)
6463 				max = c;
6464 		}
6465 		return max;
6466 	}
6467 
6468 	override int marginTop() {
6469 		if(this.children.length)
6470 			return this.children[0].marginTop;
6471 		return 0;
6472 	}
6473 	+/
6474 }
6475 
6476 ///
6477 abstract class Layout : Widget {
6478 	this(Widget parent) {
6479 		tabStop = false;
6480 		super(parent);
6481 	}
6482 }
6483 
6484 /++
6485 	Makes all children minimum width and height, placing them down
6486 	left to right, top to bottom.
6487 
6488 	Useful if you want to make a list of buttons that automatically
6489 	wrap to a new line when necessary.
6490 +/
6491 class InlineBlockLayout : Layout {
6492 	///
6493 	this(Widget parent) { super(parent); }
6494 
6495 	override void recomputeChildLayout() {
6496 		registerMovement();
6497 
6498 		int x = this.paddingLeft, y = this.paddingTop;
6499 
6500 		int lineHeight;
6501 		int previousMargin = 0;
6502 		int previousMarginBottom = 0;
6503 
6504 		foreach(child; children) {
6505 			if(child.hidden)
6506 				continue;
6507 			if(cast(FixedPosition) child) {
6508 				child.recomputeChildLayout();
6509 				continue;
6510 			}
6511 			child.width = child.flexBasisWidth();
6512 			if(child.width == 0)
6513 				child.width = child.minWidth();
6514 			if(child.width == 0)
6515 				child.width = 32;
6516 
6517 			child.height = child.flexBasisHeight();
6518 			if(child.height == 0)
6519 				child.height = child.minHeight();
6520 			if(child.height == 0)
6521 				child.height = 32;
6522 
6523 			if(x + child.width + paddingRight > this.width) {
6524 				x = this.paddingLeft;
6525 				y += lineHeight;
6526 				lineHeight = 0;
6527 				previousMargin = 0;
6528 				previousMarginBottom = 0;
6529 			}
6530 
6531 			auto margin = child.marginLeft;
6532 			if(previousMargin > margin)
6533 				margin = previousMargin;
6534 
6535 			x += margin;
6536 
6537 			child.x = x;
6538 			child.y = y;
6539 
6540 			int marginTopApplied;
6541 			if(child.marginTop > previousMarginBottom) {
6542 				child.y += child.marginTop;
6543 				marginTopApplied = child.marginTop;
6544 			}
6545 
6546 			x += child.width;
6547 			previousMargin = child.marginRight;
6548 
6549 			if(child.marginBottom > previousMarginBottom)
6550 				previousMarginBottom = child.marginBottom;
6551 
6552 			auto h = child.height + previousMarginBottom + marginTopApplied;
6553 			if(h > lineHeight)
6554 				lineHeight = h;
6555 
6556 			child.recomputeChildLayout();
6557 		}
6558 
6559 	}
6560 
6561 	override int minWidth() {
6562 		int min;
6563 		foreach(child; children) {
6564 			auto cm = child.minWidth;
6565 			if(cm > min)
6566 				min = cm;
6567 		}
6568 		return min + paddingLeft + paddingRight;
6569 	}
6570 
6571 	override int minHeight() {
6572 		int min;
6573 		foreach(child; children) {
6574 			auto cm = child.minHeight;
6575 			if(cm > min)
6576 				min = cm;
6577 		}
6578 		return min + paddingTop + paddingBottom;
6579 	}
6580 }
6581 
6582 /++
6583 	A TabMessageWidget is a clickable row of tabs followed by a content area, very similar
6584 	to the [TabWidget]. The difference is the TabMessageWidget only sends messages, whereas
6585 	the [TabWidget] will automatically change pages of child widgets.
6586 
6587 	This allows you to react to it however you see fit rather than having to
6588 	be tied to just the new sets of child widgets.
6589 
6590 	It sends the message in the form of `this.emitCommand!"changetab"();`.
6591 
6592 	History:
6593 		Added December 24, 2021 (dub v10.5)
6594 +/
6595 class TabMessageWidget : Widget {
6596 
6597 	protected void tabIndexClicked(int item) {
6598 		this.emitCommand!"changetab"();
6599 	}
6600 
6601 	/++
6602 		Adds the a new tab to the control with the given title.
6603 
6604 		Returns:
6605 			The index of the newly added tab. You will need to know
6606 			this index to refer to it later and to know which tab to
6607 			change to when you get a changetab message.
6608 	+/
6609 	int addTab(string title, int pos = int.max) {
6610 		version(win32_widgets) {
6611 			TCITEM item;
6612 			item.mask = TCIF_TEXT;
6613 			WCharzBuffer buf = WCharzBuffer(title);
6614 			item.pszText = buf.ptr;
6615 			return cast(int) SendMessage(hwnd, TCM_INSERTITEM, pos, cast(LPARAM) &item);
6616 		} else version(custom_widgets) {
6617 			if(pos >= tabs.length) {
6618 				tabs ~= title;
6619 				redraw();
6620 				return cast(int) tabs.length - 1;
6621 			} else if(pos <= 0) {
6622 				tabs = title ~ tabs;
6623 				redraw();
6624 				return 0;
6625 			} else {
6626 				tabs = tabs[0 .. pos] ~ title ~ title[pos .. $];
6627 				redraw();
6628 				return pos;
6629 			}
6630 		}
6631 	}
6632 
6633 	override void addChild(Widget child, int pos = int.max) {
6634 		if(container)
6635 			container.addChild(child, pos);
6636 		else
6637 			super.addChild(child, pos);
6638 	}
6639 
6640 	protected Widget makeContainer() {
6641 		return new Widget(this);
6642 	}
6643 
6644 	private Widget container;
6645 
6646 	override void recomputeChildLayout() {
6647 		version(win32_widgets) {
6648 			this.registerMovement();
6649 
6650 			RECT rect;
6651 			GetWindowRect(hwnd, &rect);
6652 
6653 			auto left = rect.left;
6654 			auto top = rect.top;
6655 
6656 			TabCtrl_AdjustRect(hwnd, false, &rect);
6657 			foreach(child; children) {
6658 				if(!child.showing) continue;
6659 				child.x = rect.left - left;
6660 				child.y = rect.top - top;
6661 				child.width = rect.right - rect.left;
6662 				child.height = rect.bottom - rect.top;
6663 				child.recomputeChildLayout();
6664 			}
6665 		} else version(custom_widgets) {
6666 			this.registerMovement();
6667 			foreach(child; children) {
6668 				if(!child.showing) continue;
6669 				child.x = 2;
6670 				child.y = tabBarHeight + 2; // for the border
6671 				child.width = width - 4; // for the border
6672 				child.height = height - tabBarHeight - 2 - 2; // for the border
6673 				child.recomputeChildLayout();
6674 			}
6675 		} else static assert(0);
6676 	}
6677 
6678 	version(custom_widgets)
6679 		string[] tabs;
6680 
6681 	this(Widget parent) {
6682 		super(parent);
6683 
6684 		tabStop = false;
6685 
6686 		version(win32_widgets) {
6687 			createWin32Window(this, WC_TABCONTROL, "", 0);
6688 		} else version(custom_widgets) {
6689 			addEventListener((ClickEvent event) {
6690 				if(event.target !is this && this.container !is null && event.target !is this.container) return;
6691 				if(event.clientY < tabBarHeight) {
6692 					auto t = (event.clientX / tabWidth);
6693 					if(t >= 0 && t < tabs.length) {
6694 						currentTab_ = t;
6695 						tabIndexClicked(t);
6696 						redraw();
6697 					}
6698 				}
6699 			});
6700 		} else static assert(0);
6701 
6702 		this.container = makeContainer();
6703 	}
6704 
6705 	override int marginTop() { return 4; }
6706 	override int paddingBottom() { return 4; }
6707 
6708 	override int minHeight() {
6709 		int max = 0;
6710 		foreach(child; children)
6711 			max = mymax(child.minHeight, max);
6712 
6713 
6714 		version(win32_widgets) {
6715 			RECT rect;
6716 			rect.right = this.width;
6717 			rect.bottom = max;
6718 			TabCtrl_AdjustRect(hwnd, true, &rect);
6719 
6720 			max = rect.bottom;
6721 		} else {
6722 			max += defaultLineHeight + 4;
6723 		}
6724 
6725 
6726 		return max;
6727 	}
6728 
6729 	version(win32_widgets)
6730 	override int handleWmNotify(NMHDR* hdr, int code, out int mustReturn) {
6731 		switch(code) {
6732 			case TCN_SELCHANGE:
6733 				auto sel = TabCtrl_GetCurSel(hwnd);
6734 				tabIndexClicked(sel);
6735 			break;
6736 			default:
6737 		}
6738 		return 0;
6739 	}
6740 
6741 	version(custom_widgets) {
6742 		private int currentTab_;
6743 		private int tabBarHeight() { return defaultLineHeight; }
6744 		int tabWidth = 80;
6745 	}
6746 
6747 	version(win32_widgets)
6748 	override void paint(WidgetPainter painter) {}
6749 
6750 	version(custom_widgets)
6751 	override void paint(WidgetPainter painter) {
6752 		auto cs = getComputedStyle();
6753 
6754 		draw3dFrame(0, tabBarHeight - 2, width, height - tabBarHeight + 2, painter, FrameStyle.risen, cs.background.color);
6755 
6756 		int posX = 0;
6757 		foreach(idx, title; tabs) {
6758 			auto isCurrent = idx == getCurrentTab();
6759 
6760 			painter.setClipRectangle(Point(posX, 0), tabWidth, tabBarHeight);
6761 
6762 			draw3dFrame(posX, 0, tabWidth, tabBarHeight, painter, isCurrent ? FrameStyle.risen : FrameStyle.sunk, isCurrent ? cs.windowBackgroundColor : darken(cs.windowBackgroundColor, 0.1));
6763 			painter.outlineColor = cs.foregroundColor;
6764 			painter.drawText(Point(posX + 4, 2), title, Point(posX + tabWidth, tabBarHeight - 2), TextAlignment.VerticalCenter);
6765 
6766 			if(isCurrent) {
6767 				painter.outlineColor = cs.windowBackgroundColor;
6768 				painter.fillColor = Color.transparent;
6769 				painter.drawLine(Point(posX + 2, tabBarHeight - 1), Point(posX + tabWidth, tabBarHeight - 1));
6770 				painter.drawLine(Point(posX + 2, tabBarHeight - 2), Point(posX + tabWidth, tabBarHeight - 2));
6771 
6772 				painter.outlineColor = Color.white;
6773 				painter.drawPixel(Point(posX + 1, tabBarHeight - 1));
6774 				painter.drawPixel(Point(posX + 1, tabBarHeight - 2));
6775 				painter.outlineColor = cs.activeTabColor;
6776 				painter.drawPixel(Point(posX, tabBarHeight - 1));
6777 			}
6778 
6779 			posX += tabWidth - 2;
6780 		}
6781 	}
6782 
6783 	///
6784 	@scriptable
6785 	void setCurrentTab(int item) {
6786 		version(win32_widgets)
6787 			TabCtrl_SetCurSel(hwnd, item);
6788 		else version(custom_widgets)
6789 			currentTab_ = item;
6790 		else static assert(0);
6791 
6792 		tabIndexClicked(item);
6793 	}
6794 
6795 	///
6796 	@scriptable
6797 	int getCurrentTab() {
6798 		version(win32_widgets)
6799 			return TabCtrl_GetCurSel(hwnd);
6800 		else version(custom_widgets)
6801 			return currentTab_; // FIXME
6802 		else static assert(0);
6803 	}
6804 
6805 	///
6806 	@scriptable
6807 	void removeTab(int item) {
6808 		if(item && item == getCurrentTab())
6809 			setCurrentTab(item - 1);
6810 
6811 		version(win32_widgets) {
6812 			TabCtrl_DeleteItem(hwnd, item);
6813 		}
6814 
6815 		for(int a = item; a < children.length - 1; a++)
6816 			this._children[a] = this._children[a + 1];
6817 		this._children = this._children[0 .. $-1];
6818 	}
6819 
6820 }
6821 
6822 
6823 /++
6824 	A tab widget is a set of clickable tab buttons followed by a content area.
6825 
6826 
6827 	Tabs can change existing content or can be new pages.
6828 
6829 	When the user picks a different tab, a `change` message is generated.
6830 +/
6831 class TabWidget : TabMessageWidget {
6832 	this(Widget parent) {
6833 		super(parent);
6834 	}
6835 
6836 	override protected Widget makeContainer() {
6837 		return null;
6838 	}
6839 
6840 	override void addChild(Widget child, int pos = int.max) {
6841 		if(auto twp = cast(TabWidgetPage) child) {
6842 			Widget.addChild(child, pos);
6843 			if(pos == int.max)
6844 				pos = cast(int) this.children.length - 1;
6845 
6846 			super.addTab(twp.title, pos); // need to bypass the override here which would get into a loop...
6847 
6848 			if(pos != getCurrentTab) {
6849 				child.showing = false;
6850 			}
6851 		} else {
6852 			assert(0, "Don't add children directly to a tab widget, instead add them to a page (see addPage)");
6853 		}
6854 	}
6855 
6856 	// FIXME: add tab icons at some point, Windows supports them
6857 	/++
6858 		Adds a page and its associated tab with the given label to the widget.
6859 
6860 		Returns:
6861 			The added page object, to which you can add other widgets.
6862 	+/
6863 	@scriptable
6864 	TabWidgetPage addPage(string title) {
6865 		return new TabWidgetPage(title, this);
6866 	}
6867 
6868 	/++
6869 		Gets the page at the given tab index, or `null` if the index is bad.
6870 
6871 		History:
6872 			Added December 24, 2021.
6873 	+/
6874 	TabWidgetPage getPage(int index) {
6875 		if(index < this.children.length)
6876 			return null;
6877 		return cast(TabWidgetPage) this.children[index];
6878 	}
6879 
6880 	/++
6881 		While you can still use the addTab from the parent class,
6882 		*strongly* recommend you use [addPage] insteaad.
6883 
6884 		History:
6885 			Added December 24, 2021 to fulful the interface
6886 			requirement that came from adding [TabMessageWidget].
6887 
6888 			You should not use it though since the [addPage] function
6889 			is much easier to use here.
6890 	+/
6891 	override int addTab(string title, int pos = int.max) {
6892 		auto p = addPage(title);
6893 		foreach(idx, child; this.children)
6894 			if(child is p)
6895 				return cast(int) idx;
6896 		return -1;
6897 	}
6898 
6899 	protected override void tabIndexClicked(int item) {
6900 		foreach(idx, child; children) {
6901 			child.showing(false, false); // batch the recalculates for the end
6902 		}
6903 
6904 		foreach(idx, child; children) {
6905 			if(idx == item) {
6906 				child.showing(true, false);
6907 				if(parentWindow) {
6908 					auto f = parentWindow.getFirstFocusable(child);
6909 					if(f)
6910 						f.focus();
6911 				}
6912 				recomputeChildLayout();
6913 			}
6914 		}
6915 
6916 		version(win32_widgets) {
6917 			InvalidateRect(hwnd, null, true);
6918 		} else version(custom_widgets) {
6919 			this.redraw();
6920 		}
6921 	}
6922 
6923 }
6924 
6925 /++
6926 	A page widget is basically a tab widget with hidden tabs. It is also sometimes called a "StackWidget".
6927 
6928 	You add [TabWidgetPage]s to it.
6929 +/
6930 class PageWidget : Widget {
6931 	this(Widget parent) {
6932 		super(parent);
6933 	}
6934 
6935 	override int minHeight() {
6936 		int max = 0;
6937 		foreach(child; children)
6938 			max = mymax(child.minHeight, max);
6939 
6940 		return max;
6941 	}
6942 
6943 
6944 	override void addChild(Widget child, int pos = int.max) {
6945 		if(auto twp = cast(TabWidgetPage) child) {
6946 			super.addChild(child, pos);
6947 			if(pos == int.max)
6948 				pos = cast(int) this.children.length - 1;
6949 
6950 			if(pos != getCurrentTab) {
6951 				child.showing = false;
6952 			}
6953 		} else {
6954 			assert(0, "Don't add children directly to a page widget, instead add them to a page (see addPage)");
6955 		}
6956 	}
6957 
6958 	override void recomputeChildLayout() {
6959 		this.registerMovement();
6960 		foreach(child; children) {
6961 			child.x = 0;
6962 			child.y = 0;
6963 			child.width = width;
6964 			child.height = height;
6965 			child.recomputeChildLayout();
6966 		}
6967 	}
6968 
6969 	private int currentTab_;
6970 
6971 	///
6972 	@scriptable
6973 	void setCurrentTab(int item) {
6974 		currentTab_ = item;
6975 
6976 		showOnly(item);
6977 	}
6978 
6979 	///
6980 	@scriptable
6981 	int getCurrentTab() {
6982 		return currentTab_;
6983 	}
6984 
6985 	///
6986 	@scriptable
6987 	void removeTab(int item) {
6988 		if(item && item == getCurrentTab())
6989 			setCurrentTab(item - 1);
6990 
6991 		for(int a = item; a < children.length - 1; a++)
6992 			this._children[a] = this._children[a + 1];
6993 		this._children = this._children[0 .. $-1];
6994 	}
6995 
6996 	///
6997 	@scriptable
6998 	TabWidgetPage addPage(string title) {
6999 		return new TabWidgetPage(title, this);
7000 	}
7001 
7002 	private void showOnly(int item) {
7003 		foreach(idx, child; children)
7004 			if(idx == item) {
7005 				child.show();
7006 				child.recomputeChildLayout();
7007 			} else {
7008 				child.hide();
7009 			}
7010 	}
7011 
7012 }
7013 
7014 /++
7015 
7016 +/
7017 class TabWidgetPage : Widget {
7018 	string title;
7019 	this(string title, Widget parent) {
7020 		this.title = title;
7021 		this.tabStop = false;
7022 		super(parent);
7023 
7024 		///*
7025 		version(win32_widgets) {
7026 			createWin32Window(this, Win32Class!"arsd_minigui_TabWidgetPage"w, "", 0);
7027 		}
7028 		//*/
7029 	}
7030 
7031 	override int minHeight() {
7032 		int sum = 0;
7033 		foreach(child; children)
7034 			sum += child.minHeight();
7035 		return sum;
7036 	}
7037 }
7038 
7039 version(none)
7040 /++
7041 	A collapsable sidebar is a container that shows if its assigned width is greater than its minimum and otherwise shows as a button.
7042 
7043 	I think I need to modify the layout algorithms to support this.
7044 +/
7045 class CollapsableSidebar : Widget {
7046 
7047 }
7048 
7049 /// Stacks the widgets vertically, taking all the available width for each child.
7050 class VerticalLayout : Layout {
7051 	// most of this is intentionally blank - widget's default is vertical layout right now
7052 	///
7053 	this(Widget parent) { super(parent); }
7054 
7055 	/++
7056 		Sets a max width for the layout so you don't have to subclass. The max width
7057 		is in device-independent pixels, meaning pixels at 96 dpi that are auto-scaled.
7058 
7059 		History:
7060 			Added November 29, 2021 (dub v10.5)
7061 	+/
7062 	this(int maxWidth, Widget parent) {
7063 		this.mw = maxWidth;
7064 		super(parent);
7065 	}
7066 
7067 	private int mw = int.max;
7068 
7069 	override int maxWidth() { return scaleWithDpi(mw); }
7070 }
7071 
7072 /// Stacks the widgets horizontally, taking all the available height for each child.
7073 class HorizontalLayout : Layout {
7074 	///
7075 	this(Widget parent) { super(parent); }
7076 
7077 	/++
7078 		Sets a max height for the layout so you don't have to subclass. The max height
7079 		is in device-independent pixels, meaning pixels at 96 dpi that are auto-scaled.
7080 
7081 		History:
7082 			Added November 29, 2021 (dub v10.5)
7083 	+/
7084 	this(int maxHeight, Widget parent) {
7085 		this.mh = maxHeight;
7086 		super(parent);
7087 	}
7088 
7089 	private int mh = 0;
7090 
7091 
7092 
7093 	override void recomputeChildLayout() {
7094 		.recomputeChildLayout!"width"(this);
7095 	}
7096 
7097 	override int minHeight() {
7098 		int largest = 0;
7099 		int margins = 0;
7100 		int lastMargin = 0;
7101 		foreach(child; children) {
7102 			auto mh = child.minHeight();
7103 			if(mh > largest)
7104 				largest = mh;
7105 			margins += mymax(lastMargin, child.marginTop());
7106 			lastMargin = child.marginBottom();
7107 		}
7108 		return largest + margins;
7109 	}
7110 
7111 	override int maxHeight() {
7112 		if(mh != 0)
7113 			return mymax(minHeight, scaleWithDpi(mh));
7114 
7115 		int largest = 0;
7116 		int margins = 0;
7117 		int lastMargin = 0;
7118 		foreach(child; children) {
7119 			auto mh = child.maxHeight();
7120 			if(mh == int.max)
7121 				return int.max;
7122 			if(mh > largest)
7123 				largest = mh;
7124 			margins += mymax(lastMargin, child.marginTop());
7125 			lastMargin = child.marginBottom();
7126 		}
7127 		return largest + margins;
7128 	}
7129 
7130 	override int heightStretchiness() {
7131 		int max;
7132 		foreach(child; children) {
7133 			auto c = child.heightStretchiness;
7134 			if(c > max)
7135 				max = c;
7136 		}
7137 		return max;
7138 	}
7139 
7140 }
7141 
7142 version(win32_widgets)
7143 private
7144 extern(Windows)
7145 LRESULT DoubleBufferWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) nothrow {
7146 	Widget* pwin = hwnd in Widget.nativeMapping;
7147 	if(pwin is null)
7148 		return DefWindowProc(hwnd, message, wparam, lparam);
7149 	SimpleWindow win = pwin.simpleWindowWrappingHwnd;
7150 	if(win is null)
7151 		return DefWindowProc(hwnd, message, wparam, lparam);
7152 
7153 	switch(message) {
7154 		case WM_SIZE:
7155 			auto width = LOWORD(lparam);
7156 			auto height = HIWORD(lparam);
7157 
7158 			// FIXME: could this be more efficient? it never relinquishes a large bitmap
7159 			if(width > win.bmpWidth || height > win.bmpHeight) {
7160 				auto hdc = GetDC(hwnd);
7161 				auto oldBuffer = win.buffer;
7162 				win.buffer = CreateCompatibleBitmap(hdc, width, height);
7163 
7164 				auto hdcBmp = CreateCompatibleDC(hdc);
7165 
7166 				auto oldBmp = SelectObject(hdcBmp, win.buffer);
7167 
7168 				if(oldBuffer) {
7169 					auto hdcOldBmp = CreateCompatibleDC(hdc);
7170 					auto oldOldBmp = SelectObject(hdcOldBmp, oldBuffer);
7171 
7172 					BitBlt(hdcBmp, 0, 0, win.bmpWidth, win.bmpHeight, hdcOldBmp, 0, 0, SRCCOPY);
7173 
7174 					SelectObject(hdcOldBmp, oldOldBmp);
7175 					DeleteDC(hdcOldBmp);
7176 				}
7177 
7178 				auto brush = GetSysColorBrush(COLOR_3DFACE);
7179 				RECT r;
7180 				r.left = win.bmpWidth;
7181 				r.top = 0;
7182 				r.right = width;
7183 				r.bottom = height;
7184 				FillRect(hdcBmp, &r, brush);
7185 
7186 				r.left = 0;
7187 				r.top = win.bmpHeight;
7188 				r.right = width;
7189 				r.bottom = height;
7190 				FillRect(hdcBmp, &r, brush);
7191 
7192 				SelectObject(hdcBmp, oldBmp);
7193 				DeleteDC(hdcBmp);
7194 				ReleaseDC(hwnd, hdc);
7195 
7196 				if(oldBuffer)
7197 					DeleteObject(oldBuffer);
7198 
7199 				win.bmpWidth = width;
7200 				win.bmpHeight = height;
7201 			}
7202 		break;
7203 		case WM_PAINT:
7204 			if(win.buffer is null)
7205 				goto default;
7206 
7207 			BITMAP bm;
7208 			PAINTSTRUCT ps;
7209 
7210 			HDC hdc = BeginPaint(hwnd, &ps);
7211 
7212 			HDC hdcMem = CreateCompatibleDC(hdc);
7213 			HBITMAP hbmOld = SelectObject(hdcMem, win.buffer);
7214 
7215 			GetObject(win.buffer, bm.sizeof, &bm);
7216 
7217 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
7218 
7219 			SelectObject(hdcMem, hbmOld);
7220 			DeleteDC(hdcMem);
7221 			EndPaint(hwnd, &ps);
7222 		break;
7223 		default:
7224 			return DefWindowProc(hwnd, message, wparam, lparam);
7225 	}
7226 
7227 	return 0;
7228 }
7229 
7230 private wstring Win32Class(wstring name)() {
7231 	static bool classRegistered;
7232 	if(!classRegistered) {
7233 		HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
7234 		WNDCLASSEX wc;
7235 		wc.cbSize = wc.sizeof;
7236 		wc.hInstance = hInstance;
7237 		wc.hbrBackground = cast(HBRUSH) (COLOR_3DFACE+1); // GetStockObject(WHITE_BRUSH);
7238 		wc.lpfnWndProc = &DoubleBufferWndProc;
7239 		wc.lpszClassName = name.ptr;
7240 		if(!RegisterClassExW(&wc))
7241 			throw new Exception("RegisterClass ");// ~ to!string(GetLastError()));
7242 		classRegistered = true;
7243 	}
7244 
7245 		return name;
7246 }
7247 
7248 /+
7249 version(win32_widgets)
7250 extern(Windows)
7251 private
7252 LRESULT CustomDrawWindowProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
7253 	switch(iMessage) {
7254 		case WM_PAINT:
7255 			if(auto te = hWnd in Widget.nativeMapping) {
7256 				try {
7257 					//te.redraw();
7258 					import std.stdio; writeln(te, " drawing");
7259 				} catch(Exception) {}
7260 			}
7261 			return DefWindowProc(hWnd, iMessage, wParam, lParam);
7262 		default:
7263 			return DefWindowProc(hWnd, iMessage, wParam, lParam);
7264 	}
7265 }
7266 +/
7267 
7268 
7269 /++
7270 	A widget specifically designed to hold other widgets.
7271 
7272 	History:
7273 		Added July 1, 2021
7274 +/
7275 class ContainerWidget : Widget {
7276 	this(Widget parent) {
7277 		super(parent);
7278 		this.tabStop = false;
7279 
7280 		version(win32_widgets) {
7281 			createWin32Window(this, Win32Class!"arsd_minigui_ContainerWidget"w, "", 0);
7282 		}
7283 	}
7284 }
7285 
7286 /++
7287 	A widget that takes your widget, puts scroll bars around it, and sends
7288 	messages to it when the user scrolls. Unlike [ScrollableWidget], it makes
7289 	no effort to automatically scroll or clip its child widgets - it just sends
7290 	the messages.
7291 
7292 
7293 	A ScrollMessageWidget notifies you with a [ScrollEvent] that it has changed.
7294 	The scroll coordinates are all given in a unit you interpret as you wish. One
7295 	of these units is moved on each press of the arrow buttons and represents the
7296 	smallest amount the user can scroll. The intention is for this to be one line,
7297 	one item in a list, one row in a table, etc. Whatever makes sense for your widget
7298 	in each direction that the user might be interested in.
7299 
7300 	You can set a "page size" with the [step] property. (Yes, I regret the name...)
7301 	This is the amount it jumps when the user pressed page up and page down, or clicks
7302 	in the exposed part of the scroll bar.
7303 
7304 	You should add child content to the ScrollMessageWidget. However, it is important to
7305 	note that the coordinates are always independent of the scroll position! It is YOUR
7306 	responsibility to do any necessary transforms, clipping, etc., while drawing the
7307 	content and interpreting mouse events if they are supposed to change with the scroll.
7308 	This is in contrast to the (likely to be deprecated) [ScrollableWidget], which tries
7309 	to maintain the illusion that there's an infinite space. The [ScrollMessageWidget] gives
7310 	you more control (which can be considerably more efficient and adapted to your actual data)
7311 	at the expense of you also needing to be aware of its reality.
7312 
7313 	Please note that it does NOT react to mouse wheel events or various keyboard events as of
7314 	version 10.3. Maybe this will change in the future....
7315 +/
7316 class ScrollMessageWidget : Widget {
7317 	this(Widget parent) {
7318 		super(parent);
7319 
7320 		container = new Widget(this);
7321 		hsb = new HorizontalScrollbar(this);
7322 		vsb = new VerticalScrollbar(this);
7323 
7324 		hsb.addEventListener("scrolltonextline", {
7325 			hsb.setPosition(hsb.position + 1);
7326 			notify();
7327 		});
7328 		hsb.addEventListener("scrolltopreviousline", {
7329 			hsb.setPosition(hsb.position - 1);
7330 			notify();
7331 		});
7332 		vsb.addEventListener("scrolltonextline", {
7333 			vsb.setPosition(vsb.position + 1);
7334 			notify();
7335 		});
7336 		vsb.addEventListener("scrolltopreviousline", {
7337 			vsb.setPosition(vsb.position - 1);
7338 			notify();
7339 		});
7340 		hsb.addEventListener("scrolltonextpage", {
7341 			hsb.setPosition(hsb.position + hsb.step_);
7342 			notify();
7343 		});
7344 		hsb.addEventListener("scrolltopreviouspage", {
7345 			hsb.setPosition(hsb.position - hsb.step_);
7346 			notify();
7347 		});
7348 		vsb.addEventListener("scrolltonextpage", {
7349 			vsb.setPosition(vsb.position + vsb.step_);
7350 			notify();
7351 		});
7352 		vsb.addEventListener("scrolltopreviouspage", {
7353 			vsb.setPosition(vsb.position - vsb.step_);
7354 			notify();
7355 		});
7356 		hsb.addEventListener("scrolltoposition", (Event event) {
7357 			hsb.setPosition(event.intValue);
7358 			notify();
7359 		});
7360 		vsb.addEventListener("scrolltoposition", (Event event) {
7361 			vsb.setPosition(event.intValue);
7362 			notify();
7363 		});
7364 
7365 
7366 		tabStop = false;
7367 		container.tabStop = false;
7368 		magic = true;
7369 	}
7370 
7371 	/++
7372 		Add default event listeners for keyboard and mouse wheel scrolling shortcuts.
7373 
7374 
7375 		The defaults for [addDefaultWheelListeners] are:
7376 
7377 			$(LIST
7378 				* Mouse wheel scrolls vertically
7379 				* Alt key + mouse wheel scrolls horiontally
7380 				* Shift + mouse wheel scrolls faster.
7381 				* Any mouse click or wheel event will focus the inner widget if it has `tabStop = true`
7382 			)
7383 
7384 		The defaults for [addDefaultKeyboardListeners] are:
7385 
7386 			$(LIST
7387 				* Arrow keys scroll by the given amounts
7388 				* Shift+arrow keys scroll by the given amounts times the given shiftMultiplier
7389 				* Page up and down scroll by the vertical viewable area
7390 				* Home and end scroll to the start and end of the verticle viewable area.
7391 				* Alt + page up / page down / home / end will horizonally scroll instead of vertical.
7392 			)
7393 
7394 		My recommendation is to change the scroll amounts if you are scrolling by pixels, but otherwise keep them at one line.
7395 
7396 		Params:
7397 			horizontalArrowScrollAmount =
7398 			verticalArrowScrollAmount =
7399 			verticalWheelScrollAmount = how much should be scrolled vertically on each tick of the mouse wheel
7400 			horizontalWheelScrollAmount = how much should be scrolled horizontally when alt is held on each tick of the mouse wheel
7401 			shiftMultiplier = multiplies the scroll amount by this when shift is held
7402 	+/
7403 	void addDefaultKeyboardListeners(int verticalArrowScrollAmount = 1, int horizontalArrowScrollAmount = 1, int shiftMultiplier = 3) {
7404 		auto _this = this;
7405 
7406 		container.addEventListener((scope KeyDownEvent ke) {
7407 			switch(ke.key) {
7408 				case Key.Left:
7409 					_this.scrollLeft(horizontalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7410 				break;
7411 				case Key.Right:
7412 					_this.scrollRight(horizontalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7413 				break;
7414 				case Key.Up:
7415 					_this.scrollUp(verticalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7416 				break;
7417 				case Key.Down:
7418 					_this.scrollDown(verticalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7419 				break;
7420 				case Key.PageUp:
7421 					if(ke.altKey)
7422 						_this.scrollLeft(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7423 					else
7424 						_this.scrollUp(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7425 				break;
7426 				case Key.PageDown:
7427 					if(ke.altKey)
7428 						_this.scrollRight(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7429 					else
7430 						_this.scrollDown(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7431 				break;
7432 				case Key.Home:
7433 					if(ke.altKey)
7434 						_this.scrollLeft(short.max * 16);
7435 					else
7436 						_this.scrollUp(short.max * 16);
7437 				break;
7438 				case Key.End:
7439 					if(ke.altKey)
7440 						_this.scrollRight(short.max * 16);
7441 					else
7442 						_this.scrollDown(short.max * 16);
7443 				break;
7444 
7445 				default:
7446 					// ignore, not for us.
7447 			}
7448 
7449 		});
7450 	}
7451 
7452 	/// ditto
7453 	void addDefaultWheelListeners(int verticalWheelScrollAmount = 1, int horizontalWheelScrollAmount = 1, int shiftMultiplier = 3) {
7454 		auto _this = this;
7455 		container.addEventListener((scope ClickEvent ce) {
7456 
7457 			if(ce.target && ce.target.tabStop)
7458 				ce.target.focus();
7459 
7460 			// ctrl is reserved for the application
7461 			if(ce.ctrlKey)
7462 				return;
7463 
7464 			if(horizontalWheelScrollAmount == 0 && ce.altKey)
7465 				return;
7466 
7467 			if(shiftMultiplier == 0 && ce.shiftKey)
7468 				return;
7469 
7470 			if(ce.button == MouseButton.wheelDown) {
7471 				if(ce.altKey)
7472 					_this.scrollRight(horizontalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7473 				else
7474 					_this.scrollDown(verticalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7475 			} else if(ce.button == MouseButton.wheelUp) {
7476 				if(ce.altKey)
7477 					_this.scrollLeft(horizontalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7478 				else
7479 					_this.scrollUp(verticalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7480 			}
7481 		});
7482 	}
7483 
7484 	/++
7485 		Scrolls the given amount.
7486 
7487 		History:
7488 			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.
7489 	+/
7490 	void scrollUp(int amount = 1) {
7491 		vsb.setPosition(vsb.position - amount);
7492 		notify();
7493 	}
7494 	/// ditto
7495 	void scrollDown(int amount = 1) {
7496 		vsb.setPosition(vsb.position + amount);
7497 		notify();
7498 	}
7499 	/// ditto
7500 	void scrollLeft(int amount = 1) {
7501 		hsb.setPosition(hsb.position - amount);
7502 		notify();
7503 	}
7504 	/// ditto
7505 	void scrollRight(int amount = 1) {
7506 		hsb.setPosition(hsb.position + amount);
7507 		notify();
7508 	}
7509 
7510 	///
7511 	VerticalScrollbar verticalScrollBar() { return vsb; }
7512 	///
7513 	HorizontalScrollbar horizontalScrollBar() { return hsb; }
7514 
7515 	void notify() {
7516 		static bool insideNotify;
7517 
7518 		if(insideNotify)
7519 			return; // avoid the recursive call, even if it isn't strictly correct
7520 
7521 		insideNotify = true;
7522 		scope(exit) insideNotify = false;
7523 
7524 		this.emit!ScrollEvent();
7525 	}
7526 
7527 	mixin Emits!ScrollEvent;
7528 
7529 	///
7530 	Point position() {
7531 		return Point(hsb.position, vsb.position);
7532 	}
7533 
7534 	///
7535 	void setPosition(int x, int y) {
7536 		hsb.setPosition(x);
7537 		vsb.setPosition(y);
7538 	}
7539 
7540 	///
7541 	void setPageSize(int unitsX, int unitsY) {
7542 		hsb.setStep(unitsX);
7543 		vsb.setStep(unitsY);
7544 	}
7545 
7546 	///
7547 	void setTotalArea(int width, int height) {
7548 		hsb.setMax(width);
7549 		vsb.setMax(height);
7550 	}
7551 
7552 	/// Always set the viewable area AFTER setitng the total area if you are going to change both.
7553 	/// NEVER call this from inside a scroll event. This includes through recomputeChildLayout.
7554 	/// If you need to do that, use [queueRecomputeChildLayout].
7555 	void setViewableArea(int width, int height) {
7556 
7557 		// actually there IS A need to dothis cuz the max might have changed since then
7558 		//if(width == hsb.viewableArea_ && height == vsb.viewableArea_)
7559 			//return; // no need to do what is already done
7560 		hsb.setViewableArea(width);
7561 		vsb.setViewableArea(height);
7562 
7563 		bool needsNotify = false;
7564 
7565 		// FIXME: if at any point the rhs is outside the scrollbar, we need
7566 		// to reset to 0. but it should remember the old position in case the
7567 		// window resizes again, so it can kinda return ot where it was.
7568 		//
7569 		// so there's an inner position and a exposed position. the exposed one is always in bounds and thus may be (0,0)
7570 		if(width >= hsb.max) {
7571 			// there's plenty of room to display it all so we need to reset to zero
7572 			// FIXME: adjust so it matches the note above
7573 			hsb.setPosition(0);
7574 			needsNotify = true;
7575 		}
7576 		if(height >= vsb.max) {
7577 			// there's plenty of room to display it all so we need to reset to zero
7578 			// FIXME: adjust so it matches the note above
7579 			vsb.setPosition(0);
7580 			needsNotify = true;
7581 		}
7582 		if(needsNotify)
7583 			notify();
7584 	}
7585 
7586 	private bool magic;
7587 	override void addChild(Widget w, int position = int.max) {
7588 		if(magic)
7589 			container.addChild(w, position);
7590 		else
7591 			super.addChild(w, position);
7592 	}
7593 
7594 	override void recomputeChildLayout() {
7595 		if(hsb is null || vsb is null || container is null) return;
7596 
7597 		registerMovement();
7598 
7599 		hsb.height = scaleWithDpi(16); // FIXME? are tese 16s sane?
7600 		hsb.x = 0;
7601 		hsb.y = this.height - hsb.height;
7602 		hsb.width = this.width - scaleWithDpi(16);
7603 		hsb.recomputeChildLayout();
7604 
7605 		vsb.width = scaleWithDpi(16); // FIXME?
7606 		vsb.x = this.width - vsb.width;
7607 		vsb.y = 0;
7608 		vsb.height = this.height - scaleWithDpi(16);
7609 		vsb.recomputeChildLayout();
7610 
7611 		if(this.header is null) {
7612 			container.x = 0;
7613 			container.y = 0;
7614 			container.width = this.width - vsb.width;
7615 			container.height = this.height - hsb.height;
7616 			container.recomputeChildLayout();
7617 		} else {
7618 			header.x = 0;
7619 			header.y = 0;
7620 			header.width = this.width - vsb.width;
7621 			header.height = scaleWithDpi(16); // size of the button
7622 			header.recomputeChildLayout();
7623 
7624 			container.x = 0;
7625 			container.y = scaleWithDpi(16);
7626 			container.width = this.width - vsb.width;
7627 			container.height = this.height - hsb.height - scaleWithDpi(16);
7628 			container.recomputeChildLayout();
7629 		}
7630 	}
7631 
7632 	HorizontalScrollbar hsb;
7633 	VerticalScrollbar vsb;
7634 	Widget container;
7635 	private Widget header;
7636 
7637 	/++
7638 		Adds a fixed-size "header" widget. This will be positioned to align with the scroll up button.
7639 
7640 		History:
7641 			Added September 27, 2021 (dub v10.3)
7642 	+/
7643 	Widget getHeader() {
7644 		if(this.header is null) {
7645 			magic = false;
7646 			scope(exit) magic = true;
7647 			this.header = new Widget(this);
7648 			recomputeChildLayout();
7649 		}
7650 		return this.header;
7651 	}
7652 }
7653 
7654 /++
7655 	$(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")
7656 	$(IMG //arsdnet.net/minigui-screenshots/linux/ScrollMessageWidget.png, Same thing, but in the default Linux theme.)
7657 +/
7658 version(minigui_screenshots)
7659 @Screenshot("ScrollMessageWidget")
7660 unittest {
7661 	auto window = new Window("ScrollMessageWidget");
7662 
7663 	auto smw = new ScrollMessageWidget(window);
7664 	smw.addDefaultKeyboardListeners();
7665 	smw.addDefaultWheelListeners();
7666 
7667 	window.loop();
7668 }
7669 
7670 /++
7671 	Bypasses automatic layout for its children, using manual positioning and sizing only.
7672 	While you need to manually position them, you must ensure they are inside the StaticLayout's
7673 	bounding box to avoid undefined behavior.
7674 
7675 	You should almost never use this.
7676 +/
7677 class StaticLayout : Layout {
7678 	///
7679 	this(Widget parent) { super(parent); }
7680 	override void recomputeChildLayout() {
7681 		registerMovement();
7682 		foreach(child; children)
7683 			child.recomputeChildLayout();
7684 	}
7685 }
7686 
7687 /++
7688 	Bypasses automatic positioning when being laid out. It is your responsibility to make
7689 	room for this widget in the parent layout.
7690 
7691 	Its children are laid out normally, unless there is exactly one, in which case it takes
7692 	on the full size of the `StaticPosition` object (if you plan to put stuff on the edge, you
7693 	can do that with `padding`).
7694 +/
7695 class StaticPosition : Layout {
7696 	///
7697 	this(Widget parent) { super(parent); }
7698 
7699 	override void recomputeChildLayout() {
7700 		registerMovement();
7701 		if(this.children.length == 1) {
7702 			auto child = children[0];
7703 			child.x = 0;
7704 			child.y = 0;
7705 			child.width = this.width;
7706 			child.height = this.height;
7707 			child.recomputeChildLayout();
7708 		} else
7709 		foreach(child; children)
7710 			child.recomputeChildLayout();
7711 	}
7712 
7713 	alias width = typeof(super).width;
7714 	alias height = typeof(super).height;
7715 
7716 	@property int width(int w) @nogc pure @safe nothrow {
7717 		return this._width = w;
7718 	}
7719 
7720 	@property int height(int w) @nogc pure @safe nothrow {
7721 		return this._height = w;
7722 	}
7723 
7724 }
7725 
7726 /++
7727 	FixedPosition is like [StaticPosition], but its coordinates
7728 	are always relative to the viewport, meaning they do not scroll with
7729 	the parent content.
7730 +/
7731 class FixedPosition : StaticPosition {
7732 	///
7733 	this(Widget parent) { super(parent); }
7734 }
7735 
7736 version(win32_widgets)
7737 int processWmCommand(HWND parentWindow, HWND handle, ushort cmd, ushort idm) {
7738 	if(true) {
7739 		// cmd == 0 = menu, cmd == 1 = accelerator
7740 		if(auto item = idm in Action.mapping) {
7741 			foreach(handler; (*item).triggered)
7742 				handler();
7743 		/*
7744 			auto event = new Event("triggered", *item);
7745 			event.button = idm;
7746 			event.dispatch();
7747 		*/
7748 			return 0;
7749 		}
7750 	}
7751 	if(handle)
7752 	if(auto widgetp = handle in Widget.nativeMapping) {
7753 		(*widgetp).handleWmCommand(cmd, idm);
7754 		return 0;
7755 	}
7756 	return 1;
7757 }
7758 
7759 
7760 ///
7761 class Window : Widget {
7762 	int mouseCaptureCount = 0;
7763 	Widget mouseCapturedBy;
7764 	void captureMouse(Widget byWhom) {
7765 		assert(mouseCapturedBy is null || byWhom is mouseCapturedBy);
7766 		mouseCaptureCount++;
7767 		mouseCapturedBy = byWhom;
7768 		win.grabInput();
7769 	}
7770 	void releaseMouseCapture() {
7771 		mouseCaptureCount--;
7772 		mouseCapturedBy = null;
7773 		win.releaseInputGrab();
7774 	}
7775 
7776 	/++
7777 		Sets the window icon which is often seen in title bars and taskbars.
7778 
7779 		History:
7780 			Added April 5, 2022 (dub v10.8)
7781 	+/
7782 	@property void icon(MemoryImage icon) {
7783 		if(win && icon)
7784 			win.icon = icon;
7785 	}
7786 
7787 	///
7788 	@scriptable
7789 	@property bool focused() {
7790 		return win.focused;
7791 	}
7792 
7793 	static class Style : Widget.Style {
7794 		override WidgetBackground background() {
7795 			version(custom_widgets)
7796 				return WidgetBackground(WidgetPainter.visualTheme.windowBackgroundColor);
7797 			else version(win32_widgets)
7798 				return WidgetBackground(Color.transparent);
7799 			else static assert(0);
7800 		}
7801 	}
7802 	mixin OverrideStyle!Style;
7803 
7804 	/++
7805 		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.
7806 	+/
7807 	static int lineHeight() {
7808 		OperatingSystemFont font;
7809 		if(auto vt = WidgetPainter.visualTheme) {
7810 			font = vt.defaultFontCached();
7811 		}
7812 
7813 		if(font is null) {
7814 			static int defaultHeightCache;
7815 			if(defaultHeightCache == 0) {
7816 				font = new OperatingSystemFont;
7817 				font.loadDefault;
7818 				defaultHeightCache = font.height() * 5 / 4;
7819 			}
7820 			return defaultHeightCache;
7821 		}
7822 
7823 		return font.height() * 5 / 4;
7824 	}
7825 
7826 	Widget focusedWidget;
7827 
7828 	private SimpleWindow win_;
7829 
7830 	@property {
7831 		/++
7832 			Provides access to the underlying [SimpleWindow]. Note that changing properties on this window may disconnect minigui's event dispatchers.
7833 
7834 			History:
7835 				Prior to June 21, 2021, it was a public (but undocumented) member. Now it a semi-protected property.
7836 		+/
7837 		public SimpleWindow win() {
7838 			return win_;
7839 		}
7840 		///
7841 		protected void win(SimpleWindow w) {
7842 			win_ = w;
7843 		}
7844 	}
7845 
7846 	/// YOU ALMOST CERTAINLY SHOULD NOT USE THIS. This is really only for special purposes like pseudowindows or popup windows doing their own thing.
7847 	this(Widget p) {
7848 		tabStop = false;
7849 		super(p);
7850 	}
7851 
7852 	private void actualRedraw() {
7853 		if(recomputeChildLayoutRequired)
7854 			recomputeChildLayoutEntry();
7855 		if(!showing) return;
7856 
7857 		assert(parentWindow !is null);
7858 
7859 		auto w = drawableWindow;
7860 		if(w is null)
7861 			w = parentWindow.win;
7862 
7863 		if(w.closed())
7864 			return;
7865 
7866 		auto ugh = this.parent;
7867 		int lox, loy;
7868 		while(ugh) {
7869 			lox += ugh.x;
7870 			loy += ugh.y;
7871 			ugh = ugh.parent;
7872 		}
7873 		auto painter = w.draw(true);
7874 		privatePaint(WidgetPainter(painter, this), lox, loy, Rectangle(0, 0, int.max, int.max), false, willDraw());
7875 		// RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
7876 	}
7877 
7878 
7879 	private bool skipNextChar = false;
7880 
7881 	/++
7882 		Creates a window from an existing [SimpleWindow]. This constructor attaches various event handlers to the SimpleWindow object which may overwrite your existing handlers.
7883 
7884 		This constructor is intended primarily for internal use and may be changed to `protected` later.
7885 	+/
7886 	this(SimpleWindow win) {
7887 
7888 		static if(UsingSimpledisplayX11) {
7889 			win.discardAdditionalConnectionState = &discardXConnectionState;
7890 			win.recreateAdditionalConnectionState = &recreateXConnectionState;
7891 		}
7892 
7893 		tabStop = false;
7894 		super(null);
7895 		this.win = win;
7896 
7897 		win.addEventListener((Widget.RedrawEvent) {
7898 			if(win.eventQueued!RecomputeEvent) {
7899 				// import std.stdio; writeln("skipping");
7900 				return; // let the recompute event do the actual redraw
7901 			}
7902 			this.actualRedraw();
7903 		});
7904 
7905 		win.addEventListener((Widget.RecomputeEvent) {
7906 			recomputeChildLayoutEntry();
7907 			if(win.eventQueued!RedrawEvent)
7908 				return; // let the queued one do it
7909 			else {
7910 				// import std.stdio; writeln("drawing");
7911 				this.actualRedraw(); // if not queued, it needs to be done now anyway
7912 			}
7913 		});
7914 
7915 		this.width = win.width;
7916 		this.height = win.height;
7917 		this.parentWindow = this;
7918 
7919 		win.closeQuery = () {
7920 			if(this.emit!ClosingEvent())
7921 				win.close();
7922 		};
7923 		win.onClosing = () {
7924 			this.emit!ClosedEvent();
7925 		};
7926 
7927 		win.windowResized = (int w, int h) {
7928 			this.width = w;
7929 			this.height = h;
7930 			recomputeChildLayout();
7931 			// this causes a HUGE performance problem for no apparent benefit, hence the commenting
7932 			//version(win32_widgets)
7933 				//InvalidateRect(hwnd, null, true);
7934 			redraw();
7935 		};
7936 
7937 		win.onFocusChange = (bool getting) {
7938 			if(this.focusedWidget) {
7939 				if(getting) {
7940 					this.focusedWidget.emit!FocusEvent();
7941 					this.focusedWidget.emit!FocusInEvent();
7942 				} else {
7943 					this.focusedWidget.emit!BlurEvent();
7944 					this.focusedWidget.emit!FocusOutEvent();
7945 				}
7946 			}
7947 
7948 			if(getting) {
7949 				this.emit!FocusEvent();
7950 				this.emit!FocusInEvent();
7951 			} else {
7952 				this.emit!BlurEvent();
7953 				this.emit!FocusOutEvent();
7954 			}
7955 		};
7956 
7957 		win.onDpiChanged = {
7958 			this.queueRecomputeChildLayout();
7959 			auto event = new DpiChangedEvent(this);
7960 			event.sendDirectly();
7961 
7962 			privateDpiChanged();
7963 		};
7964 
7965 		win.setEventHandlers(
7966 			(MouseEvent e) {
7967 				dispatchMouseEvent(e);
7968 			},
7969 			(KeyEvent e) {
7970 				//import std.stdio;
7971 				//writefln("%x   %s", cast(uint) e.key, e.key);
7972 				dispatchKeyEvent(e);
7973 			},
7974 			(dchar e) {
7975 				if(e == 13) e = 10; // hack?
7976 				if(e == 127) return; // linux sends this, windows doesn't. we don't want it.
7977 				dispatchCharEvent(e);
7978 			},
7979 		);
7980 
7981 		addEventListener("char", (Widget, Event ev) {
7982 			if(skipNextChar) {
7983 				ev.preventDefault();
7984 				skipNextChar = false;
7985 			}
7986 		});
7987 
7988 		version(win32_widgets)
7989 		win.handleNativeEvent = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
7990 			if(hwnd !is this.win.impl.hwnd)
7991 				return 1; // we don't care... pass it on
7992 			auto ret = WindowProcedureHelper(this, hwnd, msg, wParam, lParam, mustReturn);
7993 			if(mustReturn)
7994 				return ret;
7995 			return 1; // pass it on
7996 		};
7997 
7998 		if(Window.newWindowCreated)
7999 			Window.newWindowCreated(this);
8000 	}
8001 
8002 	version(custom_widgets)
8003 	override void defaultEventHandler_click(ClickEvent event) {
8004 		if(event.target && event.target.tabStop)
8005 			event.target.focus();
8006 	}
8007 
8008 	private static void delegate(Window) newWindowCreated;
8009 
8010 	version(win32_widgets)
8011 	override void paint(WidgetPainter painter) {
8012 		/*
8013 		RECT rect;
8014 		rect.right = this.width;
8015 		rect.bottom = this.height;
8016 		DrawThemeBackground(theme, painter.impl.hdc, 4, 1, &rect, null);
8017 		*/
8018 		// 3dface is used as window backgrounds by Windows too, so that's why I'm using it here
8019 		auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
8020 		auto p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
8021 		// since the pen is null, to fill the whole space, we need the +1 on both.
8022 		gdi.Rectangle(painter.impl.hdc, 0, 0, this.width + 1, this.height + 1);
8023 		SelectObject(painter.impl.hdc, p);
8024 		SelectObject(painter.impl.hdc, b);
8025 	}
8026 	version(custom_widgets)
8027 	override void paint(WidgetPainter painter) {
8028 		auto cs = getComputedStyle();
8029 		painter.fillColor = cs.windowBackgroundColor;
8030 		painter.outlineColor = cs.windowBackgroundColor;
8031 		painter.drawRectangle(Point(0, 0), this.width, this.height);
8032 	}
8033 
8034 
8035 	override void defaultEventHandler_keydown(KeyDownEvent event) {
8036 		Widget _this = event.target;
8037 
8038 		if(event.key == Key.Tab) {
8039 			/* Window tab ordering is a recursive thingy with each group */
8040 
8041 			// FIXME inefficient
8042 			Widget[] helper(Widget p) {
8043 				if(p.hidden)
8044 					return null;
8045 				Widget[] childOrdering;
8046 
8047 				auto children = p.children.dup;
8048 
8049 				while(true) {
8050 					// UIs should be generally small, so gonna brute force it a little
8051 					// note that it must be a stable sort here; if all are index 0, it should be in order of declaration
8052 
8053 					Widget smallestTab;
8054 					foreach(ref c; children) {
8055 						if(c is null) continue;
8056 						if(smallestTab is null || c.tabOrder < smallestTab.tabOrder) {
8057 							smallestTab = c;
8058 							c = null;
8059 						}
8060 					}
8061 					if(smallestTab !is null) {
8062 						if(smallestTab.tabStop && !smallestTab.hidden)
8063 							childOrdering ~= smallestTab;
8064 						if(!smallestTab.hidden)
8065 							childOrdering ~= helper(smallestTab);
8066 					} else
8067 						break;
8068 
8069 				}
8070 
8071 				return childOrdering;
8072 			}
8073 
8074 			Widget[] tabOrdering = helper(this);
8075 
8076 			Widget recipient;
8077 
8078 			if(tabOrdering.length) {
8079 				bool seenThis = false;
8080 				Widget previous;
8081 				foreach(idx, child; tabOrdering) {
8082 					if(child is focusedWidget) {
8083 
8084 						if(event.shiftKey) {
8085 							if(idx == 0)
8086 								recipient = tabOrdering[$-1];
8087 							else
8088 								recipient = tabOrdering[idx - 1];
8089 							break;
8090 						}
8091 
8092 						seenThis = true;
8093 						if(idx + 1 == tabOrdering.length) {
8094 							// we're at the end, either move to the next group
8095 							// or start back over
8096 							recipient = tabOrdering[0];
8097 						}
8098 						continue;
8099 					}
8100 					if(seenThis) {
8101 						recipient = child;
8102 						break;
8103 					}
8104 					previous = child;
8105 				}
8106 			}
8107 
8108 			if(recipient !is null) {
8109 				// import std.stdio; writeln(typeid(recipient));
8110 				recipient.focus();
8111 
8112 				skipNextChar = true;
8113 			}
8114 		}
8115 
8116 		debug if(event.key == Key.F12) {
8117 			if(devTools) {
8118 				devTools.close();
8119 				devTools = null;
8120 			} else {
8121 				devTools = new DevToolWindow(this);
8122 				devTools.show();
8123 			}
8124 		}
8125 	}
8126 
8127 	debug DevToolWindow devTools;
8128 
8129 
8130 	/++
8131 		Creates a window. Please note windows are created in a hidden state, so you must call [show] or [loop] to get it to display.
8132 
8133 		History:
8134 			Prior to May 12, 2021, the default title was "D Application" (simpledisplay.d's default). After that, the default is `Runtime.args[0]` instead.
8135 
8136 			The width and height arguments were added to the overload that takes `string` first on June 21, 2021.
8137 	+/
8138 	this(int width = 500, int height = 500, string title = null) {
8139 		if(title is null) {
8140 			import core.runtime;
8141 			if(Runtime.args.length)
8142 				title = Runtime.args[0];
8143 		}
8144 		win = new SimpleWindow(width, height, title, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.normal, WindowFlags.dontAutoShow | WindowFlags.managesChildWindowFocus);
8145 
8146 		static if(UsingSimpledisplayX11) {
8147 		///+
8148 		// for input proxy
8149 		auto display = XDisplayConnection.get;
8150 		auto inputProxy = XCreateSimpleWindow(display, win.window, -1, -1, 1, 1, 0, 0, 0);
8151 		XSelectInput(display, inputProxy, EventMask.KeyPressMask | EventMask.KeyReleaseMask | EventMask.FocusChangeMask);
8152 		XMapWindow(display, inputProxy);
8153 		//import std.stdio; writefln("input proxy: 0x%0x", inputProxy);
8154 		this.inputProxy = new SimpleWindow(inputProxy);
8155 
8156 		XEvent lastEvent;
8157 		this.inputProxy.handleNativeEvent = (XEvent ev) {
8158 			lastEvent = ev;
8159 			return 1;
8160 		};
8161 		this.inputProxy.setEventHandlers(
8162 			(MouseEvent e) {
8163 				dispatchMouseEvent(e);
8164 			},
8165 			(KeyEvent e) {
8166 				//import std.stdio;
8167 				//writefln("%x   %s", cast(uint) e.key, e.key);
8168 				if(dispatchKeyEvent(e)) {
8169 					// FIXME: i should trap error
8170 					if(auto nw = cast(NestedChildWindowWidget) focusedWidget) {
8171 						auto thing = nw.focusableWindow();
8172 						if(thing && thing.window) {
8173 							lastEvent.xkey.window = thing.window;
8174 							// import std.stdio; writeln("sending event ", lastEvent.xkey);
8175 							trapXErrors( {
8176 								XSendEvent(XDisplayConnection.get, thing.window, false, 0, &lastEvent);
8177 							});
8178 						}
8179 					}
8180 				}
8181 			},
8182 			(dchar e) {
8183 				if(e == 13) e = 10; // hack?
8184 				if(e == 127) return; // linux sends this, windows doesn't. we don't want it.
8185 				dispatchCharEvent(e);
8186 			},
8187 		);
8188 
8189 		this.inputProxy.populateXic();
8190 		// done
8191 		//+/
8192 		}
8193 
8194 
8195 
8196 		win.setRequestedInputFocus = &this.setRequestedInputFocus;
8197 
8198 		this(win);
8199 	}
8200 
8201 	SimpleWindow inputProxy;
8202 
8203 	private SimpleWindow setRequestedInputFocus() {
8204 		return inputProxy;
8205 	}
8206 
8207 	/// ditto
8208 	this(string title, int width = 500, int height = 500) {
8209 		this(width, height, title);
8210 	}
8211 
8212 	///
8213 	@property string title() { return parentWindow.win.title; }
8214 	///
8215 	@property void title(string title) { parentWindow.win.title = title; }
8216 
8217 	///
8218 	@scriptable
8219 	void close() {
8220 		win.close();
8221 		// I synchronize here upon window closing to ensure all child windows
8222 		// get updated too before the event loop. This avoids some random X errors.
8223 		static if(UsingSimpledisplayX11) {
8224 			runInGuiThread( {
8225 				XSync(XDisplayConnection.get, false);
8226 			});
8227 		}
8228 	}
8229 
8230 	bool dispatchKeyEvent(KeyEvent ev) {
8231 		auto wid = focusedWidget;
8232 		if(wid is null)
8233 			wid = this;
8234 		KeyEventBase event = ev.pressed ? new KeyDownEvent(wid) : new KeyUpEvent(wid);
8235 		event.originalKeyEvent = ev;
8236 		event.key = ev.key;
8237 		event.state = ev.modifierState;
8238 		event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false;
8239 		event.altKey = (ev.modifierState & ModifierState.alt) ? true : false;
8240 		event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false;
8241 		event.dispatch();
8242 
8243 		return !event.propagationStopped;
8244 	}
8245 
8246 	// returns true if propagation should continue into nested things.... prolly not a great thing to do.
8247 	bool dispatchCharEvent(dchar ch) {
8248 		if(focusedWidget) {
8249 			auto event = new CharEvent(focusedWidget, ch);
8250 			event.dispatch();
8251 			return !event.propagationStopped;
8252 		}
8253 		return true;
8254 	}
8255 
8256 	Widget mouseLastOver;
8257 	Widget mouseLastDownOn;
8258 	bool lastWasDoubleClick;
8259 	bool dispatchMouseEvent(MouseEvent ev) {
8260 		auto eleR = widgetAtPoint(this, ev.x, ev.y);
8261 		auto ele = eleR.widget;
8262 
8263 		auto captureEle = ele;
8264 
8265 		if(mouseCapturedBy !is null) {
8266 			if(ele !is mouseCapturedBy && !mouseCapturedBy.isAParentOf(ele))
8267 				captureEle = mouseCapturedBy;
8268 		}
8269 
8270 		// a hack to get it relative to the widget.
8271 		eleR.x = ev.x;
8272 		eleR.y = ev.y;
8273 		auto pain = captureEle;
8274 		while(pain) {
8275 			eleR.x -= pain.x;
8276 			eleR.y -= pain.y;
8277 			pain.addScrollPosition(eleR.x, eleR.y);
8278 			pain = pain.parent;
8279 		}
8280 
8281 		void populateMouseEventBase(MouseEventBase event) {
8282 			event.button = ev.button;
8283 			event.buttonLinear = ev.buttonLinear;
8284 			event.state = ev.modifierState;
8285 			event.clientX = eleR.x;
8286 			event.clientY = eleR.y;
8287 
8288 			event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false;
8289 			event.altKey = (ev.modifierState & ModifierState.alt) ? true : false;
8290 			event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false;
8291 		}
8292 
8293 		if(ev.type == MouseEventType.buttonPressed) {
8294 			{
8295 				auto event = new MouseDownEvent(captureEle);
8296 				populateMouseEventBase(event);
8297 				event.dispatch();
8298 			}
8299 
8300 			if(ev.button != MouseButton.wheelDown && ev.button != MouseButton.wheelUp && mouseLastDownOn is ele && ev.doubleClick) {
8301 				auto event = new DoubleClickEvent(captureEle);
8302 				populateMouseEventBase(event);
8303 				event.dispatch();
8304 				lastWasDoubleClick = ev.doubleClick;
8305 			} else {
8306 				lastWasDoubleClick = false;
8307 			}
8308 
8309 			mouseLastDownOn = ele;
8310 		} else if(ev.type == MouseEventType.buttonReleased) {
8311 			{
8312 				auto event = new MouseUpEvent(captureEle);
8313 				populateMouseEventBase(event);
8314 				event.dispatch();
8315 			}
8316 			if(!lastWasDoubleClick && mouseLastDownOn is ele) {
8317 				auto event = new ClickEvent(captureEle);
8318 				populateMouseEventBase(event);
8319 				event.dispatch();
8320 			}
8321 		} else if(ev.type == MouseEventType.motion) {
8322 			// motion
8323 			{
8324 				auto event = new MouseMoveEvent(captureEle);
8325 				populateMouseEventBase(event); // fills in button which is meaningless but meh
8326 				event.dispatch();
8327 			}
8328 
8329 			if(mouseLastOver !is ele) {
8330 				if(ele !is null) {
8331 					if(!isAParentOf(ele, mouseLastOver)) {
8332 						ele.setDynamicState(DynamicState.hover, true);
8333 						auto event = new MouseEnterEvent(ele);
8334 						event.relatedTarget = mouseLastOver;
8335 						event.sendDirectly();
8336 
8337 						ele.useStyleProperties((scope Widget.Style s) {
8338 							ele.parentWindow.win.cursor = s.cursor;
8339 						});
8340 					}
8341 				}
8342 
8343 				if(mouseLastOver !is null) {
8344 					if(!isAParentOf(mouseLastOver, ele)) {
8345 						mouseLastOver.setDynamicState(DynamicState.hover, false);
8346 						auto event = new MouseLeaveEvent(mouseLastOver);
8347 						event.relatedTarget = ele;
8348 						event.sendDirectly();
8349 					}
8350 				}
8351 
8352 				if(ele !is null) {
8353 					auto event = new MouseOverEvent(ele);
8354 					event.relatedTarget = mouseLastOver;
8355 					event.dispatch();
8356 				}
8357 
8358 				if(mouseLastOver !is null) {
8359 					auto event = new MouseOutEvent(mouseLastOver);
8360 					event.relatedTarget = ele;
8361 					event.dispatch();
8362 				}
8363 
8364 				mouseLastOver = ele;
8365 			}
8366 		}
8367 
8368 		return true; // FIXME: the event default prevented?
8369 	}
8370 
8371 	/++
8372 		Shows the window and runs the application event loop.
8373 
8374 		Blocks until this window is closed.
8375 
8376 		History:
8377 			The [BlockingMode] parameter was added on December 8, 2021.
8378 			The default behavior is to block until the application quits
8379 			(so all windows have been closed), unless another minigui or
8380 			simpledisplay event loop is already running, in which case it
8381 			will block until this window closes specifically.
8382 	+/
8383 	@scriptable
8384 	void loop(BlockingMode bm = BlockingMode.automatic) {
8385 		if(win.closed)
8386 			return; // otherwise show will throw
8387 		show();
8388 		win.eventLoopWithBlockingMode(bm, 0);
8389 	}
8390 
8391 	private bool firstShow = true;
8392 
8393 	@scriptable
8394 	override void show() {
8395 		bool rd = false;
8396 		if(firstShow) {
8397 			firstShow = false;
8398 			recomputeChildLayout();
8399 			auto f = getFirstFocusable(this); // FIXME: autofocus?
8400 			if(f)
8401 				f.focus();
8402 			redraw();
8403 		}
8404 		win.show();
8405 		super.show();
8406 	}
8407 	@scriptable
8408 	override void hide() {
8409 		win.hide();
8410 		super.hide();
8411 	}
8412 
8413 	static Widget getFirstFocusable(Widget start) {
8414 		if(start is null)
8415 			return null;
8416 
8417 		foreach(widget; &start.focusableWidgets) {
8418 			return widget;
8419 		}
8420 
8421 		return null;
8422 	}
8423 
8424 	static Widget getLastFocusable(Widget start) {
8425 		if(start is null)
8426 			return null;
8427 
8428 		Widget last;
8429 		foreach(widget; &start.focusableWidgets) {
8430 			last = widget;
8431 		}
8432 
8433 		return last;
8434 	}
8435 
8436 
8437 	mixin Emits!ClosingEvent;
8438 	mixin Emits!ClosedEvent;
8439 }
8440 
8441 /++
8442 	History:
8443 		Added January 12, 2022
8444 +/
8445 class DpiChangedEvent : Event {
8446 	enum EventString = "dpichanged";
8447 
8448 	this(Widget target) {
8449 		super(EventString, target);
8450 	}
8451 }
8452 
8453 debug private class DevToolWindow : Window {
8454 	Window p;
8455 
8456 	TextEdit parentList;
8457 	TextEdit logWindow;
8458 	TextLabel clickX, clickY;
8459 
8460 	this(Window p) {
8461 		this.p = p;
8462 		super(400, 300, "Developer Toolbox");
8463 
8464 		logWindow = new TextEdit(this);
8465 		parentList = new TextEdit(this);
8466 
8467 		auto hl = new HorizontalLayout(this);
8468 		clickX = new TextLabel("", TextAlignment.Right, hl);
8469 		clickY = new TextLabel("", TextAlignment.Right, hl);
8470 
8471 		parentListeners ~= p.addEventListener("*", (Event ev) {
8472 			log(typeid(ev.source).name, " emitted ", typeid(ev).name);
8473 		});
8474 
8475 		parentListeners ~= p.addEventListener((ClickEvent ev) {
8476 			auto s = ev.srcElement;
8477 			string list = s.toString();
8478 			s = s.parent;
8479 			while(s) {
8480 				list ~= "\n";
8481 				list ~= s.toString();
8482 				s = s.parent;
8483 			}
8484 			parentList.content = list;
8485 
8486 			clickX.label = toInternal!string(ev.clientX);
8487 			clickY.label = toInternal!string(ev.clientY);
8488 		});
8489 	}
8490 
8491 	EventListener[] parentListeners;
8492 
8493 	override void close() {
8494 		assert(p !is null);
8495 		foreach(p; parentListeners)
8496 			p.disconnect();
8497 		parentListeners = null;
8498 		p.devTools = null;
8499 		p = null;
8500 		super.close();
8501 	}
8502 
8503 	override void defaultEventHandler_keydown(KeyDownEvent ev) {
8504 		if(ev.key == Key.F12) {
8505 			this.close();
8506 			if(p)
8507 				p.devTools = null;
8508 		} else {
8509 			super.defaultEventHandler_keydown(ev);
8510 		}
8511 	}
8512 
8513 	void log(T...)(T t) {
8514 		string str;
8515 		import std.conv;
8516 		foreach(i; t)
8517 			str ~= to!string(i);
8518 		str ~= "\n";
8519 		logWindow.addText(str);
8520 
8521 		version(custom_widgets)
8522 		logWindow.ensureVisibleInScroll(logWindow.textLayout.caretBoundingBox());
8523 	}
8524 }
8525 
8526 /++
8527 	A dialog is a transient window that intends to get information from
8528 	the user before being dismissed.
8529 +/
8530 abstract class Dialog : Window {
8531 	///
8532 	this(int width, int height, string title = null) {
8533 		super(width, height, title);
8534 	}
8535 
8536 	///
8537 	abstract void OK();
8538 
8539 	///
8540 	void Cancel() {
8541 		this.close();
8542 	}
8543 }
8544 
8545 /++
8546 	A custom widget similar to the HTML5 <details> tag.
8547 +/
8548 version(none)
8549 class DetailsView : Widget {
8550 
8551 }
8552 
8553 // FIXME: maybe i should expose the other list views Windows offers too
8554 
8555 /++
8556 	A TableView is a widget made to display a table of data strings.
8557 
8558 
8559 	Future_Directions:
8560 		Each item should be able to take an icon too and maybe I'll allow more of the view modes Windows offers.
8561 
8562 		I will add a selection changed event at some point, as well as item clicked events.
8563 	History:
8564 		Added September 24, 2021. Initial api stabilized in dub v10.4, but it isn't completely feature complete yet.
8565 	See_Also:
8566 		[ListWidget] which displays a list of strings without additional columns.
8567 +/
8568 class TableView : Widget {
8569 	/++
8570 
8571 	+/
8572 	this(Widget parent) {
8573 		super(parent);
8574 
8575 		version(win32_widgets) {
8576 			createWin32Window(this, WC_LISTVIEW, "", LVS_REPORT | LVS_OWNERDATA);//| LVS_OWNERDRAWFIXED);
8577 		} else version(custom_widgets) {
8578 			auto smw = new ScrollMessageWidget(this);
8579 			smw.addDefaultKeyboardListeners();
8580 			smw.addDefaultWheelListeners(1, scaleWithDpi(16));
8581 			tvwi = new TableViewWidgetInner(this, smw);
8582 		}
8583 	}
8584 
8585 	// FIXME: auto-size columns on double click of header thing like in Windows
8586 	// it need only make the currently displayed things fit well.
8587 
8588 
8589 	private ColumnInfo[] columns;
8590 	private int itemCount;
8591 
8592 	version(custom_widgets) private {
8593 		TableViewWidgetInner tvwi;
8594 	}
8595 
8596 	/// Passed to [setColumnInfo]
8597 	static struct ColumnInfo {
8598 		const(char)[] name; /// the name displayed in the header
8599 		/++
8600 			The default width, in pixels. As a special case, you can set this to -1
8601 			if you want the system to try to automatically size the width to fit visible
8602 			content. If it can't, it will try to pick a sensible default size.
8603 
8604 			Any other negative value is not allowed and may lead to unpredictable results.
8605 
8606 			History:
8607 				The -1 behavior was specified on December 3, 2021. It actually worked before
8608 				anyway on Win32 but now it is a formal feature with partial Linux support.
8609 
8610 			Bugs:
8611 				It doesn't actually attempt to calculate a best-fit width on Linux as of
8612 				December 3, 2021. I do plan to fix this in the future, but Windows is the
8613 				priority right now. At least it doesn't break things when you use it now.
8614 		+/
8615 		int width;
8616 
8617 		/++
8618 			Alignment of the text in the cell. Applies to the header as well as all data in this
8619 			column.
8620 
8621 			Bugs:
8622 				On Windows, the first column ignores this member and is always left aligned.
8623 				You can work around this by inserting a dummy first column with width = 0
8624 				then putting your actual data in the second column, which does respect the
8625 				alignment.
8626 
8627 				This is a quirk of the operating system's implementation going back a very
8628 				long time and is unlikely to ever be fixed.
8629 		+/
8630 		TextAlignment alignment;
8631 
8632 		/++
8633 			After all the pixel widths have been assigned, any left over
8634 			space is divided up among all columns and distributed to according
8635 			to the widthPercent field.
8636 
8637 
8638 			For example, if you have two fields, both with width 50 and one with
8639 			widthPercent of 25 and the other with widthPercent of 75, and the
8640 			container is 200 pixels wide, first both get their width of 50.
8641 			then the 100 remaining pixels are split up, so the one gets a total
8642 			of 75 pixels and the other gets a total of 125.
8643 
8644 			This is automatically applied as the window is resized.
8645 
8646 			If there is not enough space - that is, when a horizontal scrollbar
8647 			needs to appear - there are 0 pixels divided up, and thus everyone
8648 			gets 0. This can cause a column to shrink out of proportion when
8649 			passing the scroll threshold.
8650 
8651 			It is important to still set a fixed width (that is, to populate the
8652 			`width` field) even if you use the percents because that will be the
8653 			default minimum in the event of a scroll bar appearing.
8654 
8655 			The percents total in the column can never exceed 100 or be less than 0.
8656 			Doing this will trigger an assert error.
8657 
8658 			Implementation note:
8659 
8660 			Please note that percentages are only recalculated 1) upon original
8661 			construction and 2) upon resizing the control. If the user adjusts the
8662 			width of a column, the percentage items will not be updated.
8663 
8664 			On the other hand, if the user adjusts the width of a percentage column
8665 			then resizes the window, it is recalculated, meaning their hand adjustment
8666 			is discarded. This specific behavior may change in the future as it is
8667 			arguably a bug, but I'm not certain yet.
8668 
8669 			History:
8670 				Added November 10, 2021 (dub v10.4)
8671 		+/
8672 		int widthPercent;
8673 
8674 
8675 		private int calculatedWidth;
8676 	}
8677 	/++
8678 		Sets the number of columns along with information about the headers.
8679 
8680 		Please note: on Windows, the first column ignores your alignment preference
8681 		and is always left aligned.
8682 	+/
8683 	void setColumnInfo(ColumnInfo[] columns...) {
8684 
8685 		foreach(ref c; columns) {
8686 			c.name = c.name.idup;
8687 		}
8688 		this.columns = columns.dup;
8689 
8690 		updateCalculatedWidth(false);
8691 
8692 		version(custom_widgets) {
8693 			tvwi.header.updateHeaders();
8694 			tvwi.updateScrolls();
8695 		} else version(win32_widgets)
8696 		foreach(i, column; this.columns) {
8697 			LVCOLUMN lvColumn;
8698 			lvColumn.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
8699 			lvColumn.cx = column.width == -1 ? -1 : column.calculatedWidth;
8700 
8701 			auto bfr = WCharzBuffer(column.name);
8702 			lvColumn.pszText = bfr.ptr;
8703 
8704 			if(column.alignment & TextAlignment.Center)
8705 				lvColumn.fmt = LVCFMT_CENTER;
8706 			else if(column.alignment & TextAlignment.Right)
8707 				lvColumn.fmt = LVCFMT_RIGHT;
8708 			else
8709 				lvColumn.fmt = LVCFMT_LEFT;
8710 
8711 			if(SendMessage(hwnd, LVM_INSERTCOLUMN, cast(WPARAM) i, cast(LPARAM) &lvColumn) == -1)
8712 				throw new WindowsApiException("Insert Column Fail");
8713 		}
8714 	}
8715 
8716 	private int getActualSetSize(size_t i, bool askWindows) {
8717 		version(win32_widgets)
8718 			if(askWindows)
8719 				return cast(int) SendMessage(hwnd, LVM_GETCOLUMNWIDTH, cast(WPARAM) i, 0);
8720 		auto w = columns[i].width;
8721 		if(w == -1)
8722 			return 50; // idk, just give it some space so the percents aren't COMPLETELY off FIXME
8723 		return w;
8724 	}
8725 
8726 	private void updateCalculatedWidth(bool informWindows) {
8727 		int padding;
8728 		version(win32_widgets)
8729 			padding = 4;
8730 		int remaining = this.width;
8731 		foreach(i, column; columns)
8732 			remaining -= this.getActualSetSize(i, informWindows && column.widthPercent == 0) + padding;
8733 		remaining -= padding;
8734 		if(remaining < 0)
8735 			remaining = 0;
8736 
8737 		int percentTotal;
8738 		foreach(i, ref column; columns) {
8739 			percentTotal += column.widthPercent;
8740 
8741 			auto c = this.getActualSetSize(i, informWindows && column.widthPercent == 0) + (remaining * column.widthPercent) / 100;
8742 
8743 			column.calculatedWidth = c;
8744 
8745 			version(win32_widgets)
8746 			if(informWindows)
8747 				SendMessage(hwnd, LVM_SETCOLUMNWIDTH, i, c); // LVSCW_AUTOSIZE or LVSCW_AUTOSIZE_USEHEADER are amazing omg
8748 		}
8749 
8750 		assert(percentTotal >= 0, "The total percents in your column definitions were negative. They must add up to something between 0 and 100.");
8751 		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).");
8752 			
8753 
8754 	}
8755 
8756 	override void registerMovement() {
8757 		super.registerMovement();
8758 
8759 		updateCalculatedWidth(true);
8760 	}
8761 
8762 	/++
8763 		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.
8764 	+/
8765 	void setItemCount(int count) {
8766 		this.itemCount = count;
8767 		version(custom_widgets) {
8768 			tvwi.updateScrolls();
8769 			redraw();
8770 		} else version(win32_widgets) {
8771 			SendMessage(hwnd, LVM_SETITEMCOUNT, count, 0);
8772 		}
8773 	}
8774 
8775 	/++
8776 		Clears all items;
8777 	+/
8778 	void clear() {
8779 		this.itemCount = 0;
8780 		this.columns = null;
8781 		version(custom_widgets) {
8782 			tvwi.header.updateHeaders();
8783 			tvwi.updateScrolls();
8784 			redraw();
8785 		} else version(win32_widgets) {
8786 			SendMessage(hwnd, LVM_DELETEALLITEMS, 0, 0);
8787 		}
8788 	}
8789 
8790 	/+
8791 	version(win32_widgets)
8792 	override int handleWmDrawItem(DRAWITEMSTRUCT* dis) 
8793 		auto itemId = dis.itemID;
8794 		auto hdc = dis.hDC;
8795 		auto rect = dis.rcItem;
8796 		switch(dis.itemAction) {
8797 			case ODA_DRAWENTIRE:
8798 
8799 				// FIXME: do other items
8800 				// FIXME: do the focus rectangle i guess
8801 				// FIXME: alignment
8802 				// FIXME: column width
8803 				// FIXME: padding left
8804 				// FIXME: check dpi scaling
8805 				// FIXME: don't owner draw unless it is necessary.
8806 
8807 				auto padding = GetSystemMetrics(SM_CXEDGE); // FIXME: for dpi
8808 				RECT itemRect;
8809 				itemRect.top = 1; // subitem idx, 1-based
8810 				itemRect.left = LVIR_BOUNDS;
8811 
8812 				SendMessage(hwnd, LVM_GETSUBITEMRECT, itemId, cast(LPARAM) &itemRect);
8813 				itemRect.left += padding;
8814 
8815 				getData(itemId, 0, (in char[] data) {
8816 					auto wdata = WCharzBuffer(data);
8817 					DrawTextW(hdc, wdata.ptr, wdata.length, &itemRect, DT_RIGHT| DT_END_ELLIPSIS);
8818 
8819 				});
8820 			goto case;
8821 			case ODA_FOCUS:
8822 				if(dis.itemState & ODS_FOCUS)
8823 					DrawFocusRect(hdc, &rect);
8824 			break;
8825 			case ODA_SELECT:
8826 				// itemState & ODS_SELECTED
8827 			break;
8828 			default:
8829 		}
8830 		return 1;
8831 	}
8832 	+/
8833 
8834 	version(win32_widgets) {
8835 		CellStyle last;
8836 		COLORREF defaultColor;
8837 		COLORREF defaultBackground;
8838 	}
8839 
8840 	version(win32_widgets)
8841 	override int handleWmNotify(NMHDR* hdr, int code, out int mustReturn) {
8842 		switch(code) {
8843 			case NM_CUSTOMDRAW:
8844 				auto s = cast(NMLVCUSTOMDRAW*) hdr;
8845 				switch(s.nmcd.dwDrawStage) {
8846 					case CDDS_PREPAINT:
8847 						if(getCellStyle is null)
8848 							return 0;
8849 
8850 						mustReturn = true;
8851 						return CDRF_NOTIFYITEMDRAW;
8852 					case CDDS_ITEMPREPAINT:
8853 						mustReturn = true;
8854 						return CDRF_NOTIFYSUBITEMDRAW;
8855 					case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
8856 						mustReturn = true;
8857 
8858 						if(getCellStyle is null) // this SHOULD never happen...
8859 							return 0;
8860 
8861 						if(s.iSubItem == 0) {
8862 							// Windows resets it per row so we'll use item 0 as a chance
8863 							// to capture these for later
8864 							defaultColor = s.clrText;
8865 							defaultBackground = s.clrTextBk;
8866 						}
8867 
8868 						auto style = getCellStyle(cast(int) s.nmcd.dwItemSpec, cast(int) s.iSubItem);
8869 						// if no special style and no reset needed...
8870 						if(style == CellStyle.init && (s.iSubItem == 0 || last == CellStyle.init))
8871 							return 0; // allow default processing to continue
8872 
8873 						last = style;
8874 
8875 						// might still need to reset or use the preference.
8876 
8877 						if(style.flags & CellStyle.Flags.textColorSet)
8878 							s.clrText = style.textColor.asWindowsColorRef;
8879 						else
8880 							s.clrText = defaultColor; // reset in case it was set from last iteration not a fan
8881 						if(style.flags & CellStyle.Flags.backgroundColorSet)
8882 							s.clrTextBk = style.backgroundColor.asWindowsColorRef;
8883 						else
8884 							s.clrTextBk = defaultBackground; // need to reset it... not a fan of this
8885 
8886 						return CDRF_NEWFONT;
8887 					default:
8888 						return 0;
8889 
8890 				}
8891 			case NM_RETURN: // no need since i subclass keydown
8892 			break;
8893 			case LVN_COLUMNCLICK:
8894 				auto info = cast(LPNMLISTVIEW) hdr;
8895 				this.emit!HeaderClickedEvent(info.iSubItem);
8896 			break;
8897 			case NM_CLICK:
8898 			case NM_DBLCLK:
8899 			case NM_RCLICK:
8900 			case NM_RDBLCLK:
8901 				// the item/subitem is set here and that can be a useful notification
8902 				// even beyond the normal click notification
8903 			break;
8904 			case LVN_GETDISPINFO:
8905 				LV_DISPINFO* info = cast(LV_DISPINFO*) hdr;
8906 				if(info.item.mask & LVIF_TEXT) {
8907 					if(getData) {
8908 						getData(info.item.iItem, info.item.iSubItem, (in char[] dataReceived) {
8909 							auto bfr = WCharzBuffer(dataReceived);
8910 							auto len = info.item.cchTextMax;
8911 							if(bfr.length < len)
8912 								len = cast(typeof(len)) bfr.length;
8913 							info.item.pszText[0 .. len] = bfr.ptr[0 .. len];
8914 							info.item.pszText[len] = 0;
8915 						});
8916 					} else {
8917 						info.item.pszText[0] = 0;
8918 					}
8919 					//info.item.iItem
8920 					//if(info.item.iSubItem)
8921 				}
8922 			break;
8923 			default:
8924 		}
8925 		return 0;
8926 	}
8927 
8928 	override bool encapsulatedChildren() {
8929 		return true;
8930 	}
8931 
8932 	/++
8933 		Informs the control that content has changed.
8934 
8935 		History:
8936 			Added November 10, 2021 (dub v10.4)
8937 	+/
8938 	void update() {
8939 		version(custom_widgets)
8940 			redraw();
8941 		else {
8942 			SendMessage(hwnd, LVM_REDRAWITEMS, 0, SendMessage(hwnd, LVM_GETITEMCOUNT, 0, 0));
8943 			UpdateWindow(hwnd);
8944 		}
8945 			
8946 
8947 	}
8948 
8949 	/++
8950 		Called by the system to request the text content of an individual cell. You
8951 		should pass the text into the provided `sink` delegate. This function will be
8952 		called for each visible cell as-needed when drawing.
8953 	+/
8954 	void delegate(int row, int column, scope void delegate(in char[]) sink) getData;
8955 
8956 	/++
8957 		Available per-cell style customization options. Use one of the constructors
8958 		provided to set the values conveniently, or default construct it and set individual
8959 		values yourself. Just remember to set the `flags` so your values are actually used.
8960 		If the flag isn't set, the field is ignored and the system default is used instead.
8961 
8962 		This is returned by the [getCellStyle] delegate.
8963 
8964 		Examples:
8965 			---
8966 			// assumes you have a variables called `my_data` which is an array of arrays of numbers
8967 			auto table = new TableView(window);
8968 			// snip: you would set up columns here
8969 
8970 			// this is how you provide data to the table view class
8971 			table.getData = delegate(int row, int column, scope void delegate(in char[]) sink) {
8972 				import std.conv;
8973 				sink(to!string(my_data[row][column]));
8974 			};
8975 
8976 			// and this is how you customize the colors
8977 			table.getCellStyle = delegate(int row, int column) {
8978 				return (my_data[row][column] < 0) ?
8979 					TableView.CellStyle(Color.red); // make negative numbers red
8980 					: TableView.CellStyle.init; // leave the rest alone
8981 			};
8982 			// snip: you would call table.setItemCount here then continue with the rest of your window setup work
8983 			---
8984 
8985 		History:
8986 			Added November 27, 2021 (dub v10.4)
8987 	+/
8988 	struct CellStyle {
8989 		/// 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.
8990 		this(Color textColor) {
8991 			this.textColor = textColor;
8992 			this.flags |= Flags.textColorSet;
8993 		}
8994 		/// Sets a custom text and background color.
8995 		this(Color textColor, Color backgroundColor) {
8996 			this.textColor = textColor;
8997 			this.backgroundColor = backgroundColor;
8998 			this.flags |= Flags.textColorSet | Flags.backgroundColorSet;
8999 		}
9000 
9001 		Color textColor;
9002 		Color backgroundColor;
9003 		int flags; /// bitmask of [Flags]
9004 		/// available options to combine into [flags]
9005 		enum Flags {
9006 			textColorSet = 1 << 0,
9007 			backgroundColorSet = 1 << 1,
9008 		}
9009 	}
9010 	/++
9011 		Companion delegate to [getData] that allows you to custom style each
9012 		cell of the table.
9013 
9014 		Returns:
9015 			A [CellStyle] structure that describes the desired style for the
9016 			given cell. `return CellStyle.init` if you want the default style.
9017 
9018 		History:
9019 			Added November 27, 2021 (dub v10.4)
9020 	+/
9021 	CellStyle delegate(int row, int column) getCellStyle;
9022 
9023 	// i want to be able to do things like draw little colored things to show red for negative numbers
9024 	// or background color indicators or even in-cell charts
9025 	// void delegate(int row, int column, WidgetPainter painter, int width, int height, in char[] text) drawCell;
9026 
9027 	/++
9028 		When the user clicks on a header, this event is emitted. It has a meber to identify which header (by index) was clicked.
9029 	+/
9030 	mixin Emits!HeaderClickedEvent;
9031 }
9032 
9033 /++
9034 	This is emitted by the [TableView] when a user clicks on a column header.
9035 
9036 	Its member `columnIndex` has the zero-based index of the column that was clicked.
9037 
9038 	The default behavior of this event is to do nothing, so `preventDefault` has no effect.
9039 
9040 	History:
9041 		Added November 27, 2021 (dub v10.4)
9042 +/
9043 class HeaderClickedEvent : Event {
9044 	enum EventString = "HeaderClicked";
9045 	this(Widget target, int columnIndex) {
9046 		this.columnIndex = columnIndex;
9047 		super(EventString, target);
9048 	}
9049 
9050 	/// The index of the column
9051 	int columnIndex;
9052 
9053 	///
9054 	override @property int intValue() {
9055 		return columnIndex;
9056 	}
9057 }
9058 
9059 version(custom_widgets)
9060 private class TableViewWidgetInner : Widget {
9061 
9062 // wrap this thing in a ScrollMessageWidget
9063 
9064 	TableView tvw;
9065 	ScrollMessageWidget smw;
9066 	HeaderWidget header;
9067 
9068 	this(TableView tvw, ScrollMessageWidget smw) {
9069 		this.tvw = tvw;
9070 		this.smw = smw;
9071 		super(smw);
9072 
9073 		this.tabStop = true;
9074 
9075 		header = new HeaderWidget(this, smw.getHeader());
9076 
9077 		smw.addEventListener("scroll", () {
9078 			this.redraw();
9079 			header.redraw();
9080 		});
9081 
9082 
9083 		// I need headers outside the scroll area but rendered on the same line as the up arrow
9084 		// FIXME: add a fixed header to the SMW
9085 	}
9086 
9087 	enum padding = 3;
9088 
9089 	void updateScrolls() {
9090 		int w;
9091 		foreach(idx, column; tvw.columns) {
9092 			if(column.width == 0) continue;
9093 			w += tvw.getActualSetSize(idx, false);// + padding;
9094 		}
9095 		smw.setTotalArea(w, tvw.itemCount);
9096 		columnsWidth = w;
9097 	}
9098 
9099 	private int columnsWidth;
9100 
9101 	private int lh() { return scaleWithDpi(16); } // FIXME lineHeight
9102 
9103 	override void registerMovement() {
9104 		super.registerMovement();
9105 		// FIXME: actual column width. it might need to be done per-pixel instead of per-colum
9106 		smw.setViewableArea(this.width, this.height / lh);
9107 	}
9108 
9109 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
9110 		int x;
9111 		int y;
9112 
9113 		int row = smw.position.y;
9114 
9115 		foreach(lol; 0 .. this.height / lh) {
9116 			if(row >= tvw.itemCount)
9117 				break;
9118 			x = 0;
9119 			foreach(columnNumber, column; tvw.columns) {
9120 				auto x2 = x + column.calculatedWidth;
9121 				auto smwx = smw.position.x;
9122 
9123 				if(x2 > smwx /* if right side of it is visible at all */ || (x >= smwx && x < smwx + this.width) /* left side is visible at all*/) {
9124 					auto startX = x;
9125 					auto endX = x + column.calculatedWidth;
9126 					switch (column.alignment & (TextAlignment.Left | TextAlignment.Center | TextAlignment.Right)) {
9127 						case TextAlignment.Left: startX += padding; break;
9128 						case TextAlignment.Center: startX += padding; endX -= padding; break;
9129 						case TextAlignment.Right: endX -= padding; break;
9130 						default: /* broken */ break;
9131 					}
9132 					if(column.width != 0) // no point drawing an invisible column
9133 					tvw.getData(row, cast(int) columnNumber, (info) {
9134 						// auto clip = painter.setClipRectangle(
9135 
9136 						void dotext(WidgetPainter painter) {
9137 							painter.drawText(Point(startX - smw.position.x, y), info, Point(endX - smw.position.x, y + lh), column.alignment);
9138 						}
9139 
9140 						if(tvw.getCellStyle !is null) {
9141 							auto style = tvw.getCellStyle(row, cast(int) columnNumber);
9142 
9143 							if(style.flags & TableView.CellStyle.Flags.backgroundColorSet) {
9144 								auto tempPainter = painter;
9145 								tempPainter.fillColor = style.backgroundColor;
9146 								tempPainter.outlineColor = style.backgroundColor;
9147 
9148 								tempPainter.drawRectangle(Point(startX - smw.position.x, y), 
9149 									Point(endX - smw.position.x, y + lh));
9150 							}
9151 							auto tempPainter = painter;
9152 							if(style.flags & TableView.CellStyle.Flags.textColorSet)
9153 								tempPainter.outlineColor = style.textColor;
9154 
9155 							dotext(tempPainter);
9156 						} else {
9157 							dotext(painter);
9158 						}
9159 					});
9160 				}
9161 
9162 				x += column.calculatedWidth;
9163 			}
9164 			row++;
9165 			y += lh;
9166 		}
9167 		return bounds;
9168 	}
9169 
9170 	static class Style : Widget.Style {
9171 		override WidgetBackground background() {
9172 			return WidgetBackground(WidgetPainter.visualTheme.widgetBackgroundColor);
9173 		}
9174 	}
9175 	mixin OverrideStyle!Style;
9176 
9177 	private static class HeaderWidget : Widget {
9178 		this(TableViewWidgetInner tvw, Widget parent) {
9179 			super(parent);
9180 			this.tvw = tvw;
9181 
9182 			this.remainder = new Button("", this);
9183 
9184 			this.addEventListener((scope ClickEvent ev) {
9185 				int header = -1;
9186 				foreach(idx, child; this.children[1 .. $]) {
9187 					if(child is ev.target) {
9188 						header = cast(int) idx;
9189 						break;
9190 					}
9191 				}
9192 
9193 				if(header != -1) {
9194 					auto hce = new HeaderClickedEvent(tvw.tvw, header);
9195 					hce.dispatch();
9196 				}
9197 
9198 			});
9199 		}
9200 
9201 		void updateHeaders() {
9202 			foreach(child; children[1 .. $])
9203 				child.removeWidget();
9204 
9205 			foreach(column; tvw.tvw.columns) {
9206 				// the cast is ok because I dup it above, just the type is never changed.
9207 				// all this is private so it should never get messed up.
9208 				new Button(ImageLabel(cast(string) column.name, column.alignment), this);
9209 			}
9210 		}
9211 
9212 		Button remainder;
9213 		TableViewWidgetInner tvw;
9214 
9215 		override void recomputeChildLayout() {
9216 			registerMovement();
9217 			int pos;
9218 			foreach(idx, child; children[1 .. $]) {
9219 				if(idx >= tvw.tvw.columns.length)
9220 					continue;
9221 				child.x = pos;
9222 				child.y = 0;
9223 				child.width = tvw.tvw.columns[idx].calculatedWidth;
9224 				child.height = scaleWithDpi(16);// this.height;
9225 				pos += child.width;
9226 
9227 				child.recomputeChildLayout();
9228 			}
9229 
9230 			if(remainder is null)
9231 				return;
9232 
9233 			remainder.x = pos;
9234 			remainder.y = 0;
9235 			if(pos < this.width)
9236 				remainder.width = this.width - pos;// + 4;
9237 			else
9238 				remainder.width = 0;
9239 			remainder.height = scaleWithDpi(16);
9240 
9241 			remainder.recomputeChildLayout();
9242 		}
9243 
9244 		// for the scrollable children mixin
9245 		Point scrollOrigin() {
9246 			return Point(tvw.smw.position.x, 0);
9247 		}
9248 		void paintFrameAndBackground(WidgetPainter painter) { }
9249 
9250 		mixin ScrollableChildren;
9251 	}
9252 }
9253 
9254 /+
9255 
9256 // given struct / array / number / string / etc, make it viewable and editable
9257 class DataViewerWidget : Widget {
9258 
9259 }
9260 +/
9261 
9262 /++
9263 	A line edit box with an associated label.
9264 
9265 	History:
9266 		On May 17, 2021, the default internal layout was changed from horizontal to vertical.
9267 
9268 		```
9269 		Old: ________
9270 
9271 		New:
9272 		____________
9273 		```
9274 
9275 		To restore the old behavior, use `new LabeledLineEdit("label", TextAlignment.Right, parent);`
9276 
9277 		You can also use `new LabeledLineEdit("label", TextAlignment.Left, parent);` if you want a
9278 		horizontal label but left aligned. You may also consider a [GridLayout].
9279 +/
9280 alias LabeledLineEdit = Labeled!LineEdit;
9281 
9282 /++
9283 	History:
9284 		Added May 19, 2021
9285 +/
9286 class Labeled(T) : Widget {
9287 	///
9288 	this(string label, Widget parent) {
9289 		super(parent);
9290 		initialize!VerticalLayout(label, TextAlignment.Left, parent);
9291 	}
9292 
9293 	/++
9294 		History:
9295 			The alignment parameter was added May 17, 2021
9296 	+/
9297 	this(string label, TextAlignment alignment, Widget parent) {
9298 		super(parent);
9299 		initialize!HorizontalLayout(label, alignment, parent);
9300 	}
9301 
9302 	private void initialize(L)(string label, TextAlignment alignment, Widget parent) {
9303 		tabStop = false;
9304 		horizontal = is(L == HorizontalLayout);
9305 		auto hl = new L(this);
9306 		this.label = new TextLabel(label, alignment, hl);
9307 		this.lineEdit = new T(hl);
9308 
9309 		this.label.labelFor = this.lineEdit;
9310 	}
9311 
9312 	private bool horizontal;
9313 
9314 	TextLabel label; ///
9315 	T lineEdit; ///
9316 
9317 	override int flexBasisWidth() { return 250; }
9318 
9319 	override int minHeight() { return (horizontal ? 1 : 2) * defaultLineHeight + 4; }
9320 	override int maxHeight() { return (horizontal ? 1 : 2) * defaultLineHeight + 4; }
9321 	override int marginTop() { return 4; }
9322 	override int marginBottom() { return 4; }
9323 
9324 	// FIXME: i should prolly call it value as well as content tbh
9325 
9326 	///
9327 	@property string content() {
9328 		return lineEdit.content;
9329 	}
9330 	///
9331 	@property void content(string c) {
9332 		return lineEdit.content(c);
9333 	}
9334 
9335 	///
9336 	void selectAll() {
9337 		lineEdit.selectAll();
9338 	}
9339 
9340 	override void focus() {
9341 		lineEdit.focus();
9342 	}
9343 }
9344 
9345 /++
9346 	A labeled password edit.
9347 
9348 	History:
9349 		Added as a class on January 25, 2021, changed into an alias of the new [Labeled] template on May 19, 2021
9350 
9351 		The default parameters for the constructors were also removed on May 19, 2021
9352 +/
9353 alias LabeledPasswordEdit = Labeled!PasswordEdit;
9354 
9355 private string toMenuLabel(string s) {
9356 	string n;
9357 	n.reserve(s.length);
9358 	foreach(c; s)
9359 		if(c == '_')
9360 			n ~= ' ';
9361 		else
9362 			n ~= c;
9363 	return n;
9364 }
9365 
9366 private void autoExceptionHandler(Exception e) {
9367 	messageBox(e.msg);
9368 }
9369 
9370 private void delegate() makeAutomaticHandler(alias fn, T)(T t) {
9371 	static if(is(T : void delegate())) {
9372 		return () {
9373 			try
9374 				t();
9375 			catch(Exception e)
9376 				autoExceptionHandler(e);
9377 		};
9378 	} else static if(is(typeof(fn) Params == __parameters)) {
9379 		static if(Params.length == 1 && is(Params[0] == FileName!(member, filters, type), alias member, string[] filters, FileDialogType type)) {
9380 			return () {
9381 				void onOK(string s) {
9382 					member = s;
9383 					try
9384 						t(Params[0](s));
9385 					catch(Exception e)
9386 						autoExceptionHandler(e);
9387 				}
9388 
9389 				if(
9390 					(type == FileDialogType.Automatic && (__traits(identifier, fn).startsWith("Save") || __traits(identifier, fn).startsWith("Export")))
9391 					|| type == FileDialogType.Save)
9392 				{
9393 					getSaveFileName(&onOK, member, filters, null);
9394 				} else
9395 					getOpenFileName(&onOK, member, filters, null);
9396 			};
9397 		} else {
9398 			struct S {
9399 				static if(!__traits(compiles, mixin(`{ static foreach(i; 1..4) {} }`))) {
9400 					pragma(msg, "warning: automatic handler of params not yet implemented on your compiler");
9401 				} else mixin(q{
9402 				static foreach(idx, ignore; Params) {
9403 					mixin("Params[idx] " ~ __traits(identifier, Params[idx .. idx + 1]) ~ ";");
9404 				}
9405 				});
9406 			}
9407 			return () {
9408 				dialog((S s) {
9409 					try
9410 						cast(void) t(s.tupleof);
9411 					catch(Exception e)
9412 						autoExceptionHandler(e);
9413 				}, null, __traits(identifier, fn));
9414 			};
9415 		}
9416 	}
9417 }
9418 
9419 private template hasAnyRelevantAnnotations(a...) {
9420 	bool helper() {
9421 		bool any;
9422 		foreach(attr; a) {
9423 			static if(is(typeof(attr) == .menu))
9424 				any = true;
9425 			else static if(is(typeof(attr) == .toolbar))
9426 				any = true;
9427 			else static if(is(attr == .separator))
9428 				any = true;
9429 			else static if(is(typeof(attr) == .accelerator))
9430 				any = true;
9431 			else static if(is(typeof(attr) == .hotkey))
9432 				any = true;
9433 			else static if(is(typeof(attr) == .icon))
9434 				any = true;
9435 			else static if(is(typeof(attr) == .label))
9436 				any = true;
9437 			else static if(is(typeof(attr) == .tip))
9438 				any = true;
9439 		}
9440 		return any;
9441 	}
9442 
9443 	enum bool hasAnyRelevantAnnotations = helper();
9444 }
9445 
9446 /++
9447 	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.
9448 +/
9449 class MainWindow : Window {
9450 	///
9451 	this(string title = null, int initialWidth = 500, int initialHeight = 500) {
9452 		super(initialWidth, initialHeight, title);
9453 
9454 		_clientArea = new ClientAreaWidget();
9455 		_clientArea.x = 0;
9456 		_clientArea.y = 0;
9457 		_clientArea.width = this.width;
9458 		_clientArea.height = this.height;
9459 		_clientArea.tabStop = false;
9460 
9461 		super.addChild(_clientArea);
9462 
9463 		statusBar = new StatusBar(this);
9464 	}
9465 
9466 	/++
9467 		Adds a menu and toolbar from annotated functions.
9468 
9469 	---
9470         struct Commands {
9471                 @menu("File") {
9472                         void New() {}
9473                         void Open() {}
9474                         void Save() {}
9475                         @separator
9476                         void Exit() @accelerator("Alt+F4") @hotkey('x') {
9477                                 window.close();
9478                         }
9479                 }
9480 
9481                 @menu("Edit") {
9482                         void Undo() {
9483                                 undo();
9484                         }
9485                         @separator
9486                         void Cut() {}
9487                         void Copy() {}
9488                         void Paste() {}
9489                 }
9490 
9491                 @menu("Help") {
9492                         void About() {}
9493                 }
9494         }
9495 
9496         Commands commands;
9497 
9498         window.setMenuAndToolbarFromAnnotatedCode(commands);
9499 	---
9500 
9501 	Note that you can call this function multiple times and it will add the items in order to the given items.
9502 
9503 	+/
9504 	void setMenuAndToolbarFromAnnotatedCode(T)(ref T t) if(!is(T == class) && !is(T == interface)) {
9505 		setMenuAndToolbarFromAnnotatedCode_internal(t);
9506 	}
9507 	void setMenuAndToolbarFromAnnotatedCode(T)(T t) if(is(T == class) || is(T == interface)) {
9508 		setMenuAndToolbarFromAnnotatedCode_internal(t);
9509 	}
9510 	void setMenuAndToolbarFromAnnotatedCode_internal(T)(ref T t) {
9511 		Action[] toolbarActions;
9512 		auto menuBar = this.menuBar is null ? new MenuBar() : this.menuBar;
9513 		Menu[string] mcs;
9514 
9515 		foreach(menu; menuBar.subMenus) {
9516 			mcs[menu.label] = menu;
9517 		}
9518 
9519 		foreach(memberName; __traits(derivedMembers, T)) {
9520 			static if(memberName != "this")
9521 			static if(hasAnyRelevantAnnotations!(__traits(getAttributes, __traits(getMember, T, memberName)))) {
9522 				.menu menu;
9523 				.toolbar toolbar;
9524 				bool separator;
9525 				.accelerator accelerator;
9526 				.hotkey hotkey;
9527 				.icon icon;
9528 				string label;
9529 				string tip;
9530 				foreach(attr; __traits(getAttributes, __traits(getMember, T, memberName))) {
9531 					static if(is(typeof(attr) == .menu))
9532 						menu = attr;
9533 					else static if(is(typeof(attr) == .toolbar))
9534 						toolbar = attr;
9535 					else static if(is(attr == .separator))
9536 						separator = true;
9537 					else static if(is(typeof(attr) == .accelerator))
9538 						accelerator = attr;
9539 					else static if(is(typeof(attr) == .hotkey))
9540 						hotkey = attr;
9541 					else static if(is(typeof(attr) == .icon))
9542 						icon = attr;
9543 					else static if(is(typeof(attr) == .label))
9544 						label = attr.label;
9545 					else static if(is(typeof(attr) == .tip))
9546 						tip = attr.tip;
9547 				}
9548 
9549 				if(menu !is .menu.init || toolbar !is .toolbar.init) {
9550 					ushort correctIcon = icon.id; // FIXME
9551 					if(label.length == 0)
9552 						label = memberName.toMenuLabel;
9553 
9554 					auto handler = makeAutomaticHandler!(__traits(getMember, T, memberName))(&__traits(getMember, t, memberName));
9555 
9556 					auto action = new Action(label, correctIcon, handler);
9557 
9558 					if(accelerator.keyString.length) {
9559 						auto ke = KeyEvent.parse(accelerator.keyString);
9560 						action.accelerator = ke;
9561 						accelerators[ke.toStr] = handler;
9562 					}
9563 
9564 					if(toolbar !is .toolbar.init)
9565 						toolbarActions ~= action;
9566 					if(menu !is .menu.init) {
9567 						Menu mc;
9568 						if(menu.name in mcs) {
9569 							mc = mcs[menu.name];
9570 						} else {
9571 							mc = new Menu(menu.name, this);
9572 							menuBar.addItem(mc);
9573 							mcs[menu.name] = mc;
9574 						}
9575 
9576 						if(separator)
9577 							mc.addSeparator();
9578 						mc.addItem(new MenuItem(action));
9579 					}
9580 				}
9581 			}
9582 		}
9583 
9584 		this.menuBar = menuBar;
9585 
9586 		if(toolbarActions.length) {
9587 			auto tb = new ToolBar(toolbarActions, this);
9588 		}
9589 	}
9590 
9591 	void delegate()[string] accelerators;
9592 
9593 	override void defaultEventHandler_keydown(KeyDownEvent event) {
9594 		auto str = event.originalKeyEvent.toStr;
9595 		if(auto acl = str in accelerators)
9596 			(*acl)();
9597 		super.defaultEventHandler_keydown(event);
9598 	}
9599 
9600 	override void defaultEventHandler_mouseover(MouseOverEvent event) {
9601 		super.defaultEventHandler_mouseover(event);
9602 		if(this.statusBar !is null && event.target.statusTip.length)
9603 			this.statusBar.parts[0].content = event.target.statusTip;
9604 		else if(this.statusBar !is null && this.statusTip.length)
9605 			this.statusBar.parts[0].content = this.statusTip; // ~ " " ~ event.target.toString();
9606 	}
9607 
9608 	override void addChild(Widget c, int position = int.max) {
9609 		if(auto tb = cast(ToolBar) c)
9610 			version(win32_widgets)
9611 				super.addChild(c, 0);
9612 			else version(custom_widgets)
9613 				super.addChild(c, menuBar ? 1 : 0);
9614 			else static assert(0);
9615 		else
9616 			clientArea.addChild(c, position);
9617 	}
9618 
9619 	ToolBar _toolBar;
9620 	///
9621 	ToolBar toolBar() { return _toolBar; }
9622 	///
9623 	ToolBar toolBar(ToolBar t) {
9624 		_toolBar = t;
9625 		foreach(child; this.children)
9626 			if(child is t)
9627 				return t;
9628 		version(win32_widgets)
9629 			super.addChild(t, 0);
9630 		else version(custom_widgets)
9631 			super.addChild(t, menuBar ? 1 : 0);
9632 		else static assert(0);
9633 		return t;
9634 	}
9635 
9636 	MenuBar _menu;
9637 	///
9638 	MenuBar menuBar() { return _menu; }
9639 	///
9640 	MenuBar menuBar(MenuBar m) {
9641 		if(m is _menu) {
9642 			version(custom_widgets)
9643 				recomputeChildLayout();
9644 			return m;
9645 		}
9646 
9647 		if(_menu !is null) {
9648 			// make sure it is sanely removed
9649 			// FIXME
9650 		}
9651 
9652 		_menu = m;
9653 
9654 		version(win32_widgets) {
9655 			SetMenu(parentWindow.win.impl.hwnd, m.handle);
9656 		} else version(custom_widgets) {
9657 			super.addChild(m, 0);
9658 
9659 		//	clientArea.y = menu.height;
9660 		//	clientArea.height = this.height - menu.height;
9661 
9662 			recomputeChildLayout();
9663 		} else static assert(false);
9664 
9665 		return _menu;
9666 	}
9667 	private Widget _clientArea;
9668 	///
9669 	@property Widget clientArea() { return _clientArea; }
9670 	protected @property void clientArea(Widget wid) {
9671 		_clientArea = wid;
9672 	}
9673 
9674 	private StatusBar _statusBar;
9675 	/++
9676 		Returns the window's [StatusBar]. Be warned it may be `null`.
9677 	+/
9678 	@property StatusBar statusBar() { return _statusBar; }
9679 	/// ditto
9680 	@property void statusBar(StatusBar bar) {
9681 		if(_statusBar !is null)
9682 			_statusBar.removeWidget();
9683 		_statusBar = bar;
9684 		if(bar !is null)
9685 			super.addChild(_statusBar);
9686 	}
9687 }
9688 
9689 /+
9690 	This is really an implementation detail of [MainWindow]
9691 +/
9692 private class ClientAreaWidget : Widget {
9693 	this() {
9694 		this.tabStop = false;
9695 		super(null);
9696 		//sa = new ScrollableWidget(this);
9697 	}
9698 	/*
9699 	ScrollableWidget sa;
9700 	override void addChild(Widget w, int position) {
9701 		if(sa is null)
9702 			super.addChild(w, position);
9703 		else {
9704 			sa.addChild(w, position);
9705 			sa.setContentSize(this.minWidth + 1, this.minHeight);
9706 			import std.stdio; writeln(sa.contentWidth, "x", sa.contentHeight);
9707 		}
9708 	}
9709 	*/
9710 }
9711 
9712 /**
9713 	Toolbars are lists of buttons (typically icons) that appear under the menu.
9714 	Each button ought to correspond to a menu item, represented by [Action] objects.
9715 */
9716 class ToolBar : Widget {
9717 	version(win32_widgets) {
9718 		private const int idealHeight;
9719 		override int minHeight() { return idealHeight; }
9720 		override int maxHeight() { return idealHeight; }
9721 	} else version(custom_widgets) {
9722 		override int minHeight() { return toolbarIconSize; }// defaultLineHeight * 3/2; }
9723 		override int maxHeight() { return toolbarIconSize; } //defaultLineHeight * 3/2; }
9724 	} else static assert(false);
9725 	override int heightStretchiness() { return 0; }
9726 
9727 	version(win32_widgets) 
9728 		HIMAGELIST imageList;
9729 
9730 	this(Widget parent) {
9731 		this(null, parent);
9732 	}
9733 
9734 	///
9735 	this(Action[] actions, Widget parent) {
9736 		super(parent);
9737 
9738 		tabStop = false;
9739 
9740 		version(win32_widgets) {
9741 			// so i like how the flat thing looks on windows, but not on wine
9742 			// and eh, with windows visual styles enabled it looks cool anyway soooo gonna
9743 			// leave it commented
9744 			createWin32Window(this, "ToolbarWindow32"w, "", TBSTYLE_LIST|/*TBSTYLE_FLAT|*/TBSTYLE_TOOLTIPS);
9745 			
9746 			SendMessageW(hwnd, TB_SETEXTENDEDSTYLE, 0, 8/*TBSTYLE_EX_MIXEDBUTTONS*/);
9747 
9748 			imageList = ImageList_Create(
9749 				// width, height
9750 				16, 16,
9751 				ILC_COLOR16 | ILC_MASK,
9752 				16 /*numberOfButtons*/, 0);
9753 
9754 			SendMessageW(hwnd, TB_SETIMAGELIST, cast(WPARAM) 0, cast(LPARAM) imageList);
9755 			SendMessageW(hwnd, TB_LOADIMAGES, cast(WPARAM) IDB_STD_SMALL_COLOR, cast(LPARAM) HINST_COMMCTRL);
9756 			SendMessageW(hwnd, TB_SETMAXTEXTROWS, 0, 0);
9757 			SendMessageW(hwnd, TB_AUTOSIZE, 0, 0);
9758 
9759 			TBBUTTON[] buttons;
9760 
9761 			// FIXME: I_IMAGENONE is if here is no icon
9762 			foreach(action; actions)
9763 				buttons ~= TBBUTTON(
9764 					MAKELONG(cast(ushort)(action.iconId ? (action.iconId - 1) : -2 /* I_IMAGENONE */), 0),
9765 					action.id,
9766 					TBSTATE_ENABLED, // state
9767 					0, // style
9768 					0, // reserved array, just zero it out
9769 					0, // dwData
9770 					cast(size_t) toWstringzInternal(action.label) // INT_PTR
9771 				);
9772 
9773 			SendMessageW(hwnd, TB_BUTTONSTRUCTSIZE, cast(WPARAM)TBBUTTON.sizeof, 0);
9774 			SendMessageW(hwnd, TB_ADDBUTTONSW, cast(WPARAM) buttons.length, cast(LPARAM)buttons.ptr);
9775 
9776 			SIZE size;
9777 			import core.sys.windows.commctrl;
9778 			SendMessageW(hwnd, TB_GETMAXSIZE, 0, cast(LPARAM) &size);
9779 			idealHeight = size.cy + 4; // the plus 4 is a hack
9780 
9781 			/*
9782 			RECT rect;
9783 			GetWindowRect(hwnd, &rect);
9784 			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
9785 			*/
9786 
9787 			assert(idealHeight);
9788 		} else version(custom_widgets) {
9789 			foreach(action; actions)
9790 				new ToolButton(action, this);
9791 		} else static assert(false);
9792 	}
9793 
9794 	override void recomputeChildLayout() {
9795 		.recomputeChildLayout!"width"(this);
9796 	}
9797 }
9798 
9799 enum toolbarIconSize = 24;
9800 
9801 /// 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.
9802 class ToolButton : Button {
9803 	///
9804 	this(string label, Widget parent) {
9805 		super(label, parent);
9806 		tabStop = false;
9807 	}
9808 	///
9809 	this(Action action, Widget parent) {
9810 		super(action.label, parent);
9811 		tabStop = false;
9812 		this.action = action;
9813 	}
9814 
9815 	version(custom_widgets)
9816 	override void defaultEventHandler_click(ClickEvent event) {
9817 		foreach(handler; action.triggered)
9818 			handler();
9819 	}
9820 
9821 	Action action;
9822 
9823 	override int maxWidth() { return toolbarIconSize; }
9824 	override int minWidth() { return toolbarIconSize; }
9825 	override int maxHeight() { return toolbarIconSize; }
9826 	override int minHeight() { return toolbarIconSize; }
9827 
9828 	version(custom_widgets)
9829 	override void paint(WidgetPainter painter) {
9830 	painter.drawThemed(delegate Rectangle (const Rectangle bounds) {
9831 		painter.outlineColor = Color.black;
9832 
9833 		// I want to get from 16 to 24. that's * 3 / 2
9834 		static assert(toolbarIconSize >= 16);
9835 		enum multiplier = toolbarIconSize / 8;
9836 		enum divisor = 2 + ((toolbarIconSize % 8) ? 1 : 0);
9837 		switch(action.iconId) {
9838 			case GenericIcons.New:
9839 				painter.fillColor = Color.white;
9840 				painter.drawPolygon(
9841 					Point(3, 2) * multiplier / divisor, Point(3, 13) * multiplier / divisor, Point(12, 13) * multiplier / divisor, Point(12, 6) * multiplier / divisor,
9842 					Point(8, 2) * multiplier / divisor, Point(8, 6) * multiplier / divisor, Point(12, 6) * multiplier / divisor, Point(8, 2) * multiplier / divisor,
9843 					Point(3, 2) * multiplier / divisor, Point(3, 13) * multiplier / divisor
9844 				);
9845 			break;
9846 			case GenericIcons.Save:
9847 				painter.fillColor = Color.white;
9848 				painter.outlineColor = Color.black;
9849 				painter.drawRectangle(Point(2, 2) * multiplier / divisor, Point(13, 13) * multiplier / divisor);
9850 
9851 				// the label
9852 				painter.drawRectangle(Point(4, 8) * multiplier / divisor, Point(11, 13) * multiplier / divisor);
9853 
9854 				// the slider
9855 				painter.fillColor = Color.black;
9856 				painter.outlineColor = Color.black;
9857 				painter.drawRectangle(Point(4, 3) * multiplier / divisor, Point(10, 6) * multiplier / divisor);
9858 
9859 				painter.fillColor = Color.white;
9860 				painter.outlineColor = Color.white;
9861 				// the disc window
9862 				painter.drawRectangle(Point(5, 3) * multiplier / divisor, Point(6, 5) * multiplier / divisor);
9863 			break;
9864 			case GenericIcons.Open:
9865 				painter.fillColor = Color.white;
9866 				painter.drawPolygon(
9867 					Point(4, 4) * multiplier / divisor, Point(4, 12) * multiplier / divisor, Point(13, 12) * multiplier / divisor, Point(13, 3) * multiplier / divisor,
9868 					Point(9, 3) * multiplier / divisor, Point(9, 4) * multiplier / divisor, Point(4, 4) * multiplier / divisor);
9869 				painter.drawPolygon(
9870 					Point(2, 6) * multiplier / divisor, Point(11, 6) * multiplier / divisor,
9871 					Point(12, 12) * multiplier / divisor, Point(4, 12) * multiplier / divisor,
9872 					Point(2, 6) * multiplier / divisor);
9873 				//painter.drawLine(Point(9, 6) * multiplier / divisor, Point(13, 7) * multiplier / divisor);
9874 			break;
9875 			case GenericIcons.Copy:
9876 				painter.fillColor = Color.white;
9877 				painter.drawRectangle(Point(3, 2) * multiplier / divisor, Point(9, 10) * multiplier / divisor);
9878 				painter.drawRectangle(Point(6, 5) * multiplier / divisor, Point(12, 13) * multiplier / divisor);
9879 			break;
9880 			case GenericIcons.Cut:
9881 				painter.fillColor = Color.transparent;
9882 				painter.outlineColor = getComputedStyle.foregroundColor();
9883 				painter.drawLine(Point(3, 2) * multiplier / divisor, Point(10, 9) * multiplier / divisor);
9884 				painter.drawLine(Point(4, 9) * multiplier / divisor, Point(11, 2) * multiplier / divisor);
9885 				painter.drawRectangle(Point(3, 9) * multiplier / divisor, Point(5, 13) * multiplier / divisor);
9886 				painter.drawRectangle(Point(9, 9) * multiplier / divisor, Point(11, 12) * multiplier / divisor);
9887 			break;
9888 			case GenericIcons.Paste:
9889 				painter.fillColor = Color.white;
9890 				painter.drawRectangle(Point(2, 3) * multiplier / divisor, Point(11, 11) * multiplier / divisor);
9891 				painter.drawRectangle(Point(6, 8) * multiplier / divisor, Point(13, 13) * multiplier / divisor);
9892 				painter.drawLine(Point(6, 2) * multiplier / divisor, Point(4, 5) * multiplier / divisor);
9893 				painter.drawLine(Point(6, 2) * multiplier / divisor, Point(9, 5) * multiplier / divisor);
9894 				painter.fillColor = Color.black;
9895 				painter.drawRectangle(Point(4, 5) * multiplier / divisor, Point(9, 6) * multiplier / divisor);
9896 			break;
9897 			case GenericIcons.Help:
9898 				painter.outlineColor = getComputedStyle.foregroundColor();
9899 				painter.drawText(Point(0, 0), "?", Point(width, height), TextAlignment.Center | TextAlignment.VerticalCenter);
9900 			break;
9901 			case GenericIcons.Undo:
9902 				painter.fillColor = Color.transparent;
9903 				painter.drawArc(Point(3, 4) * multiplier / divisor, 9 * multiplier / divisor, 9 * multiplier / divisor, 0, 360 * 64);
9904 				painter.outlineColor = Color.black;
9905 				painter.fillColor = Color.black;
9906 				painter.drawPolygon(
9907 					Point(4, 4) * multiplier / divisor,
9908 					Point(8, 2) * multiplier / divisor,
9909 					Point(8, 6) * multiplier / divisor,
9910 					Point(4, 4) * multiplier / divisor,
9911 				);
9912 			break;
9913 			case GenericIcons.Redo:
9914 				painter.fillColor = Color.transparent;
9915 				painter.drawArc(Point(3, 4) * multiplier / divisor, 9 * multiplier / divisor, 9 * multiplier / divisor, 0, 360 * 64);
9916 				painter.outlineColor = Color.black;
9917 				painter.fillColor = Color.black;
9918 				painter.drawPolygon(
9919 					Point(10, 4) * multiplier / divisor,
9920 					Point(6, 2) * multiplier / divisor,
9921 					Point(6, 6) * multiplier / divisor,
9922 					Point(10, 4) * multiplier / divisor,
9923 				);
9924 			break;
9925 			default:
9926 				painter.drawText(Point(0, 0), action.label, Point(width, height), TextAlignment.Center | TextAlignment.VerticalCenter);
9927 		}
9928 		return bounds;
9929 		});
9930 	}
9931 
9932 }
9933 
9934 
9935 ///
9936 class MenuBar : Widget {
9937 	MenuItem[] items;
9938 	Menu[] subMenus;
9939 
9940 	version(win32_widgets) {
9941 		HMENU handle;
9942 		///
9943 		this(Widget parent = null) {
9944 			super(parent);
9945 
9946 			handle = CreateMenu();
9947 			tabStop = false;
9948 		}
9949 	} else version(custom_widgets) {
9950 		///
9951 		this(Widget parent = null) {
9952 			tabStop = false; // these are selected some other way
9953 			super(parent);
9954 		}
9955 
9956 		mixin Padding!q{2};
9957 	} else static assert(false);
9958 
9959 	version(custom_widgets)
9960 	override void paint(WidgetPainter painter) {
9961 		draw3dFrame(this, painter, FrameStyle.risen, getComputedStyle().background.color);
9962 	}
9963 
9964 	///
9965 	MenuItem addItem(MenuItem item) {
9966 		this.addChild(item);
9967 		items ~= item;
9968 		version(win32_widgets) {
9969 			AppendMenuW(handle, MF_STRING, item.action is null ? 9000 : item.action.id, toWstringzInternal(item.label));
9970 		}
9971 		return item;
9972 	}
9973 
9974 
9975 	///
9976 	Menu addItem(Menu item) {
9977 
9978 		subMenus ~= item;
9979 
9980 		auto mbItem = new MenuItem(item.label, null);// this.parentWindow); // I'ma add the child down below so hopefully this isn't too insane
9981 
9982 		addChild(mbItem);
9983 		items ~= mbItem;
9984 
9985 		version(win32_widgets) {
9986 			AppendMenuW(handle, MF_STRING | MF_POPUP, cast(UINT) item.handle, toWstringzInternal(item.label));
9987 		} else version(custom_widgets) {
9988 			mbItem.defaultEventHandlers["mousedown"] = (Widget e, Event ev) {
9989 				item.popup(mbItem);
9990 			};
9991 		} else static assert(false);
9992 
9993 		return item;
9994 	}
9995 
9996 	override void recomputeChildLayout() {
9997 		.recomputeChildLayout!"width"(this);
9998 	}
9999 
10000 	override int maxHeight() { return defaultLineHeight + 4; }
10001 	override int minHeight() { return defaultLineHeight + 4; }
10002 }
10003 
10004 
10005 /**
10006 	Status bars appear at the bottom of a MainWindow.
10007 	They are made out of Parts, with a width and content.
10008 
10009 	They can have multiple parts or be in simple mode. FIXME: implement simple mode.
10010 
10011 
10012 	sb.parts[0].content = "Status bar text!";
10013 */
10014 class StatusBar : Widget {
10015 	private Part[] partsArray;
10016 	///
10017 	struct Parts {
10018 		@disable this();
10019 		this(StatusBar owner) { this.owner = owner; }
10020 		//@disable this(this);
10021 		///
10022 		@property int length() { return cast(int) owner.partsArray.length; }
10023 		private StatusBar owner;
10024 		private this(StatusBar owner, Part[] parts) {
10025 			this.owner.partsArray = parts;
10026 			this.owner = owner;
10027 		}
10028 		///
10029 		Part opIndex(int p) {
10030 			if(owner.partsArray.length == 0)
10031 				this ~= new StatusBar.Part(300);
10032 			return owner.partsArray[p];
10033 		}
10034 
10035 		///
10036 		Part opOpAssign(string op : "~" )(Part p) {
10037 			assert(owner.partsArray.length < 255);
10038 			p.owner = this.owner;
10039 			p.idx = cast(int) owner.partsArray.length;
10040 			owner.partsArray ~= p;
10041 			version(win32_widgets) {
10042 				int[256] pos;
10043 				int cpos = 0;
10044 				foreach(idx, part; owner.partsArray) {
10045 					if(part.width)
10046 						cpos += part.width;
10047 					else
10048 						cpos += 100;
10049 
10050 					if(idx + 1 == owner.partsArray.length)
10051 						pos[idx] = -1;
10052 					else
10053 						pos[idx] = cpos;
10054 				}
10055 				SendMessageW(owner.hwnd, WM_USER + 4 /*SB_SETPARTS*/, owner.partsArray.length, cast(size_t) pos.ptr);
10056 			} else version(custom_widgets) {
10057 				owner.redraw();
10058 			} else static assert(false);
10059 
10060 			return p;
10061 		}
10062 	}
10063 
10064 	private Parts _parts;
10065 	///
10066 	final @property Parts parts() {
10067 		return _parts;
10068 	}
10069 
10070 	///
10071 	static class Part {
10072 		int width;
10073 		StatusBar owner;
10074 
10075 		///
10076 		this(int w = 100) { width = w; }
10077 
10078 		private int idx;
10079 		private string _content;
10080 		///
10081 		@property string content() { return _content; }
10082 		///
10083 		@property void content(string s) {
10084 			version(win32_widgets) {
10085 				_content = s;
10086 				WCharzBuffer bfr = WCharzBuffer(s);
10087 				SendMessageW(owner.hwnd, SB_SETTEXT, idx, cast(LPARAM) bfr.ptr);
10088 			} else version(custom_widgets) {
10089 				if(_content != s) {
10090 					_content = s;
10091 					owner.redraw();
10092 				}
10093 			} else static assert(false);
10094 		}
10095 	}
10096 	string simpleModeContent;
10097 	bool inSimpleMode;
10098 
10099 
10100 	///
10101 	this(Widget parent) {
10102 		super(null); // FIXME
10103 		_parts = Parts(this);
10104 		tabStop = false;
10105 		version(win32_widgets) {
10106 			parentWindow = parent.parentWindow;
10107 			createWin32Window(this, "msctls_statusbar32"w, "", 0);
10108 
10109 			RECT rect;
10110 			GetWindowRect(hwnd, &rect);
10111 			idealHeight = rect.bottom - rect.top;
10112 			assert(idealHeight);
10113 		} else version(custom_widgets) {
10114 		} else static assert(false);
10115 	}
10116 
10117 	version(win32_widgets)
10118 	override protected void dpiChanged() {
10119 		RECT rect;
10120 		GetWindowRect(hwnd, &rect);
10121 		idealHeight = rect.bottom - rect.top;
10122 		assert(idealHeight);
10123 	}
10124 
10125 	version(custom_widgets)
10126 	override void paint(WidgetPainter painter) {
10127 		auto cs = getComputedStyle();
10128 		this.draw3dFrame(painter, FrameStyle.sunk, cs.background.color);
10129 		int cpos = 0;
10130 		int remainingLength = this.width;
10131 		foreach(idx, part; this.partsArray) {
10132 			auto partWidth = part.width ? part.width : ((idx + 1 == this.partsArray.length) ? remainingLength : 100);
10133 			painter.setClipRectangle(Point(cpos, 0), partWidth, height);
10134 			draw3dFrame(cpos, 0, partWidth, height, painter, FrameStyle.sunk, cs.background.color);
10135 			painter.setClipRectangle(Point(cpos + 2, 2), partWidth - 4, height - 4);
10136 
10137 			painter.outlineColor = cs.foregroundColor();
10138 			painter.fillColor = cs.foregroundColor();
10139 
10140 			painter.drawText(Point(cpos + 4, 0), part.content, Point(width, height), TextAlignment.VerticalCenter);
10141 			cpos += partWidth;
10142 			remainingLength -= partWidth;
10143 		}
10144 	}
10145 
10146 
10147 	version(win32_widgets) {
10148 		private int idealHeight;
10149 		override int maxHeight() { return idealHeight; }
10150 		override int minHeight() { return idealHeight; }
10151 	} else version(custom_widgets) {
10152 		override int maxHeight() { return defaultLineHeight + 4; }
10153 		override int minHeight() { return defaultLineHeight + 4; }
10154 	} else static assert(false);
10155 }
10156 
10157 /// Displays an in-progress indicator without known values
10158 version(none)
10159 class IndefiniteProgressBar : Widget {
10160 	version(win32_widgets)
10161 	this(Widget parent) {
10162 		super(parent);
10163 		createWin32Window(this, "msctls_progress32"w, "", 8 /* PBS_MARQUEE */);
10164 		tabStop = false;
10165 	}
10166 	override int minHeight() { return 10; }
10167 }
10168 
10169 /// A progress bar with a known endpoint and completion amount
10170 class ProgressBar : Widget {
10171 	/++
10172 		History:
10173 			Added March 16, 2022 (dub v10.7)
10174 	+/
10175 	this(int min, int max, Widget parent) {
10176 		this(parent);
10177 		setRange(cast(ushort) min, cast(ushort) max); // FIXME
10178 	}
10179 	this(Widget parent) {
10180 		version(win32_widgets) {
10181 			super(parent);
10182 			createWin32Window(this, "msctls_progress32"w, "", 0);
10183 			tabStop = false;
10184 		} else version(custom_widgets) {
10185 			super(parent);
10186 			max = 100;
10187 			step = 10;
10188 			tabStop = false;
10189 		} else static assert(0);
10190 	}
10191 
10192 	version(custom_widgets)
10193 	override void paint(WidgetPainter painter) {
10194 		auto cs = getComputedStyle();
10195 		this.draw3dFrame(painter, FrameStyle.sunk, cs.background.color);
10196 		painter.fillColor = cs.progressBarColor;
10197 		painter.drawRectangle(Point(0, 0), width * current / max, height);
10198 	}
10199 
10200 
10201 	version(custom_widgets) {
10202 		int current;
10203 		int max;
10204 		int step;
10205 	}
10206 
10207 	///
10208 	void advanceOneStep() {
10209 		version(win32_widgets)
10210 			SendMessageW(hwnd, PBM_STEPIT, 0, 0);
10211 		else version(custom_widgets)
10212 			addToPosition(step);
10213 		else static assert(false);
10214 	}
10215 
10216 	///
10217 	void setStepIncrement(int increment) {
10218 		version(win32_widgets)
10219 			SendMessageW(hwnd, PBM_SETSTEP, increment, 0);
10220 		else version(custom_widgets)
10221 			step = increment;
10222 		else static assert(false);
10223 	}
10224 
10225 	///
10226 	void addToPosition(int amount) {
10227 		version(win32_widgets)
10228 			SendMessageW(hwnd, PBM_DELTAPOS, amount, 0);
10229 		else version(custom_widgets)
10230 			setPosition(current + amount);
10231 		else static assert(false);
10232 	}
10233 
10234 	///
10235 	void setPosition(int pos) {
10236 		version(win32_widgets)
10237 			SendMessageW(hwnd, PBM_SETPOS, pos, 0);
10238 		else version(custom_widgets) {
10239 			current = pos;
10240 			if(current > max)
10241 				current = max;
10242 			redraw();
10243 		}
10244 		else static assert(false);
10245 	}
10246 
10247 	///
10248 	void setRange(ushort min, ushort max) {
10249 		version(win32_widgets)
10250 			SendMessageW(hwnd, PBM_SETRANGE, 0, MAKELONG(min, max));
10251 		else version(custom_widgets) {
10252 			this.max = max;
10253 		}
10254 		else static assert(false);
10255 	}
10256 
10257 	override int minHeight() { return 10; }
10258 }
10259 
10260 version(custom_widgets)
10261 private void extractWindowsStyleLabel(scope const char[] label, out string thisLabel, out dchar thisAccelerator) {
10262 	thisLabel.reserve(label.length);
10263 	bool justSawAmpersand;
10264 	foreach(ch; label) {
10265 		if(justSawAmpersand) {
10266 			justSawAmpersand = false;
10267 			if(ch == '&') {
10268 				goto plain;
10269 			}
10270 			thisAccelerator = ch;
10271 		} else {
10272 			if(ch == '&') {
10273 				justSawAmpersand = true;
10274 				continue;
10275 			}
10276 			plain:
10277 			thisLabel ~= ch;
10278 		}
10279 	}
10280 }
10281 
10282 /++
10283 	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.
10284 
10285 
10286 	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
10287 
10288 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
10289 
10290 	History:
10291 		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.
10292 +/
10293 class Fieldset : Widget {
10294 	// FIXME: on Windows,it doesn't draw the background on the label
10295 	// on X, it doesn't fix the clipping rectangle for it
10296 	version(win32_widgets)
10297 		override int paddingTop() { return defaultLineHeight; }
10298 	else version(custom_widgets)
10299 		override int paddingTop() { return defaultLineHeight + 2; }
10300 	else static assert(false);
10301 	override int paddingBottom() { return 6; }
10302 	override int paddingLeft() { return 6; }
10303 	override int paddingRight() { return 6; }
10304 
10305 	override int marginLeft() { return 6; }
10306 	override int marginRight() { return 6; }
10307 	override int marginTop() { return 2; }
10308 	override int marginBottom() { return 2; }
10309 
10310 	string legend;
10311 
10312 	version(custom_widgets) private dchar accelerator;
10313 
10314 	this(string legend, Widget parent) {
10315 		version(win32_widgets) {
10316 			super(parent);
10317 			this.legend = legend;
10318 			createWin32Window(this, "button"w, legend, BS_GROUPBOX);
10319 			tabStop = false;
10320 		} else version(custom_widgets) {
10321 			super(parent);
10322 			tabStop = false;
10323 
10324 			legend.extractWindowsStyleLabel(this.legend, this.accelerator);
10325 		} else static assert(0);
10326 	}
10327 
10328 	version(custom_widgets)
10329 	override void paint(WidgetPainter painter) {
10330 		painter.fillColor = Color.transparent;
10331 		auto cs = getComputedStyle();
10332 		painter.pen = Pen(cs.foregroundColor, 1);
10333 		painter.drawRectangle(Point(0, defaultLineHeight / 2), width, height - Window.lineHeight / 2);
10334 
10335 		auto tx = painter.textSize(legend);
10336 		painter.outlineColor = Color.transparent;
10337 
10338 		static if(UsingSimpledisplayX11) {
10339 			painter.fillColor = getComputedStyle().windowBackgroundColor;
10340 			painter.drawRectangle(Point(8, 0), tx.width, tx.height);
10341 		} else version(Windows) {
10342 			auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
10343 			painter.drawRectangle(Point(8, -tx.height/2), tx.width, tx.height);
10344 			SelectObject(painter.impl.hdc, b);
10345 		} else static assert(0);
10346 		painter.outlineColor = cs.foregroundColor;
10347 		painter.drawText(Point(8, 0), legend);
10348 	}
10349 
10350 	override int maxHeight() {
10351 		auto m = paddingTop() + paddingBottom();
10352 		foreach(child; children) {
10353 			auto mh = child.maxHeight();
10354 			if(mh == int.max)
10355 				return int.max;
10356 			m += mh;
10357 			m += child.marginBottom();
10358 			m += child.marginTop();
10359 		}
10360 		m += 6;
10361 		if(m < minHeight)
10362 			return minHeight;
10363 		return m;
10364 	}
10365 
10366 	override int minHeight() {
10367 		auto m = paddingTop() + paddingBottom();
10368 		foreach(child; children) {
10369 			m += child.minHeight();
10370 			m += child.marginBottom();
10371 			m += child.marginTop();
10372 		}
10373 		return m + 6;
10374 	}
10375 }
10376 
10377 /++
10378 	$(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")
10379 	$(IMG //arsdnet.net/minigui-screenshots/linux/Fieldset.png, Same thing, but in the default Linux theme.)
10380 +/
10381 version(minigui_screenshots)
10382 @Screenshot("Fieldset")
10383 unittest {
10384 	auto window = new Window(200, 100);
10385 	auto set = new Fieldset("Baby will", window);
10386 	auto option1 = new Radiobox("Eat", set);
10387 	auto option2 = new Radiobox("Cry", set);
10388 	auto option3 = new Radiobox("Sleep", set);
10389 	window.loop();
10390 }
10391 
10392 /// Draws a line
10393 class HorizontalRule : Widget {
10394 	mixin Margin!q{ 2 };
10395 	override int minHeight() { return 2; }
10396 	override int maxHeight() { return 2; }
10397 
10398 	///
10399 	this(Widget parent) {
10400 		super(parent);
10401 	}
10402 
10403 	override void paint(WidgetPainter painter) {
10404 		auto cs = getComputedStyle();
10405 		painter.outlineColor = cs.darkAccentColor;
10406 		painter.drawLine(Point(0, 0), Point(width, 0));
10407 		painter.outlineColor = cs.lightAccentColor;
10408 		painter.drawLine(Point(0, 1), Point(width, 1));
10409 	}
10410 }
10411 
10412 version(minigui_screenshots)
10413 @Screenshot("HorizontalRule")
10414 /++
10415 	$(IMG //arsdnet.net/minigui-screenshots/linux/HorizontalRule.png, Same thing, but in the default Linux theme.)
10416 
10417 +/
10418 unittest {
10419 	auto window = new Window(200, 100);
10420 	auto above = new TextLabel("Above the line", TextAlignment.Left, window);
10421 	new HorizontalRule(window);
10422 	auto below = new TextLabel("Below the line", TextAlignment.Left, window);
10423 	window.loop();
10424 }
10425 
10426 /// ditto
10427 class VerticalRule : Widget {
10428 	mixin Margin!q{ 2 };
10429 	override int minWidth() { return 2; }
10430 	override int maxWidth() { return 2; }
10431 
10432 	///
10433 	this(Widget parent) {
10434 		super(parent);
10435 	}
10436 
10437 	override void paint(WidgetPainter painter) {
10438 		auto cs = getComputedStyle();
10439 		painter.outlineColor = cs.darkAccentColor;
10440 		painter.drawLine(Point(0, 0), Point(0, height));
10441 		painter.outlineColor = cs.lightAccentColor;
10442 		painter.drawLine(Point(1, 0), Point(1, height));
10443 	}
10444 }
10445 
10446 
10447 ///
10448 class Menu : Window {
10449 	void remove() {
10450 		foreach(i, child; parentWindow.children)
10451 			if(child is this) {
10452 				parentWindow._children = parentWindow._children[0 .. i] ~ parentWindow._children[i + 1 .. $];
10453 				break;
10454 			}
10455 		parentWindow.redraw();
10456 
10457 		parentWindow.releaseMouseCapture();
10458 	}
10459 
10460 	///
10461 	void addSeparator() {
10462 		version(win32_widgets)
10463 			AppendMenu(handle, MF_SEPARATOR, 0, null);
10464 		else version(custom_widgets)
10465 			auto hr = new HorizontalRule(this);
10466 		else static assert(0);
10467 	}
10468 
10469 	override int paddingTop() { return 4; }
10470 	override int paddingBottom() { return 4; }
10471 	override int paddingLeft() { return 2; }
10472 	override int paddingRight() { return 2; }
10473 
10474 	version(win32_widgets) {}
10475 	else version(custom_widgets) {
10476 		SimpleWindow dropDown;
10477 		Widget menuParent;
10478 		void popup(Widget parent, int offsetX = 0, int offsetY = int.min) {
10479 			this.menuParent = parent;
10480 
10481 			int w = 150;
10482 			int h = paddingTop + paddingBottom;
10483 			if(this.children.length) {
10484 				// hacking it to get the ideal height out of recomputeChildLayout
10485 				this.width = w;
10486 				this.height = h;
10487 				this.recomputeChildLayout();
10488 				h = this.children[$-1].y + this.children[$-1].height + this.children[$-1].marginBottom;
10489 				h += paddingBottom;
10490 
10491 				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
10492 			}
10493 
10494 			if(offsetY == int.min)
10495 				offsetY = parent.defaultLineHeight;
10496 
10497 			auto coord = parent.globalCoordinates();
10498 			dropDown.moveResize(coord.x + offsetX, coord.y + offsetY, w, h);
10499 			this.x = 0;
10500 			this.y = 0;
10501 			this.width = dropDown.width;
10502 			this.height = dropDown.height;
10503 			this.drawableWindow = dropDown;
10504 			this.recomputeChildLayout();
10505 
10506 			static if(UsingSimpledisplayX11)
10507 				XSync(XDisplayConnection.get, 0);
10508 
10509 			dropDown.visibilityChanged = (bool visible) {
10510 				if(visible) {
10511 					this.redraw();
10512 					dropDown.grabInput();
10513 				} else {
10514 					dropDown.releaseInputGrab();
10515 				}
10516 			};
10517 
10518 			dropDown.show();
10519 
10520 			clickListener = this.addEventListener((scope ClickEvent ev) {
10521 				unpopup();
10522 				// need to unlock asap just in case other user handlers block...
10523 				static if(UsingSimpledisplayX11)
10524 					flushGui();
10525 			}, true /* again for asap action */);
10526 		}
10527 
10528 		EventListener clickListener;
10529 	}
10530 	else static assert(false);
10531 
10532 	version(custom_widgets)
10533 	void unpopup() {
10534 		mouseLastOver = mouseLastDownOn = null;
10535 		dropDown.hide();
10536 		if(!menuParent.parentWindow.win.closed) {
10537 			if(auto maw = cast(MouseActivatedWidget) menuParent) {
10538 				maw.setDynamicState(DynamicState.depressed, false);
10539 				maw.setDynamicState(DynamicState.hover, false);
10540 				maw.redraw();
10541 			}
10542 			// menuParent.parentWindow.win.focus();
10543 		}
10544 		clickListener.disconnect();
10545 	}
10546 
10547 	MenuItem[] items;
10548 
10549 	///
10550 	MenuItem addItem(MenuItem item) {
10551 		addChild(item);
10552 		items ~= item;
10553 		version(win32_widgets) {
10554 			AppendMenuW(handle, MF_STRING, item.action is null ? 9000 : item.action.id, toWstringzInternal(item.label));
10555 		}
10556 		return item;
10557 	}
10558 
10559 	string label;
10560 
10561 	version(win32_widgets) {
10562 		HMENU handle;
10563 		///
10564 		this(string label, Widget parent) {
10565 			// not actually passing the parent since it effs up the drawing
10566 			super(cast(Widget) null);// parent);
10567 			this.label = label;
10568 			handle = CreatePopupMenu();
10569 		}
10570 	} else version(custom_widgets) {
10571 		///
10572 		this(string label, Widget parent) {
10573 
10574 			if(dropDown) {
10575 				dropDown.close();
10576 			}
10577 			dropDown = new SimpleWindow(
10578 				150, 4,
10579 				null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.dropdownMenu, WindowFlags.dontAutoShow, parent ? parent.parentWindow.win : null);
10580 
10581 			this.label = label;
10582 
10583 			super(dropDown);
10584 		}
10585 	} else static assert(false);
10586 
10587 	override int maxHeight() { return defaultLineHeight; }
10588 	override int minHeight() { return defaultLineHeight; }
10589 
10590 	version(custom_widgets)
10591 	override void paint(WidgetPainter painter) {
10592 		this.draw3dFrame(painter, FrameStyle.risen, getComputedStyle.background.color);
10593 	}
10594 }
10595 
10596 /++
10597 	A MenuItem belongs to a [Menu] - use [Menu.addItem] to add one - and calls an [Action] when it is clicked.
10598 +/
10599 class MenuItem : MouseActivatedWidget {
10600 	Menu submenu;
10601 
10602 	Action action;
10603 	string label;
10604 
10605 	override int paddingLeft() { return 4; }
10606 
10607 	override int maxHeight() { return defaultLineHeight + 4; }
10608 	override int minHeight() { return defaultLineHeight + 4; }
10609 	override int minWidth() { return defaultTextWidth(label) + 8 + scaleWithDpi(12); }
10610 	override int maxWidth() {
10611 		if(cast(MenuBar) parent) {
10612 			return minWidth();
10613 		}
10614 		return int.max;
10615 	}
10616 	/// This should ONLY be used if there is no associated action, for example, if the menu item is just a submenu.
10617 	this(string lbl, Widget parent = null) {
10618 		super(parent);
10619 		//label = lbl; // FIXME
10620 		foreach(char ch; lbl) // FIXME
10621 			if(ch != '&') // FIXME
10622 				label ~= ch; // FIXME
10623 		tabStop = false; // these are selected some other way
10624 	}
10625 
10626 	///
10627 	this(Action action, Widget parent = null) {
10628 		assert(action !is null);
10629 		this(action.label, parent);
10630 		this.action = action;
10631 		tabStop = false; // these are selected some other way
10632 	}
10633 
10634 	version(custom_widgets)
10635 	override void paint(WidgetPainter painter) {
10636 		auto cs = getComputedStyle();
10637 		if(dynamicState & DynamicState.depressed)
10638 			this.draw3dFrame(painter, FrameStyle.sunk, cs.background.color);
10639 		if(dynamicState & DynamicState.hover)
10640 			painter.outlineColor = cs.activeMenuItemColor;
10641 		else
10642 			painter.outlineColor = cs.foregroundColor;
10643 		painter.fillColor = Color.transparent;
10644 		painter.drawText(scaleWithDpi(Point(cast(MenuBar) this.parent ? 4 : 20, 0)), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
10645 		if(action && action.accelerator !is KeyEvent.init) {
10646 			painter.drawText(scaleWithDpi(Point(cast(MenuBar) this.parent ? 4 : 20, 0)), action.accelerator.toStr(), Point(width - 4, height), TextAlignment.Right | TextAlignment.VerticalCenter);
10647 
10648 		}
10649 	}
10650 
10651 	static class Style : Widget.Style {
10652 		override bool variesWithState(ulong dynamicStateFlags) {
10653 			return super.variesWithState(dynamicStateFlags) || (dynamicStateFlags & (DynamicState.depressed | DynamicState.hover));
10654 		}
10655 	}
10656 	mixin OverrideStyle!Style;
10657 
10658 	override void defaultEventHandler_triggered(Event event) {
10659 		if(action)
10660 		foreach(handler; action.triggered)
10661 			handler();
10662 
10663 		if(auto pmenu = cast(Menu) this.parent)
10664 			pmenu.remove();
10665 
10666 		super.defaultEventHandler_triggered(event);
10667 	}
10668 }
10669 
10670 version(win32_widgets)
10671 /// A "mouse activiated widget" is really just an abstract variant of button.
10672 class MouseActivatedWidget : Widget {
10673 	@property bool isChecked() {
10674 		assert(hwnd);
10675 		return SendMessageW(hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED;
10676 
10677 	}
10678 	@property void isChecked(bool state) {
10679 		assert(hwnd);
10680 		SendMessageW(hwnd, BM_SETCHECK, state ? BST_CHECKED : BST_UNCHECKED, 0);
10681 
10682 	}
10683 
10684 	override void handleWmCommand(ushort cmd, ushort id) {
10685 		if(cmd == 0) {
10686 			auto event = new Event(EventType.triggered, this);
10687 			event.dispatch();
10688 		}
10689 	}
10690 
10691 	this(Widget parent) {
10692 		super(parent);
10693 	}
10694 }
10695 else version(custom_widgets)
10696 /// ditto
10697 class MouseActivatedWidget : Widget {
10698 	@property bool isChecked() { return isChecked_; }
10699 	@property bool isChecked(bool b) { return isChecked_ = b; }
10700 
10701 	private bool isChecked_;
10702 
10703 	this(Widget parent) {
10704 		super(parent);
10705 
10706 		addEventListener((MouseDownEvent ev) {
10707 			if(ev.button == MouseButton.left) {
10708 				setDynamicState(DynamicState.depressed, true);
10709 				setDynamicState(DynamicState.hover, true);
10710 				redraw();
10711 			}
10712 		});
10713 
10714 		addEventListener((MouseUpEvent ev) {
10715 			if(ev.button == MouseButton.left) {
10716 				setDynamicState(DynamicState.depressed, false);
10717 				setDynamicState(DynamicState.hover, false);
10718 				redraw();
10719 			}
10720 		});
10721 
10722 		addEventListener((MouseMoveEvent mme) {
10723 			if(!(mme.state & ModifierState.leftButtonDown)) {
10724 				if(dynamicState_ & DynamicState.depressed) {
10725 					setDynamicState(DynamicState.depressed, false);
10726 					redraw();
10727 				}
10728 			}
10729 		});
10730 	}
10731 
10732 	override void defaultEventHandler_focus(Event ev) {
10733 		super.defaultEventHandler_focus(ev);
10734 		this.redraw();
10735 	}
10736 	override void defaultEventHandler_blur(Event ev) {
10737 		super.defaultEventHandler_blur(ev);
10738 		setDynamicState(DynamicState.depressed, false);
10739 		this.redraw();
10740 	}
10741 	override void defaultEventHandler_keydown(KeyDownEvent ev) {
10742 		super.defaultEventHandler_keydown(ev);
10743 		if(ev.key == Key.Space || ev.key == Key.Enter || ev.key == Key.PadEnter) {
10744 			setDynamicState(DynamicState.depressed, true);
10745 			setDynamicState(DynamicState.hover, true);
10746 			this.redraw();
10747 		}
10748 	}
10749 	override void defaultEventHandler_keyup(KeyUpEvent ev) {
10750 		super.defaultEventHandler_keyup(ev);
10751 		if(!(dynamicState & DynamicState.depressed))
10752 			return;
10753 		setDynamicState(DynamicState.depressed, false);
10754 		setDynamicState(DynamicState.hover, false);
10755 		this.redraw();
10756 
10757 		auto event = new Event(EventType.triggered, this);
10758 		event.sendDirectly();
10759 	}
10760 	override void defaultEventHandler_click(ClickEvent ev) {
10761 		super.defaultEventHandler_click(ev);
10762 		if(ev.button == MouseButton.left) {
10763 			auto event = new Event(EventType.triggered, this);
10764 			event.sendDirectly();
10765 		}
10766 	}
10767 
10768 }
10769 else static assert(false);
10770 
10771 /*
10772 /++
10773 	Like the tablet thing, it would have a label, a description, and a switch slider thingy.
10774 
10775 	Basically the same as a checkbox.
10776 +/
10777 class OnOffSwitch : MouseActivatedWidget {
10778 
10779 }
10780 */
10781 
10782 /++
10783 	History:
10784 		Added June 15, 2021 (dub v10.1)
10785 +/
10786 struct ImageLabel {
10787 	/++
10788 		Defines a label+image combo used by some widgets.
10789 		
10790 		If you provide just a text label, that is all the widget will try to
10791 		display. Or just an image will display just that. If you provide both,
10792 		it may display both text and image side by side or display the image
10793 		and offer text on an input event depending on the widget.
10794 
10795 		History:
10796 			The `alignment` parameter was added on September 27, 2021
10797 	+/
10798 	this(string label, TextAlignment alignment = TextAlignment.Center) {
10799 		this.label = label;
10800 		this.displayFlags = DisplayFlags.displayText;
10801 		this.alignment = alignment;
10802 	}
10803 
10804 	/// ditto
10805 	this(string label, MemoryImage image, TextAlignment alignment = TextAlignment.Center) {
10806 		this.label = label;
10807 		this.image = image;
10808 		this.displayFlags = DisplayFlags.displayText | DisplayFlags.displayImage;
10809 		this.alignment = alignment;
10810 	}
10811 
10812 	/// ditto
10813 	this(MemoryImage image, TextAlignment alignment = TextAlignment.Center) {
10814 		this.image = image;
10815 		this.displayFlags = DisplayFlags.displayImage;
10816 		this.alignment = alignment;
10817 	}
10818 
10819 	/// ditto
10820 	this(string label, MemoryImage image, int displayFlags, TextAlignment alignment = TextAlignment.Center) {
10821 		this.label = label;
10822 		this.image = image;
10823 		this.alignment = alignment;
10824 		this.displayFlags = displayFlags;
10825 	}
10826 
10827 	string label;
10828 	MemoryImage image;
10829 
10830 	enum DisplayFlags {
10831 		displayText = 1 << 0,
10832 		displayImage = 1 << 1,
10833 	}
10834 
10835 	int displayFlags = DisplayFlags.displayText | DisplayFlags.displayImage;
10836 
10837 	TextAlignment alignment;
10838 }
10839 
10840 /++
10841 	A basic checked or not checked box with an attached label.
10842 
10843 
10844 	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
10845 
10846 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
10847 
10848 	History:
10849 		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.
10850 +/
10851 class Checkbox : MouseActivatedWidget {
10852 	version(win32_widgets) {
10853 		override int maxHeight() { return scaleWithDpi(16); }
10854 		override int minHeight() { return scaleWithDpi(16); }
10855 	} else version(custom_widgets) {
10856 		override int maxHeight() { return defaultLineHeight; }
10857 		override int minHeight() { return defaultLineHeight; }
10858 	} else static assert(0);
10859 
10860 	override int marginLeft() { return 4; }
10861 
10862 	override int flexBasisWidth() { return 24 + cast(int) label.length * 7; }
10863 
10864 	/++
10865 		Just an alias because I keep typing checked out of web habit.
10866 
10867 		History:
10868 			Added May 31, 2021
10869 	+/
10870 	alias checked = isChecked;
10871 
10872 	private string label;
10873 	private dchar accelerator;
10874 
10875 	/++
10876 	+/
10877 	this(string label, Widget parent) {
10878 		this(ImageLabel(label), Appearance.checkbox, parent);
10879 	}
10880 
10881 	/// ditto
10882 	this(string label, Appearance appearance, Widget parent) {
10883 		this(ImageLabel(label), appearance, parent);
10884 	}
10885 
10886 	/++
10887 		Changes the look and may change the ideal size of the widget without changing its behavior. The precise look is platform-specific.
10888 
10889 		History:
10890 			Added June 29, 2021 (dub v10.2)
10891 	+/
10892 	enum Appearance {
10893 		checkbox, /// a normal checkbox
10894 		pushbutton, /// a button that is showed as pushed when checked and up when unchecked. Similar to the bold button in a toolbar in Wordpad.
10895 		//sliderswitch,
10896 	}
10897 	private Appearance appearance;
10898 
10899 	/// ditto
10900 	private this(ImageLabel label, Appearance appearance, Widget parent) {
10901 		super(parent);
10902 		version(win32_widgets) {
10903 			this.label = label.label;
10904 
10905 			uint extraStyle;
10906 			final switch(appearance) {
10907 				case Appearance.checkbox:
10908 				break;
10909 				case Appearance.pushbutton:
10910 					extraStyle |= BS_PUSHLIKE;
10911 				break;
10912 			}
10913 
10914 			createWin32Window(this, "button"w, label.label, BS_CHECKBOX | extraStyle);
10915 		} else version(custom_widgets) {
10916 			label.label.extractWindowsStyleLabel(this.label, this.accelerator);
10917 		} else static assert(0);
10918 	}
10919 
10920 	version(custom_widgets)
10921 	override void paint(WidgetPainter painter) {
10922 		auto cs = getComputedStyle();
10923 		if(isFocused()) {
10924 			painter.pen = Pen(Color.black, 1, Pen.Style.Dotted);
10925 			painter.fillColor = cs.windowBackgroundColor;
10926 			painter.drawRectangle(Point(0, 0), width, height);
10927 			painter.pen = Pen(Color.black, 1, Pen.Style.Solid);
10928 		} else {
10929 			painter.pen = Pen(cs.windowBackgroundColor, 1, Pen.Style.Solid);
10930 			painter.fillColor = cs.windowBackgroundColor;
10931 			painter.drawRectangle(Point(0, 0), width, height);
10932 		}
10933 
10934 
10935 		enum buttonSize = 16;
10936 
10937 		painter.outlineColor = Color.black;
10938 		painter.fillColor = Color.white;
10939 		painter.drawRectangle(scaleWithDpi(Point(2, 2)), scaleWithDpi(buttonSize - 2), scaleWithDpi(buttonSize - 2));
10940 
10941 		if(isChecked) {
10942 			painter.pen = Pen(Color.black, 2);
10943 			// I'm using height so the checkbox is square
10944 			enum padding = 5;
10945 			painter.drawLine(scaleWithDpi(Point(padding, padding)), scaleWithDpi(Point(buttonSize - (padding-2), buttonSize - (padding-2))));
10946 			painter.drawLine(scaleWithDpi(Point(buttonSize-(padding-2), padding)), scaleWithDpi(Point(padding, buttonSize - (padding-2))));
10947 
10948 			painter.pen = Pen(Color.black, 1);
10949 		}
10950 
10951 		if(label !is null) {
10952 			painter.outlineColor = cs.foregroundColor();
10953 			painter.fillColor = cs.foregroundColor();
10954 
10955 			// FIXME: should prolly just align the baseline or something
10956 			painter.drawText(scaleWithDpi(Point(buttonSize + 4, 2)), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
10957 		}
10958 	}
10959 
10960 	override void defaultEventHandler_triggered(Event ev) {
10961 		isChecked = !isChecked;
10962 
10963 		this.emit!(ChangeEvent!bool)(&isChecked);
10964 
10965 		redraw();
10966 	}
10967 
10968 	/// Emits a change event with the checked state
10969 	mixin Emits!(ChangeEvent!bool);
10970 }
10971 
10972 /// Adds empty space to a layout.
10973 class VerticalSpacer : Widget {
10974 	///
10975 	this(Widget parent) {
10976 		super(parent);
10977 	}
10978 }
10979 
10980 /// ditto
10981 class HorizontalSpacer : Widget {
10982 	///
10983 	this(Widget parent) {
10984 		super(parent);
10985 		this.tabStop = false;
10986 	}
10987 }
10988 
10989 
10990 /++
10991 	Creates a radio button with an associated label. These are usually put inside a [Fieldset].
10992 
10993 
10994 	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
10995 
10996 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
10997 
10998 	History:
10999 		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.
11000 +/
11001 class Radiobox : MouseActivatedWidget {
11002 
11003 	version(win32_widgets) {
11004 		override int maxHeight() { return scaleWithDpi(16); }
11005 		override int minHeight() { return scaleWithDpi(16); }
11006 	} else version(custom_widgets) {
11007 		override int maxHeight() { return defaultLineHeight; }
11008 		override int minHeight() { return defaultLineHeight; }
11009 	} else static assert(0);
11010 
11011 	override int marginLeft() { return 4; }
11012 
11013 	private string label;
11014 	private dchar accelerator;
11015 
11016 	version(win32_widgets)
11017 	this(string label, Widget parent) {
11018 		super(parent);
11019 		this.label = label;
11020 		createWin32Window(this, "button"w, label, BS_AUTORADIOBUTTON);
11021 	}
11022 	else version(custom_widgets)
11023 	this(string label, Widget parent) {
11024 		super(parent);
11025 		label.extractWindowsStyleLabel(this.label, this.accelerator);
11026 		height = 16;
11027 		width = height + 4 + cast(int) label.length * 16;
11028 	}
11029 	else static assert(false);
11030 
11031 	version(custom_widgets)
11032 	override void paint(WidgetPainter painter) {
11033 		auto cs = getComputedStyle();
11034 		if(isFocused) {
11035 			painter.fillColor = cs.windowBackgroundColor;
11036 			painter.pen = Pen(Color.black, 1, Pen.Style.Dotted);
11037 		} else {
11038 			painter.fillColor = cs.windowBackgroundColor;
11039 			painter.outlineColor = cs.windowBackgroundColor;
11040 		}
11041 		painter.drawRectangle(Point(0, 0), width, height);
11042 
11043 		painter.pen = Pen(Color.black, 1, Pen.Style.Solid);
11044 
11045 		enum buttonSize = 16;
11046 
11047 		painter.outlineColor = Color.black;
11048 		painter.fillColor = Color.white;
11049 		painter.drawEllipse(scaleWithDpi(Point(2, 2)), scaleWithDpi(Point(buttonSize - 2, buttonSize - 2)));
11050 		if(isChecked) {
11051 			painter.outlineColor = Color.black;
11052 			painter.fillColor = Color.black;
11053 			// I'm using height so the checkbox is square
11054 			painter.drawEllipse(scaleWithDpi(Point(5, 5)), scaleWithDpi(Point(buttonSize - 5, buttonSize - 5)));
11055 		}
11056 
11057 		painter.outlineColor = cs.foregroundColor();
11058 		painter.fillColor = cs.foregroundColor();
11059 
11060 		painter.drawText(scaleWithDpi(Point(buttonSize + 4, 0)), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
11061 	}
11062 
11063 
11064 	override void defaultEventHandler_triggered(Event ev) {
11065 		isChecked = true;
11066 
11067 		if(this.parent) {
11068 			foreach(child; this.parent.children) {
11069 				if(child is this) continue;
11070 				if(auto rb = cast(Radiobox) child) {
11071 					rb.isChecked = false;
11072 					rb.emit!(ChangeEvent!bool)(&rb.isChecked);
11073 					rb.redraw();
11074 				}
11075 			}
11076 		}
11077 
11078 		this.emit!(ChangeEvent!bool)(&this.isChecked);
11079 
11080 		redraw();
11081 	}
11082 
11083 	/// 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.
11084 	mixin Emits!(ChangeEvent!bool);
11085 }
11086 
11087 
11088 /++
11089 	Creates a push button with unbounded size. When it is clicked, it emits a `triggered` event.
11090 
11091 
11092 	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
11093 
11094 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
11095 
11096 	History:
11097 		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.
11098 +/
11099 class Button : MouseActivatedWidget {
11100 	override int heightStretchiness() { return 3; }
11101 	override int widthStretchiness() { return 3; }
11102 
11103 	/++
11104 		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.
11105 
11106 		History:
11107 			Added July 2, 2021
11108 	+/
11109 	public bool triggersOnMultiClick;
11110 
11111 	private string label_;
11112 	private TextAlignment alignment;
11113 	private dchar accelerator;
11114 
11115 	///
11116 	string label() { return label_; }
11117 	///
11118 	void label(string l) {
11119 		label_ = l;
11120 		version(win32_widgets) {
11121 			WCharzBuffer bfr = WCharzBuffer(l);
11122 			SetWindowTextW(hwnd, bfr.ptr);
11123 		} else version(custom_widgets) {
11124 			redraw();
11125 		}
11126 	}
11127 
11128 	override void defaultEventHandler_dblclick(DoubleClickEvent ev) {
11129 		super.defaultEventHandler_dblclick(ev);
11130 		if(triggersOnMultiClick) {
11131 			if(ev.button == MouseButton.left) {
11132 				auto event = new Event(EventType.triggered, this);
11133 				event.sendDirectly();
11134 			}
11135 		}
11136 	}
11137 
11138 	private Sprite sprite;
11139 	private int displayFlags;
11140 
11141 	/++
11142 		Creates a push button with the given label, which may be an image or some text.
11143 
11144 		Bugs:
11145 			If the image is bigger than the button, it may not be displayed in the right position on Linux.
11146 
11147 		History:
11148 			The [ImageLabel] overload was added on June 21, 2021 (dub v10.1).
11149 
11150 			The button with label and image will respect requests to show both on Windows as
11151 			of March 28, 2022 iff you provide a manifest file to opt into common controls v6.
11152 	+/
11153 	this(ImageLabel label, Widget parent) {
11154 		version(win32_widgets) {
11155 			// FIXME: use ideal button size instead
11156 			width = 50;
11157 			height = 30;
11158 			super(parent);
11159 
11160 			// BS_BITMAP is set when we want image only, so checking for exactly that combination
11161 			enum imgFlags = ImageLabel.DisplayFlags.displayImage | ImageLabel.DisplayFlags.displayText;
11162 			auto extraStyle = ((label.displayFlags & imgFlags) == ImageLabel.DisplayFlags.displayImage) ? BS_BITMAP : 0;
11163 
11164 			// the transparent thing can mess up borders in other cases, so only going to keep it for bitmap things where it might matter
11165 			createWin32Window(this, "button"w, label.label, BS_PUSHBUTTON | extraStyle, extraStyle == BS_BITMAP ? WS_EX_TRANSPARENT : 0 );
11166 
11167 			if(label.image) {
11168 				sprite = Sprite.fromMemoryImage(parentWindow.win, label.image, true);
11169 
11170 				SendMessageW(hwnd, BM_SETIMAGE, IMAGE_BITMAP, cast(LPARAM) sprite.nativeHandle);
11171 			}
11172 
11173 			this.label = label.label;
11174 		} else version(custom_widgets) {
11175 			width = 50;
11176 			height = 30;
11177 			super(parent);
11178 
11179 			label.label.extractWindowsStyleLabel(this.label_, this.accelerator);
11180 
11181 			if(label.image) {
11182 				this.sprite = Sprite.fromMemoryImage(parentWindow.win, label.image);
11183 				this.displayFlags = label.displayFlags;
11184 			}
11185 
11186 			this.alignment = label.alignment;
11187 		}
11188 	}
11189 
11190 	///
11191 	this(string label, Widget parent) {
11192 		this(ImageLabel(label), parent);
11193 	}
11194 
11195 	override int minHeight() { return defaultLineHeight + 4; }
11196 
11197 	static class Style : Widget.Style {
11198 		override WidgetBackground background() {
11199 			auto cs = widget.getComputedStyle(); // FIXME: this is potentially recursive
11200 
11201 			auto pressed = DynamicState.depressed | DynamicState.hover;
11202 			if((widget.dynamicState & pressed) == pressed) {
11203 				return WidgetBackground(cs.depressedButtonColor());
11204 			} else if(widget.dynamicState & DynamicState.hover) {
11205 				return WidgetBackground(cs.hoveringColor());
11206 			} else {
11207 				return WidgetBackground(cs.buttonColor());
11208 			}
11209 		}
11210 
11211 		override FrameStyle borderStyle() {
11212 			auto pressed = DynamicState.depressed | DynamicState.hover;
11213 			if((widget.dynamicState & pressed) == pressed) {
11214 				return FrameStyle.sunk;
11215 			} else {
11216 				return FrameStyle.risen;
11217 			}
11218 
11219 		}
11220 
11221 		override bool variesWithState(ulong dynamicStateFlags) {
11222 			return super.variesWithState(dynamicStateFlags) || (dynamicStateFlags & (DynamicState.depressed | DynamicState.hover));
11223 		}
11224 	}
11225 	mixin OverrideStyle!Style;
11226 
11227 	version(custom_widgets)
11228 	override void paint(WidgetPainter painter) {
11229 		painter.drawThemed(delegate Rectangle(const Rectangle bounds) {
11230 			if(sprite) {
11231 				sprite.drawAt(
11232 					painter,
11233 					bounds.upperLeft + Point((bounds.width - sprite.width) / 2, (bounds.height - sprite.height) / 2),
11234 					Point(0, 0),
11235 					bounds.size
11236 				);
11237 			} else {
11238 				painter.drawText(bounds.upperLeft, label, bounds.lowerRight, alignment | TextAlignment.VerticalCenter);
11239 			}
11240 			return bounds;
11241 		});
11242 	}
11243 
11244 	override int flexBasisWidth() {
11245 		version(win32_widgets) {
11246 			SIZE size;
11247 			SendMessage(hwnd, BCM_GETIDEALSIZE, 0, cast(LPARAM) &size);
11248 			if(size.cx == 0)
11249 				goto fallback;
11250 			return size.cx + scaleWithDpi(16);
11251 		}
11252 		fallback:
11253 			return scaleWithDpi(cast(int) label.length * 8 + 16);
11254 	}
11255 
11256 	override int flexBasisHeight() {
11257 		version(win32_widgets) {
11258 			SIZE size;
11259 			SendMessage(hwnd, BCM_GETIDEALSIZE, 0, cast(LPARAM) &size);
11260 			if(size.cy == 0)
11261 				goto fallback;
11262 			return size.cy + scaleWithDpi(6);
11263 		}
11264 		fallback:
11265 			return defaultLineHeight + 4;
11266 	}
11267 }
11268 
11269 /++
11270 	A button with a consistent size, suitable for user commands like OK and cANCEL.
11271 +/
11272 class CommandButton : Button {
11273 	this(string label, Widget parent) {
11274 		super(label, parent);
11275 	}
11276 
11277 	// FIXME: I think I can simply make this 0 stretchiness instead of max now that the flex basis is there
11278 
11279 	override int maxHeight() {
11280 		return defaultLineHeight + 4;
11281 	}
11282 
11283 	override int maxWidth() {
11284 		return defaultLineHeight * 4;
11285 	}
11286 
11287 	override int marginLeft() { return 12; }
11288 	override int marginRight() { return 12; }
11289 	override int marginTop() { return 12; }
11290 	override int marginBottom() { return 12; }
11291 }
11292 
11293 ///
11294 enum ArrowDirection {
11295 	left, ///
11296 	right, ///
11297 	up, ///
11298 	down ///
11299 }
11300 
11301 ///
11302 version(custom_widgets)
11303 class ArrowButton : Button {
11304 	///
11305 	this(ArrowDirection direction, Widget parent) {
11306 		super("", parent);
11307 		this.direction = direction;
11308 		triggersOnMultiClick = true;
11309 	}
11310 
11311 	private ArrowDirection direction;
11312 
11313 	override int minHeight() { return scaleWithDpi(16); }
11314 	override int maxHeight() { return scaleWithDpi(16); }
11315 	override int minWidth() { return scaleWithDpi(16); }
11316 	override int maxWidth() { return scaleWithDpi(16); }
11317 
11318 	override void paint(WidgetPainter painter) {
11319 		super.paint(painter);
11320 
11321 		auto cs = getComputedStyle();
11322 
11323 		painter.outlineColor = cs.foregroundColor;
11324 		painter.fillColor = cs.foregroundColor;
11325 
11326 		auto offset = Point((this.width - scaleWithDpi(16)) / 2, (this.height - scaleWithDpi(16)) / 2);
11327 
11328 		final switch(direction) {
11329 			case ArrowDirection.up:
11330 				painter.drawPolygon(
11331 					scaleWithDpi(Point(2, 10) + offset),
11332 					scaleWithDpi(Point(7, 5) + offset),
11333 					scaleWithDpi(Point(12, 10) + offset),
11334 					scaleWithDpi(Point(2, 10) + offset)
11335 				);
11336 			break;
11337 			case ArrowDirection.down:
11338 				painter.drawPolygon(
11339 					scaleWithDpi(Point(2, 6) + offset),
11340 					scaleWithDpi(Point(7, 11) + offset),
11341 					scaleWithDpi(Point(12, 6) + offset),
11342 					scaleWithDpi(Point(2, 6) + offset)
11343 				);
11344 			break;
11345 			case ArrowDirection.left:
11346 				painter.drawPolygon(
11347 					scaleWithDpi(Point(10, 2) + offset),
11348 					scaleWithDpi(Point(5, 7) + offset),
11349 					scaleWithDpi(Point(10, 12) + offset),
11350 					scaleWithDpi(Point(10, 2) + offset)
11351 				);
11352 			break;
11353 			case ArrowDirection.right:
11354 				painter.drawPolygon(
11355 					scaleWithDpi(Point(6, 2) + offset),
11356 					scaleWithDpi(Point(11, 7) + offset),
11357 					scaleWithDpi(Point(6, 12) + offset),
11358 					scaleWithDpi(Point(6, 2) + offset)
11359 				);
11360 			break;
11361 		}
11362 	}
11363 }
11364 
11365 private
11366 int[2] getChildPositionRelativeToParentOrigin(Widget c) nothrow {
11367 	int x, y;
11368 	Widget par = c;
11369 	while(par) {
11370 		x += par.x;
11371 		y += par.y;
11372 		par = par.parent;
11373 	}
11374 	return [x, y];
11375 }
11376 
11377 version(win32_widgets)
11378 private
11379 int[2] getChildPositionRelativeToParentHwnd(Widget c) nothrow {
11380 // MapWindowPoints?
11381 	int x, y;
11382 	Widget par = c;
11383 	while(par) {
11384 		x += par.x;
11385 		y += par.y;
11386 		par = par.parent;
11387 		if(par !is null && par.useNativeDrawing())
11388 			break;
11389 	}
11390 	return [x, y];
11391 }
11392 
11393 ///
11394 class ImageBox : Widget {
11395 	private MemoryImage image_;
11396 
11397 	override int widthStretchiness() { return 1; }
11398 	override int heightStretchiness() { return 1; }
11399 	override int widthShrinkiness() { return 1; }
11400 	override int heightShrinkiness() { return 1; }
11401 
11402 	override int flexBasisHeight() {
11403 		return image_.height;
11404 	}
11405 
11406 	override int flexBasisWidth() {
11407 		return image_.width;
11408 	}
11409 
11410 	///
11411 	public void setImage(MemoryImage image){
11412 		this.image_ = image;
11413 		if(this.parentWindow && this.parentWindow.win) {
11414 			if(sprite)
11415 				sprite.dispose();
11416 			sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_, true));
11417 		}
11418 		redraw();
11419 	}
11420 
11421 	/// How to fit the image in the box if they aren't an exact match in size?
11422 	enum HowToFit {
11423 		center, /// centers the image, cropping around all the edges as needed
11424 		crop, /// always draws the image in the upper left, cropping the lower right if needed
11425 		// stretch, /// not implemented
11426 	}
11427 
11428 	private Sprite sprite;
11429 	private HowToFit howToFit_;
11430 
11431 	private Color backgroundColor_;
11432 
11433 	///
11434 	this(MemoryImage image, HowToFit howToFit, Color backgroundColor, Widget parent) {
11435 		this.image_ = image;
11436 		this.tabStop = false;
11437 		this.howToFit_ = howToFit;
11438 		this.backgroundColor_ = backgroundColor;
11439 		super(parent);
11440 		updateSprite();
11441 	}
11442 
11443 	/// ditto
11444 	this(MemoryImage image, HowToFit howToFit, Widget parent) {
11445 		this(image, howToFit, Color.transparent, parent);
11446 	}
11447 
11448 	private void updateSprite() {
11449 		if(sprite is null && this.parentWindow && this.parentWindow.win) {
11450 			sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_, true));
11451 		}
11452 	}
11453 
11454 	override void paint(WidgetPainter painter) {
11455 		updateSprite();
11456 		if(backgroundColor_.a) {
11457 			painter.fillColor = backgroundColor_;
11458 			painter.drawRectangle(Point(0, 0), width, height);
11459 		}
11460 		if(howToFit_ == HowToFit.crop)
11461 			sprite.drawAt(painter, Point(0, 0));
11462 		else if(howToFit_ == HowToFit.center) {
11463 			sprite.drawAt(painter, Point((width - image_.width) / 2, (height - image_.height) / 2));
11464 		}
11465 	}
11466 }
11467 
11468 ///
11469 class TextLabel : Widget {
11470 	override int maxHeight() { return defaultLineHeight; }
11471 	override int minHeight() { return defaultLineHeight; }
11472 	override int minWidth() { return 32; }
11473 
11474 	override int flexBasisHeight() { return minHeight(); }
11475 	override int flexBasisWidth() { return defaultTextWidth(label); }
11476 
11477 	string label_;
11478 
11479 	/++
11480 		Indicates which other control this label is here for. Similar to HTML `for` attribute.
11481 
11482 		In practice this means a click on the label will focus the `labelFor`. In future versions
11483 		it will also set screen reader hints but that is not yet implemented.
11484 
11485 		History:
11486 			Added October 3, 2021 (dub v10.4)
11487 	+/
11488 	Widget labelFor;
11489 
11490 	///
11491 	@scriptable
11492 	string label() { return label_; }
11493 
11494 	///
11495 	@scriptable
11496 	void label(string l) {
11497 		label_ = l;
11498 		version(win32_widgets) {
11499 			WCharzBuffer bfr = WCharzBuffer(l);
11500 			SetWindowTextW(hwnd, bfr.ptr);
11501 		} else version(custom_widgets)
11502 			redraw();
11503 	}
11504 
11505 	///
11506 	this(string label, TextAlignment alignment, Widget parent) {
11507 		this.label_ = label;
11508 		this.alignment = alignment;
11509 		this.tabStop = false;
11510 		super(parent);
11511 
11512 		version(win32_widgets)
11513 		createWin32Window(this, "static"w, label, (alignment & TextAlignment.Center) ? SS_CENTER : 0, (alignment & TextAlignment.Right) ? WS_EX_RIGHT : WS_EX_LEFT);
11514 	}
11515 
11516 	override void defaultEventHandler_click(scope ClickEvent ce) {
11517 		if(this.labelFor !is null)
11518 			this.labelFor.focus();
11519 	}
11520 
11521 	/++
11522 		WARNING: this currently sets TextAlignment.Right as the default. That will change in a future version.
11523 		For future-proofing of your code, if you rely on TextAlignment.Right, you MUST specify that explicitly.
11524 	+/
11525 	this(string label, Widget parent) {
11526 		this(label, TextAlignment.Right, parent);
11527 	}
11528 
11529 
11530 	TextAlignment alignment;
11531 
11532 	version(custom_widgets)
11533 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
11534 		painter.outlineColor = getComputedStyle().foregroundColor;
11535 		painter.drawText(Point(0, 0), this.label, Point(width, height), alignment);
11536 		return bounds;
11537 	}
11538 
11539 }
11540 
11541 version(custom_widgets)
11542 	private struct etc {
11543 		mixin ExperimentalTextComponent;
11544 	}
11545 
11546 version(win32_widgets)
11547 	alias EditableTextWidgetParent = Widget; ///
11548 else version(custom_widgets)
11549 	alias EditableTextWidgetParent = ScrollableWidget; ///
11550 else static assert(0);
11551 
11552 /+
11553 	This awful thing has to be rewritten. And it needs to takecare of parentWindow.inputProxy.setIMEPopupLocation too
11554 +/
11555 
11556 /// Contains the implementation of text editing
11557 abstract class EditableTextWidget : EditableTextWidgetParent {
11558 	this(Widget parent) {
11559 		super(parent);
11560 	}
11561 
11562 	bool wordWrapEnabled_ = false;
11563 	void wordWrapEnabled(bool enabled) {
11564 		version(win32_widgets) {
11565 			SendMessageW(hwnd, EM_FMTLINES, enabled ? 1 : 0, 0);
11566 		} else version(custom_widgets) {
11567 			wordWrapEnabled_ = enabled; // FIXME
11568 		} else static assert(false);
11569 	}
11570 
11571 	override int minWidth() { return scaleWithDpi(16); }
11572 	override int minHeight() { return defaultLineHeight + 0; } // the +0 is to leave room for the padding
11573 	override int widthStretchiness() { return 7; }
11574 
11575 	void selectAll() {
11576 		version(win32_widgets)
11577 			SendMessage(hwnd, EM_SETSEL, 0, -1);
11578 		else version(custom_widgets) {
11579 			textLayout.selectAll();
11580 			redraw();
11581 		}
11582 	}
11583 
11584 	@property string content() {
11585 		version(win32_widgets) {
11586 			wchar[4096] bufferstack;
11587 			wchar[] buffer;
11588 			auto len = GetWindowTextLength(hwnd);
11589 			if(len < bufferstack.length)
11590 				buffer = bufferstack[0 .. len + 1];
11591 			else
11592 				buffer = new wchar[](len + 1);
11593 
11594 			auto l = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
11595 			if(l >= 0)
11596 				return makeUtf8StringFromWindowsString(buffer[0 .. l]);
11597 			else
11598 				return null;
11599 		} else version(custom_widgets) {
11600 			return textLayout.getPlainText();
11601 		} else static assert(false);
11602 	}
11603 	@property void content(string s) {
11604 		version(win32_widgets) {
11605 			WCharzBuffer bfr = WCharzBuffer(s, WindowsStringConversionFlags.convertNewLines);
11606 			SetWindowTextW(hwnd, bfr.ptr);
11607 		} else version(custom_widgets) {
11608 			textLayout.clear();
11609 			textLayout.addText(s);
11610 
11611 			{
11612 			// FIXME: it should be able to get this info easier
11613 			auto painter = draw();
11614 			textLayout.redoLayout(painter);
11615 			}
11616 			auto cbb = textLayout.contentBoundingBox();
11617 			setContentSize(cbb.width, cbb.height);
11618 			/*
11619 			textLayout.addText(ForegroundColor.red, s);
11620 			textLayout.addText(ForegroundColor.blue, TextFormat.underline, "http://dpldocs.info/");
11621 			textLayout.addText(" is the best!");
11622 			*/
11623 			redraw();
11624 		}
11625 		else static assert(false);
11626 	}
11627 
11628 	void addText(string txt) {
11629 		version(custom_widgets) {
11630 
11631 			textLayout.addText(txt);
11632 
11633 			{
11634 			// FIXME: it should be able to get this info easier
11635 			auto painter = draw();
11636 			textLayout.redoLayout(painter);
11637 			}
11638 			auto cbb = textLayout.contentBoundingBox();
11639 			setContentSize(cbb.width, cbb.height);
11640 
11641 		} else version(win32_widgets) {
11642 			// get the current selection
11643 			DWORD StartPos, EndPos;
11644 			SendMessageW( hwnd, EM_GETSEL, cast(WPARAM)(&StartPos), cast(LPARAM)(&EndPos) );
11645 
11646 			// move the caret to the end of the text
11647 			int outLength = GetWindowTextLengthW(hwnd);
11648 			SendMessageW( hwnd, EM_SETSEL, outLength, outLength );
11649 
11650 			// insert the text at the new caret position
11651 			WCharzBuffer bfr = WCharzBuffer(txt, WindowsStringConversionFlags.convertNewLines);
11652 			SendMessageW( hwnd, EM_REPLACESEL, TRUE, cast(LPARAM) bfr.ptr );
11653 
11654 			// restore the previous selection
11655 			SendMessageW( hwnd, EM_SETSEL, StartPos, EndPos );
11656 		} else static assert(0);
11657 	}
11658 
11659 	version(custom_widgets)
11660 	override void paintFrameAndBackground(WidgetPainter painter) {
11661 		this.draw3dFrame(painter, FrameStyle.sunk, Color.white);
11662 	}
11663 
11664 	version(win32_widgets) { /* will do it with Windows calls in the classes */ }
11665 	else version(custom_widgets) {
11666 		// FIXME
11667 
11668 		static if(SimpledisplayTimerAvailable)
11669 			Timer caretTimer;
11670 		etc.TextLayout textLayout;
11671 
11672 		void setupCustomTextEditing() {
11673 			textLayout = new etc.TextLayout(Rectangle(4, 2, width - 8, height - 4));
11674 			textLayout.selectionXorColor = getComputedStyle().activeListXorColor;
11675 		}
11676 
11677 		override void paint(WidgetPainter painter) {
11678 			if(parentWindow.win.closed) return;
11679 
11680 			textLayout.boundingBox = Rectangle(4, 2, width - 8, height - 4);
11681 
11682 			/*
11683 			painter.outlineColor = Color.white;
11684 			painter.fillColor = Color.white;
11685 			painter.drawRectangle(Point(4, 4), contentWidth, contentHeight);
11686 			*/
11687 
11688 			painter.outlineColor = Color.black;
11689 			// painter.drawText(Point(4, 4), content, Point(width - 4, height - 4));
11690 
11691 			textLayout.caretShowingOnScreen = false;
11692 
11693 			textLayout.drawInto(painter, !parentWindow.win.closed && isFocused());
11694 		}
11695 
11696 		static class Style : Widget.Style {
11697 			override MouseCursor cursor() {
11698 				return GenericCursor.Text;
11699 			}
11700 		}
11701 		mixin OverrideStyle!Style;
11702 	}
11703 	else static assert(false);
11704 
11705 
11706 
11707 	version(custom_widgets)
11708 	override void defaultEventHandler_mousedown(MouseDownEvent ev) {
11709 		super.defaultEventHandler_mousedown(ev);
11710 		if(parentWindow.win.closed) return;
11711 		if(ev.button == MouseButton.left) {
11712 			if(textLayout.selectNone())
11713 				redraw();
11714 			textLayout.moveCaretToPixelCoordinates(ev.clientX, ev.clientY);
11715 			this.focus();
11716 			//this.parentWindow.win.grabInput();
11717 		} else if(ev.button == MouseButton.middle) {
11718 			static if(UsingSimpledisplayX11) {
11719 				getPrimarySelection(parentWindow.win, (txt) {
11720 					textLayout.insert(txt);
11721 					redraw();
11722 
11723 					auto cbb = textLayout.contentBoundingBox();
11724 					setContentSize(cbb.width, cbb.height);
11725 				});
11726 			}
11727 		}
11728 	}
11729 
11730 	version(custom_widgets)
11731 	override void defaultEventHandler_mouseup(MouseUpEvent ev) {
11732 		//this.parentWindow.win.releaseInputGrab();
11733 		super.defaultEventHandler_mouseup(ev);
11734 	}
11735 
11736 	version(custom_widgets)
11737 	override void defaultEventHandler_mousemove(MouseMoveEvent ev) {
11738 		super.defaultEventHandler_mousemove(ev);
11739 		if(ev.state & ModifierState.leftButtonDown) {
11740 			textLayout.selectToPixelCoordinates(ev.clientX, ev.clientY);
11741 			redraw();
11742 		}
11743 	}
11744 
11745 	version(custom_widgets)
11746 	override void defaultEventHandler_focus(Event ev) {
11747 		super.defaultEventHandler_focus(ev);
11748 		if(parentWindow.win.closed) return;
11749 		auto painter = this.draw();
11750 		textLayout.drawCaret(painter);
11751 
11752 		static if(SimpledisplayTimerAvailable)
11753 		if(caretTimer) {
11754 			caretTimer.destroy();
11755 			caretTimer = null;
11756 		}
11757 
11758 		bool blinkingCaret = true;
11759 		static if(UsingSimpledisplayX11)
11760 			if(!Image.impl.xshmAvailable)
11761 				blinkingCaret = false; // if on a remote connection, don't waste bandwidth on an expendable blink
11762 
11763 		if(blinkingCaret)
11764 		static if(SimpledisplayTimerAvailable)
11765 		caretTimer = new Timer(500, {
11766 			if(parentWindow.win.closed) {
11767 				caretTimer.destroy();
11768 				return;
11769 			}
11770 			if(isFocused()) {
11771 				auto painter = this.draw();
11772 				textLayout.drawCaret(painter);
11773 			} else if(textLayout.caretShowingOnScreen) {
11774 				auto painter = this.draw();
11775 				textLayout.eraseCaret(painter);
11776 			}
11777 		});
11778 	}
11779 
11780 	private string lastContentBlur;
11781 
11782 	override void defaultEventHandler_blur(Event ev) {
11783 		super.defaultEventHandler_blur(ev);
11784 		if(parentWindow.win.closed) return;
11785 		version(custom_widgets) {
11786 			auto painter = this.draw();
11787 			textLayout.eraseCaret(painter);
11788 			static if(SimpledisplayTimerAvailable)
11789 			if(caretTimer) {
11790 				caretTimer.destroy();
11791 				caretTimer = null;
11792 			}
11793 		}
11794 
11795 		if(this.content != lastContentBlur) {
11796 			auto evt = new ChangeEvent!string(this, &this.content);
11797 			evt.dispatch();
11798 			lastContentBlur = this.content;
11799 		}
11800 	}
11801 
11802 	version(custom_widgets)
11803 	override void defaultEventHandler_char(CharEvent ev) {
11804 		super.defaultEventHandler_char(ev);
11805 		textLayout.insert(ev.character);
11806 		redraw();
11807 
11808 		// FIXME: too inefficient
11809 		auto cbb = textLayout.contentBoundingBox();
11810 		setContentSize(cbb.width, cbb.height);
11811 	}
11812 	version(custom_widgets)
11813 	override void defaultEventHandler_keydown(KeyDownEvent ev) {
11814 		//super.defaultEventHandler_keydown(ev);
11815 		switch(ev.key) {
11816 			case Key.Delete:
11817 				textLayout.delete_();
11818 				redraw();
11819 			break;
11820 			case Key.Left:
11821 				textLayout.moveLeft();
11822 				redraw();
11823 			break;
11824 			case Key.Right:
11825 				textLayout.moveRight();
11826 				redraw();
11827 			break;
11828 			case Key.Up:
11829 				textLayout.moveUp();
11830 				redraw();
11831 			break;
11832 			case Key.Down:
11833 				textLayout.moveDown();
11834 				redraw();
11835 			break;
11836 			case Key.Home:
11837 				textLayout.moveHome();
11838 				redraw();
11839 			break;
11840 			case Key.End:
11841 				textLayout.moveEnd();
11842 				redraw();
11843 			break;
11844 			case Key.PageUp:
11845 				foreach(i; 0 .. 32)
11846 				textLayout.moveUp();
11847 				redraw();
11848 			break;
11849 			case Key.PageDown:
11850 				foreach(i; 0 .. 32)
11851 				textLayout.moveDown();
11852 				redraw();
11853 			break;
11854 
11855 			default:
11856 				 {} // intentionally blank, let "char" handle it
11857 		}
11858 		/*
11859 		if(ev.key == Key.Backspace) {
11860 			textLayout.backspace();
11861 			redraw();
11862 		}
11863 		*/
11864 		ensureVisibleInScroll(textLayout.caretBoundingBox());
11865 	}
11866 
11867 
11868 }
11869 
11870 ///
11871 class LineEdit : EditableTextWidget {
11872 	// FIXME: hack
11873 	version(custom_widgets) {
11874 	override bool showingVerticalScroll() { return false; }
11875 	override bool showingHorizontalScroll() { return false; }
11876 	}
11877 
11878 	override int flexBasisWidth() { return 250; }
11879 
11880 	///
11881 	this(Widget parent) {
11882 		super(parent);
11883 		version(win32_widgets) {
11884 			createWin32Window(this, "edit"w, "", 
11885 				0, WS_EX_CLIENTEDGE);//|WS_HSCROLL|ES_AUTOHSCROLL);
11886 		} else version(custom_widgets) {
11887 			setupCustomTextEditing();
11888 			addEventListener(delegate(CharEvent ev) {
11889 				if(ev.character == '\n')
11890 					ev.preventDefault();
11891 			});
11892 		} else static assert(false);
11893 	}
11894 	override int maxHeight() { return defaultLineHeight + 4; }
11895 	override int minHeight() { return defaultLineHeight + 4; }
11896 
11897 	/+
11898 	@property void passwordMode(bool p) {
11899 		SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) | ES_PASSWORD);
11900 	}
11901 	+/
11902 }
11903 
11904 /++
11905 	A [LineEdit] that displays `*` in place of the actual characters.
11906 
11907 	Alas, Windows requires the window to be created differently to use this style,
11908 	so it had to be a new class instead of a toggle on and off on an existing object.
11909 
11910 	FIXME: this is not yet implemented on Linux, it will work the same as a TextEdit there for now.
11911 
11912 	History:
11913 		Added January 24, 2021
11914 +/
11915 class PasswordEdit : EditableTextWidget {
11916 	version(custom_widgets) {
11917 	override bool showingVerticalScroll() { return false; }
11918 	override bool showingHorizontalScroll() { return false; }
11919 	}
11920 
11921 	override int flexBasisWidth() { return 250; }
11922 
11923 	///
11924 	this(Widget parent) {
11925 		super(parent);
11926 		version(win32_widgets) {
11927 			createWin32Window(this, "edit"w, "", 
11928 				ES_PASSWORD, WS_EX_CLIENTEDGE);//|WS_HSCROLL|ES_AUTOHSCROLL);
11929 		} else version(custom_widgets) {
11930 			setupCustomTextEditing();
11931 			addEventListener(delegate(CharEvent ev) {
11932 				if(ev.character == '\n')
11933 					ev.preventDefault();
11934 			});
11935 		} else static assert(false);
11936 	}
11937 	override int maxHeight() { return defaultLineHeight + 4; }
11938 	override int minHeight() { return defaultLineHeight + 4; }
11939 }
11940 
11941 
11942 ///
11943 class TextEdit : EditableTextWidget {
11944 	///
11945 	this(Widget parent) {
11946 		super(parent);
11947 		version(win32_widgets) {
11948 			createWin32Window(this, "edit"w, "", 
11949 				0|WS_VSCROLL|WS_HSCROLL|ES_MULTILINE|ES_WANTRETURN|ES_AUTOHSCROLL|ES_AUTOVSCROLL, WS_EX_CLIENTEDGE);
11950 		} else version(custom_widgets) {
11951 			setupCustomTextEditing();
11952 		} else static assert(false);
11953 	}
11954 	override int maxHeight() { return int.max; }
11955 	override int heightStretchiness() { return 7; }
11956 
11957 	override int flexBasisWidth() { return 250; }
11958 	override int flexBasisHeight() { return 250; }
11959 }
11960 
11961 
11962 /++
11963 
11964 +/
11965 version(none)
11966 class RichTextDisplay : Widget {
11967 	@property void content(string c) {}
11968 	void appendContent(string c) {}
11969 }
11970 
11971 ///
11972 class MessageBox : Window {
11973 	private string message;
11974 	MessageBoxButton buttonPressed = MessageBoxButton.None;
11975 	///
11976 	this(string message, string[] buttons = ["OK"], MessageBoxButton[] buttonIds = [MessageBoxButton.OK]) {
11977 		super(300, 100);
11978 
11979 		assert(buttons.length);
11980 		assert(buttons.length ==  buttonIds.length);
11981 
11982 		this.message = message;
11983 
11984 		int buttonsWidth = cast(int) buttons.length * 50 + (cast(int) buttons.length - 1) * 16;
11985 		buttonsWidth = scaleWithDpi(buttonsWidth);
11986 
11987 		int x = this.width / 2 - buttonsWidth / 2;
11988 
11989 		foreach(idx, buttonText; buttons) {
11990 			auto button = new Button(buttonText, this);
11991 			button.x = x;
11992 			button.y = height - (button.height + 10);
11993 			button.addEventListener(EventType.triggered, ((size_t idx) { return () {
11994 				this.buttonPressed = buttonIds[idx];
11995 				win.close();
11996 			}; })(idx));
11997 
11998 			button.registerMovement();
11999 			x += button.width;
12000 			x += scaleWithDpi(16);
12001 			if(idx == 0)
12002 				button.focus();
12003 		}
12004 
12005 		win.show();
12006 		redraw();
12007 	}
12008 
12009 	override void paint(WidgetPainter painter) {
12010 		super.paint(painter);
12011 
12012 		auto cs = getComputedStyle();
12013 
12014 		painter.outlineColor = cs.foregroundColor();
12015 		painter.fillColor = cs.foregroundColor();
12016 
12017 		painter.drawText(Point(0, 0), message, Point(width, height / 2), TextAlignment.Center | TextAlignment.VerticalCenter);
12018 	}
12019 
12020 	// this one is all fixed position
12021 	override void recomputeChildLayout() {}
12022 }
12023 
12024 ///
12025 enum MessageBoxStyle {
12026 	OK, ///
12027 	OKCancel, ///
12028 	RetryCancel, ///
12029 	YesNo, ///
12030 	YesNoCancel, ///
12031 	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.
12032 }
12033 
12034 ///
12035 enum MessageBoxIcon {
12036 	None, ///
12037 	Info, ///
12038 	Warning, ///
12039 	Error ///
12040 }
12041 
12042 /// Identifies the button the user pressed on a message box.
12043 enum MessageBoxButton {
12044 	None, /// The user closed the message box without clicking any of the buttons.
12045 	OK, ///
12046 	Cancel, ///
12047 	Retry, ///
12048 	Yes, ///
12049 	No, ///
12050 	Continue ///
12051 }
12052 
12053 
12054 /++
12055 	Displays a modal message box, blocking until the user dismisses it.
12056 
12057 	Returns: the button pressed.
12058 +/
12059 MessageBoxButton messageBox(string title, string message, MessageBoxStyle style = MessageBoxStyle.OK, MessageBoxIcon icon = MessageBoxIcon.None) {
12060 	version(win32_widgets) {
12061 		WCharzBuffer t = WCharzBuffer(title);
12062 		WCharzBuffer m = WCharzBuffer(message);
12063 		UINT type;
12064 		with(MessageBoxStyle)
12065 		final switch(style) {
12066 			case OK: type |= MB_OK; break;
12067 			case OKCancel: type |= MB_OKCANCEL; break;
12068 			case RetryCancel: type |= MB_RETRYCANCEL; break;
12069 			case YesNo: type |= MB_YESNO; break;
12070 			case YesNoCancel: type |= MB_YESNOCANCEL; break;
12071 			case RetryCancelContinue: type |= MB_CANCELTRYCONTINUE; break;
12072 		}
12073 		with(MessageBoxIcon)
12074 		final switch(icon) {
12075 			case None: break;
12076 			case Info: type |= MB_ICONINFORMATION; break;
12077 			case Warning: type |= MB_ICONWARNING; break;
12078 			case Error: type |= MB_ICONERROR; break;
12079 		}
12080 		switch(MessageBoxW(null, m.ptr, t.ptr, type)) {
12081 			case IDOK: return MessageBoxButton.OK;
12082 			case IDCANCEL: return MessageBoxButton.Cancel;
12083 			case IDTRYAGAIN, IDRETRY: return MessageBoxButton.Retry;
12084 			case IDYES: return MessageBoxButton.Yes;
12085 			case IDNO: return MessageBoxButton.No;
12086 			case IDCONTINUE: return MessageBoxButton.Continue;
12087 			default: return MessageBoxButton.None;
12088 		}
12089 	} else {
12090 		string[] buttons;
12091 		MessageBoxButton[] buttonIds;
12092 		with(MessageBoxStyle)
12093 		final switch(style) {
12094 			case OK:
12095 				buttons = ["OK"];
12096 				buttonIds = [MessageBoxButton.OK];
12097 			break;
12098 			case OKCancel:
12099 				buttons = ["OK", "Cancel"];
12100 				buttonIds = [MessageBoxButton.OK, MessageBoxButton.Cancel];
12101 			break;
12102 			case RetryCancel:
12103 				buttons = ["Retry", "Cancel"];
12104 				buttonIds = [MessageBoxButton.Retry, MessageBoxButton.Cancel];
12105 			break;
12106 			case YesNo:
12107 				buttons = ["Yes", "No"];
12108 				buttonIds = [MessageBoxButton.Yes, MessageBoxButton.No];
12109 			break;
12110 			case YesNoCancel:
12111 				buttons = ["Yes", "No", "Cancel"];
12112 				buttonIds = [MessageBoxButton.Yes, MessageBoxButton.No, MessageBoxButton.Cancel];
12113 			break;
12114 			case RetryCancelContinue:
12115 				buttons = ["Try Again", "Cancel", "Continue"];
12116 				buttonIds = [MessageBoxButton.Retry, MessageBoxButton.Cancel, MessageBoxButton.Continue];
12117 			break;
12118 		}
12119 		auto mb = new MessageBox(message, buttons, buttonIds);
12120 		EventLoop el = EventLoop.get;
12121 		el.run(() { return !mb.win.closed; });
12122 		return mb.buttonPressed;
12123 	}
12124 }
12125 
12126 /// ditto
12127 int messageBox(string message, MessageBoxStyle style = MessageBoxStyle.OK, MessageBoxIcon icon = MessageBoxIcon.None) {
12128 	return messageBox(null, message, style, icon);
12129 }
12130 
12131 
12132 
12133 ///
12134 alias void delegate(Widget handlerAttachedTo, Event event) EventHandler;
12135 
12136 /++
12137 	This is an opaque type you can use to disconnect an event handler when you're no longer interested.
12138 
12139 	History:
12140 		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.
12141 +/
12142 struct EventListener {
12143 	private Widget widget;
12144 	private string event;
12145 	private EventHandler handler;
12146 	private bool useCapture;
12147 
12148 	///
12149 	void disconnect() {
12150 		widget.removeEventListener(this);
12151 	}
12152 }
12153 
12154 /++
12155 	The purpose of this enum was to give a compile-time checked version of various standard event strings.
12156 
12157 	Now, I recommend you use a statically typed event object instead.
12158 
12159 	See_Also: [Event]
12160 +/
12161 enum EventType : string {
12162 	click = "click", ///
12163 
12164 	mouseenter = "mouseenter", ///
12165 	mouseleave = "mouseleave", ///
12166 	mousein = "mousein", ///
12167 	mouseout = "mouseout", ///
12168 	mouseup = "mouseup", ///
12169 	mousedown = "mousedown", ///
12170 	mousemove = "mousemove", ///
12171 
12172 	keydown = "keydown", ///
12173 	keyup = "keyup", ///
12174 	char_ = "char", ///
12175 
12176 	focus = "focus", ///
12177 	blur = "blur", ///
12178 
12179 	triggered = "triggered", ///
12180 
12181 	change = "change", ///
12182 }
12183 
12184 /++
12185 	Represents an event that is currently being processed.
12186 
12187 
12188 	Minigui's event model is based on the web browser. An event has a name, a target,
12189 	and an associated data object. It starts from the window and works its way down through
12190 	the target through all intermediate [Widget]s, triggering capture phase handlers as it goes,
12191 	then goes back up again all the way back to the window, triggering bubble phase handlers. At
12192 	the end, if [Event.preventDefault] has not been called, it calls the target widget's default
12193 	handlers for the event (please note that default handlers will be called even if [Event.stopPropagation]
12194 	was called; that just stops it from calling other handlers in the widget tree, but the default happens
12195 	whenever propagation is done, not only if it gets to the end of the chain).
12196 
12197 	This model has several nice points:
12198 
12199 	$(LIST
12200 		* It is easy to delegate dynamic handlers to a parent. You can have a parent container
12201 		  with event handlers set, then add/remove children as much as you want without needing
12202 		  to manage the event handlers on them - the parent alone can manage everything.
12203 
12204 		* It is easy to create new custom events in your application.
12205 
12206 		* It is familiar to many web developers.
12207 	)
12208 
12209 	There's a few downsides though:
12210 
12211 	$(LIST
12212 		* There's not a lot of type safety.
12213 
12214 		* You don't get a static list of what events a widget can emit.
12215 
12216 		* Tracing where an event got cancelled along the chain can get difficult; the downside of
12217 		  the central delegation benefit is it can be lead to debugging of action at a distance.
12218 	)
12219 
12220 	In May 2021, I started to adjust this model to minigui takes better advantage of D over Javascript
12221 	while keeping the benefits - and most compatibility with - the existing model. The main idea is
12222 	to simply use a D object type which provides a static interface as well as a built-in event name.
12223 	Then, a new static interface allows you to see what an event can emit and attach handlers to it
12224 	similarly to C#, which just forwards to the JS style api. They're fully compatible so you can still
12225 	delegate to a parent and use custom events as well as using the runtime dynamic access, in addition
12226 	to having a little more help from the D compiler and documentation generator.
12227 
12228 	Your code would change like this:
12229 
12230 	---
12231 	// old
12232 	widget.addEventListener("keydown", (Event ev) { ... }, /* optional arg */ useCapture );
12233 
12234 	// new
12235 	widget.addEventListener((KeyDownEvent ev) { ... }, /* optional arg */ useCapture );
12236 	---
12237 
12238 	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.
12239 
12240 	All you have to do is replace the string with a specific Event subclass. It will figure out the event string from the class.
12241 
12242 	Alternatively, you can cast the Event yourself to the appropriate subclass, but it is easier to let the library do it for you!
12243 
12244 	Thus the family of functions are:
12245 
12246 	[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.
12247 
12248 	[Widget.addDirectEventListener] is addEventListener, but only calls the handler if target == this. Useful for something you can't afford to delegate.
12249 
12250 	[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.
12251 
12252 	Let's implement a custom widget that can emit a ChangeEvent describing its `checked` property:
12253 
12254 	---
12255 	class MyCheckbox : Widget {
12256 		/// This gives a chance to document it and generates a convenience function to send it and attach handlers.
12257 		/// It is NOT actually required but should be used whenever possible.
12258 		mixin Emits!(ChangeEvent!bool);
12259 
12260 		this(Widget parent) {
12261 			super(parent);
12262 			setDefaultEventHandler((ClickEvent) { checked = !checked; });
12263 		}
12264 
12265 		private bool _checked;
12266 		@property bool checked() { return _checked; }
12267 		@property void checked(bool set) {
12268 			_checked = set;
12269 			emit!(ChangeEvent!bool)(&checked);
12270 		}
12271 	}
12272 	---
12273 
12274 	## Creating Your Own Events
12275 
12276 	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.
12277 
12278 	---
12279 	class MyEvent : Event {
12280 		this(Widget target) { super(EventString, target); }
12281 		mixin Register; // adds EventString and other reflection information
12282 	}
12283 	---
12284 
12285 	Then declare that it is sent with the [Emits] mixin, so you can use [Widget.emit] to dispatch it.
12286 
12287 	History:
12288 		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.
12289 
12290 		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.
12291 +/
12292 /+
12293 
12294 	## General Conventions
12295 
12296 	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.
12297 
12298 
12299 	## Qt-style signals and slots
12300 
12301 	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.
12302 
12303 	The intention is for events to be used when
12304 
12305 	---
12306 	class Demo : Widget {
12307 		this() {
12308 			myPropertyChanged = Signal!int(this);
12309 		}
12310 		@property myProperty(int v) {
12311 			myPropertyChanged.emit(v);
12312 		}
12313 
12314 		Signal!int myPropertyChanged; // i need to get `this` off it and inspect the name...
12315 		// but it can just genuinely not care about `this` since that's not really passed.
12316 	}
12317 
12318 	class Foo : Widget {
12319 		// the slot uda is not necessary, but it helps the script and ui builder find it.
12320 		@slot void setValue(int v) { ... }
12321 	}
12322 
12323 	demo.myPropertyChanged.connect(&foo.setValue);
12324 	---
12325 
12326 	The Signal type has a disabled default constructor, meaning your widget constructor must pass `this` to it in its constructor.
12327 
12328 	Some events may also wish to implement the Signal interface. These use particular arguments to call a method automatically.
12329 
12330 	class StringChangeEvent : ChangeEvent, Signal!string {
12331 		mixin SignalImpl
12332 	}
12333 
12334 +/
12335 class Event : ReflectableProperties {
12336 	/// Creates an event without populating any members and without sending it. See [dispatch]
12337 	this(string eventName, Widget emittedBy) {
12338 		this.eventName = eventName;
12339 		this.srcElement = emittedBy;
12340 	}
12341 
12342 
12343 	/// Implementations for the [ReflectableProperties] interface/
12344 	void getPropertiesList(scope void delegate(string name) sink) const {}
12345 	/// ditto
12346 	void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink) { }
12347 	/// ditto
12348 	SetPropertyResult setPropertyFromString(string name, scope const(char)[] str, bool strIsJson) {
12349 		return SetPropertyResult.notPermitted;
12350 	}
12351 
12352 
12353 	/+
12354 	/++
12355 		This is an internal implementation detail of [Register] and is subject to be changed or removed at any time without notice.
12356 
12357 		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.
12358 	+/
12359 	protected final void sinkJsonString(string memberName, scope const(char)[] value, scope void delegate(string name, scope const(char)[] value) finalSink) {
12360 		if(value.length == 0) {
12361 			finalSink(memberName, `""`);
12362 			return;
12363 		}
12364 
12365 		char[1024] bufferBacking;
12366 		char[] buffer = bufferBacking;
12367 		int bufferPosition;
12368 
12369 		void sink(char ch) {
12370 			if(bufferPosition >= buffer.length)
12371 				buffer.length = buffer.length + 1024;
12372 			buffer[bufferPosition++] = ch;
12373 		}
12374 
12375 		sink('"');
12376 
12377 		foreach(ch; value) {
12378 			switch(ch) {
12379 				case '\\':
12380 					sink('\\'); sink('\\');
12381 				break;
12382 				case '"':
12383 					sink('\\'); sink('"');
12384 				break;
12385 				case '\n':
12386 					sink('\\'); sink('n');
12387 				break;
12388 				case '\r':
12389 					sink('\\'); sink('r');
12390 				break;
12391 				case '\t':
12392 					sink('\\'); sink('t');
12393 				break;
12394 				default:
12395 					sink(ch);
12396 			}
12397 		}
12398 
12399 		sink('"');
12400 
12401 		finalSink(memberName, buffer[0 .. bufferPosition]);
12402 	}
12403 	+/
12404 
12405 	/+
12406 	enum EventInitiator {
12407 		system,
12408 		minigui,
12409 		user
12410 	}
12411 
12412 	immutable EventInitiator; initiatedBy;
12413 	+/
12414 
12415 	/++
12416 		Events should generally follow the propagation model, but there's some exceptions
12417 		to that rule. If so, they should override this to return false. In that case, only
12418 		bubbling event handlers on the target itself and capturing event handlers on the containing
12419 		window will be called. (That is, [dispatch] will call [sendDirectly] instead of doing the normal
12420 		capture -> target -> bubble process.)
12421 
12422 		History:
12423 			Added May 12, 2021
12424 	+/
12425 	bool propagates() const pure nothrow @nogc @safe {
12426 		return true;
12427 	}
12428 
12429 	/++
12430 		hints as to whether preventDefault will actually do anything. not entirely reliable.
12431 
12432 		History:
12433 			Added May 14, 2021
12434 	+/
12435 	bool cancelable() const pure nothrow @nogc @safe {
12436 		return true;
12437 	}
12438 
12439 	/++
12440 		You can mix this into child class to register some boilerplate. It includes the `EventString`
12441 		member, a constructor, and implementations of the dynamic get data interfaces.
12442 
12443 		If you fail to do this, your event will probably not have full compatibility but it might still work for you.
12444 
12445 
12446 		You can override the default EventString by simply providing your own in the form of
12447 		`enum string EventString = "some.name";` The default is the name of your class and its parent entity
12448 		which provides some namespace protection against conflicts in other libraries while still being fairly
12449 		easy to use.
12450 
12451 		If you provide your own constructor, it will override the default constructor provided here. A constructor
12452 		must call `super(EventString, passed_widget_target)` at some point. The `passed_widget_target` must be the
12453 		first argument to your constructor.
12454 
12455 		History:
12456 			Added May 13, 2021.
12457 	+/
12458 	protected static mixin template Register() {
12459 		public enum string EventString = __traits(identifier, __traits(parent, typeof(this))) ~ "." ~  __traits(identifier, typeof(this));
12460 		this(Widget target) { super(EventString, target); }
12461 
12462 		mixin ReflectableProperties.RegisterGetters;
12463 	}
12464 
12465 	/++
12466 		This is the widget that emitted the event.
12467 
12468 
12469 		The aliased names come from Javascript for ease of web developers to transition in, but they're all synonyms.
12470 
12471 		History:
12472 			The `source` name was added on May 14, 2021. It is a little weird that `source` and `target` are synonyms,
12473 			but that's a side effect of it doing both capture and bubble handlers and people are used to it from the web
12474 			so I don't intend to remove these aliases.
12475 	+/
12476 	Widget source;
12477 	/// ditto
12478 	alias source target;
12479 	/// ditto
12480 	alias source srcElement;
12481 
12482 	Widget relatedTarget; /// Note: likely to be deprecated at some point.
12483 
12484 	/// Prevents the default event handler (if there is one) from being called
12485 	void preventDefault() {
12486 		lastDefaultPrevented = true;
12487 		defaultPrevented = true;
12488 	}
12489 
12490 	/// Stops the event propagation immediately.
12491 	void stopPropagation() {
12492 		propagationStopped = true;
12493 	}
12494 
12495 	private bool defaultPrevented;
12496 	private bool propagationStopped;
12497 	private string eventName;
12498 
12499 	private bool isBubbling;
12500 
12501 	/// 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.
12502 	protected void adjustScrolling() { }
12503 	/// ditto
12504 	protected void adjustClientCoordinates(int deltaX, int deltaY) { }
12505 
12506 	/++
12507 		this sends it only to the target. If you want propagation, use dispatch() instead.
12508 
12509 		This should be made private!!!
12510 
12511 	+/
12512 	void sendDirectly() {
12513 		if(srcElement is null)
12514 			return;
12515 
12516 		// 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.
12517 
12518 		//debug if(eventName != "mousemove" && target !is null && target.parentWindow && target.parentWindow.devTools)
12519 			//target.parentWindow.devTools.log("Event ", eventName, " dispatched directly to ", srcElement);
12520 
12521 		adjustScrolling();
12522 
12523 		if(auto e = target.parentWindow) {
12524 			if(auto handlers = "*" in e.capturingEventHandlers)
12525 			foreach(handler; *handlers)
12526 				if(handler) handler(e, this);
12527 			if(auto handlers = eventName in e.capturingEventHandlers)
12528 			foreach(handler; *handlers)
12529 				if(handler) handler(e, this);
12530 		}
12531 
12532 		auto e = srcElement;
12533 
12534 		if(auto handlers = eventName in e.bubblingEventHandlers)
12535 		foreach(handler; *handlers)
12536 			if(handler) handler(e, this);
12537 
12538 		if(auto handlers = "*" in e.bubblingEventHandlers)
12539 		foreach(handler; *handlers)
12540 			if(handler) handler(e, this);
12541 
12542 		// there's never a default for a catch-all event
12543 		if(!defaultPrevented)
12544 			if(eventName in e.defaultEventHandlers)
12545 				e.defaultEventHandlers[eventName](e, this);
12546 	}
12547 
12548 	/// this dispatches the element using the capture -> target -> bubble process
12549 	void dispatch() {
12550 		if(srcElement is null)
12551 			return;
12552 
12553 		if(!propagates) {
12554 			sendDirectly;
12555 			return;
12556 		}
12557 
12558 		//debug if(eventName != "mousemove" && target !is null && target.parentWindow && target.parentWindow.devTools)
12559 			//target.parentWindow.devTools.log("Event ", eventName, " dispatched to ", srcElement);
12560 
12561 		adjustScrolling();
12562 		// first capture, then bubble
12563 
12564 		Widget[] chain;
12565 		Widget curr = srcElement;
12566 		while(curr) {
12567 			auto l = curr;
12568 			chain ~= l;
12569 			curr = curr.parent;
12570 		}
12571 
12572 		isBubbling = false;
12573 
12574 		foreach_reverse(e; chain) {
12575 			if(auto handlers = "*" in e.capturingEventHandlers)
12576 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
12577 
12578 			if(propagationStopped)
12579 				break;
12580 
12581 			if(auto handlers = eventName in e.capturingEventHandlers)
12582 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
12583 
12584 			// the default on capture should really be to always do nothing
12585 
12586 			//if(!defaultPrevented)
12587 			//	if(eventName in e.defaultEventHandlers)
12588 			//		e.defaultEventHandlers[eventName](e.element, this);
12589 
12590 			if(propagationStopped)
12591 				break;
12592 		}
12593 
12594 		int adjustX;
12595 		int adjustY;
12596 
12597 		isBubbling = true;
12598 		if(!propagationStopped)
12599 		foreach(e; chain) {
12600 			if(auto handlers = eventName in e.bubblingEventHandlers)
12601 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
12602 
12603 			if(propagationStopped)
12604 				break;
12605 
12606 			if(auto handlers = "*" in e.bubblingEventHandlers)
12607 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
12608 
12609 			if(propagationStopped)
12610 				break;
12611 
12612 			if(e.encapsulatedChildren()) {
12613 				adjustClientCoordinates(adjustX, adjustY);
12614 				target = e;
12615 			} else {
12616 				adjustX += e.x;
12617 				adjustY += e.y;
12618 			}
12619 		}
12620 
12621 		if(!defaultPrevented)
12622 		foreach(e; chain) {
12623 			if(eventName in e.defaultEventHandlers)
12624 				e.defaultEventHandlers[eventName](e, this);
12625 		}
12626 	}
12627 
12628 
12629 	/* old compatibility things */
12630 	deprecated("Use some subclass of KeyEventBase instead of plain Event in your handler going forward. WARNING these may crash on non-key events!")
12631 	final @property {
12632 		Key key() { return (cast(KeyEventBase) this).key; }
12633 		KeyEvent originalKeyEvent() { return (cast(KeyEventBase) this).originalKeyEvent; }
12634 
12635 		bool ctrlKey() { return (cast(KeyEventBase) this).ctrlKey; }
12636 		bool altKey() { return (cast(KeyEventBase) this).altKey; }
12637 		bool shiftKey() { return (cast(KeyEventBase) this).shiftKey; }
12638 	}
12639 
12640 	deprecated("Use some subclass of MouseEventBase instead of Event in your handler going forward. WARNING these may crash on non-mouse events!")
12641 	final @property {
12642 		int clientX() { return (cast(MouseEventBase) this).clientX; }
12643 		int clientY() { return (cast(MouseEventBase) this).clientY; }
12644 
12645 		int viewportX() { return (cast(MouseEventBase) this).viewportX; }
12646 		int viewportY() { return (cast(MouseEventBase) this).viewportY; }
12647 
12648 		int button() { return (cast(MouseEventBase) this).button; }
12649 		int buttonLinear() { return (cast(MouseEventBase) this).buttonLinear; }
12650 	}
12651 
12652 	deprecated("Use either a KeyEventBase or a MouseEventBase instead of Event in your handler going forward")
12653 	final @property {
12654 		int state() {
12655 			if(auto meb = cast(MouseEventBase) this)
12656 				return meb.state;
12657 			if(auto keb = cast(KeyEventBase) this)
12658 				return keb.state;
12659 			assert(0);
12660 		}
12661 	}
12662 
12663 	deprecated("Use a CharEvent instead of Event in your handler going forward")
12664 	final @property {
12665 		dchar character() {
12666 			if(auto ce = cast(CharEvent) this)
12667 				return ce.character;
12668 			return dchar.init;
12669 		}
12670 	}
12671 
12672 	// for change events
12673 	@property {
12674 		///
12675 		int intValue() { return 0; }
12676 		///
12677 		string stringValue() { return null; }
12678 	}
12679 }
12680 
12681 /++
12682 	This lets you statically verify you send the events you claim you send and gives you a hook to document them.
12683 
12684 	Please note that a widget may send events not listed as Emits. You can always construct and dispatch
12685 	dynamic and custom events, but the static list helps ensure you get them right.
12686 
12687 	If this is declared, you can use [Widget.emit] to send the event.
12688 
12689 	All events work the same way though, following the capture->widget->bubble model described under [Event].
12690 
12691 	History:
12692 		Added May 4, 2021
12693 +/
12694 mixin template Emits(EventType) {
12695 	import arsd.minigui : EventString;
12696 	static if(is(EventType : Event) && !is(EventType == Event))
12697 		mixin("private EventType[0] emits_" ~ EventStringIdentifier!EventType ~";");
12698 	else
12699 		static assert(0, "You can only emit subclasses of Event");
12700 }
12701 
12702 /// ditto
12703 mixin template Emits(string eventString) {
12704 	mixin("private Event[0] emits_" ~ eventString ~";");
12705 }
12706 
12707 /*
12708 class SignalEvent(string name) : Event {
12709 
12710 }
12711 */
12712 
12713 /++
12714 	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".
12715 
12716 
12717 	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.
12718 
12719 	History:
12720 		Added on May 13, 2021. Prior to that, you'd most likely `addEventListener(EventType.triggered, ...)` to handle similar things.
12721 +/
12722 class CommandEvent : Event {
12723 	enum EventString = "command";
12724 	this(Widget source, string CommandString = EventString) {
12725 		super(CommandString, source);
12726 	}
12727 }
12728 
12729 /++
12730 	A [CommandEvent] is typically actually an instance of these to hold the strongly-typed arguments.
12731 +/
12732 class CommandEventWithArgs(Args...) : CommandEvent {
12733 	this(Widget source, string CommandString, Args args) { super(source, CommandString); this.args = args; }
12734 	Args args;
12735 }
12736 
12737 /++
12738 	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.
12739 
12740 	See [CommandEvent] for more information.
12741 
12742 	Returns:
12743 		The [EventListener] you can use to remove the handler.
12744 +/
12745 EventListener consumesCommand(string CommandString, WidgetType, Args...)(WidgetType w, void delegate(Args) handler) {
12746 	return w.addEventListener(CommandString, (Event ev) {
12747 		if(ev.target is w)
12748 			return; // it does not consume its own commands!
12749 		if(auto cev = cast(CommandEventWithArgs!Args) ev) {
12750 			handler(cev.args);
12751 			ev.stopPropagation();
12752 		}
12753 	});
12754 }
12755 
12756 /++
12757 	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.
12758 +/
12759 void emitCommand(string CommandString, WidgetType, Args...)(WidgetType w, Args args) {
12760 	auto event = new CommandEventWithArgs!Args(w, CommandString, args);
12761 	event.dispatch();
12762 }
12763 
12764 class ResizeEvent : Event {
12765 	enum EventString = "resize";
12766 
12767 	this(Widget target) { super(EventString, target); }
12768 
12769 	override bool propagates() const { return false; }
12770 }
12771 
12772 /++
12773 	ClosingEvent is fired when a user is attempting to close a window. You can `preventDefault` to cancel the close.
12774 
12775 	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.
12776 
12777 	History:
12778 		Added June 21, 2021 (dub v10.1)
12779 +/
12780 class ClosingEvent : Event {
12781 	enum EventString = "closing";
12782 
12783 	this(Widget target) { super(EventString, target); }
12784 
12785 	override bool propagates() const { return false; }
12786 	override bool cancelable() const { return true; }
12787 }
12788 
12789 /// ditto
12790 class ClosedEvent : Event {
12791 	enum EventString = "closed";
12792 
12793 	this(Widget target) { super(EventString, target); }
12794 
12795 	override bool propagates() const { return false; }
12796 	override bool cancelable() const { return false; }
12797 }
12798 
12799 ///
12800 class BlurEvent : Event {
12801 	enum EventString = "blur";
12802 
12803 	// FIXME: related target?
12804 	this(Widget target) { super(EventString, target); }
12805 
12806 	override bool propagates() const { return false; }
12807 }
12808 
12809 ///
12810 class FocusEvent : Event {
12811 	enum EventString = "focus";
12812 
12813 	// FIXME: related target?
12814 	this(Widget target) { super(EventString, target); }
12815 
12816 	override bool propagates() const { return false; }
12817 }
12818 
12819 /++
12820 	FocusInEvent is a FocusEvent that propagates, while FocusOutEvent is a BlurEvent that propagates.
12821 
12822 	History:
12823 		Added July 3, 2021
12824 +/
12825 class FocusInEvent : Event {
12826 	enum EventString = "focusin";
12827 
12828 	// FIXME: related target?
12829 	this(Widget target) { super(EventString, target); }
12830 
12831 	override bool cancelable() const { return false; }
12832 }
12833 
12834 /// ditto
12835 class FocusOutEvent : Event {
12836 	enum EventString = "focusout";
12837 
12838 	// FIXME: related target?
12839 	this(Widget target) { super(EventString, target); }
12840 
12841 	override bool cancelable() const { return false; }
12842 }
12843 
12844 ///
12845 class ScrollEvent : Event {
12846 	enum EventString = "scroll";
12847 	this(Widget target) { super(EventString, target); }
12848 
12849 	override bool cancelable() const { return false; }
12850 }
12851 
12852 /++
12853 	Indicates that a character has been typed by the user. Normally dispatched to the currently focused widget.
12854 
12855 	History:
12856 		Added May 2, 2021. Previously, this was simply a "char" event and `character` as a member of the [Event] base class.
12857 +/
12858 class CharEvent : Event {
12859 	enum EventString = "char";
12860 	this(Widget target, dchar ch) {
12861 		character = ch;
12862 		super(EventString, target);
12863 	}
12864 
12865 	immutable dchar character;
12866 }
12867 
12868 /++
12869 	You should generally use a `ChangeEvent!Type` instead of this directly. See [ChangeEvent] for more information.
12870 +/
12871 abstract class ChangeEventBase : Event {
12872 	enum EventString = "change";
12873 	this(Widget target) {
12874 		super(EventString, target);
12875 	}
12876 
12877 	/+
12878 		// idk where or how exactly i want to do this.
12879 		// i might come back to it later.
12880 
12881 	// If a widget itself broadcasts one of theses itself, it stops propagation going down
12882 	// this way the source doesn't get too confused (think of a nested scroll widget)
12883 	//
12884 	// the idea is like the scroll bar emits a command event saying like "scroll left one line"
12885 	// then you consume that command and change you scroll x position to whatever. then you do
12886 	// some kind of change event that is broadcast back to the children and any horizontal scroll
12887 	// listeners are now able to update, without having an explicit connection between them.
12888 	void broadcastToChildren(string fieldName) {
12889 
12890 	}
12891 	+/
12892 }
12893 
12894 /++
12895 	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.
12896 
12897 
12898 	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).
12899 
12900 	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);`
12901 
12902 	Since it is emitted after the value has already changed, [preventDefault] is unlikely to do anything.
12903 
12904 	History:
12905 		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.
12906 +/
12907 class ChangeEvent(T) : ChangeEventBase {
12908 	this(Widget target, T delegate() getNewValue) {
12909 		assert(getNewValue !is null);
12910 		this.getNewValue = getNewValue;
12911 		super(target);
12912 	}
12913 
12914 	private T delegate() getNewValue;
12915 
12916 	/++
12917 		Gets the new value that just changed.
12918 	+/
12919 	@property T value() {
12920 		return getNewValue();
12921 	}
12922 
12923 	/// compatibility method for old generic Events
12924 	static if(is(immutable T == immutable int))
12925 		override int intValue() { return value; }
12926 	/// ditto
12927 	static if(is(immutable T == immutable string))
12928 		override string stringValue() { return value; }
12929 }
12930 
12931 /++
12932 	Contains shared properties for [KeyDownEvent]s and [KeyUpEvent]s.
12933 
12934 
12935 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
12936 
12937 	History:
12938 		Added May 2, 2021. Previously, its properties were members of the [Event] base class.
12939 +/
12940 abstract class KeyEventBase : Event {
12941 	this(string name, Widget target) {
12942 		super(name, target);
12943 	}
12944 
12945 	// for key events
12946 	Key key; ///
12947 
12948 	KeyEvent originalKeyEvent;
12949 
12950 	/++
12951 		Indicates the current state of the given keyboard modifier keys.
12952 
12953 		History:
12954 			Added to events on April 15, 2020.
12955 	+/
12956 	bool ctrlKey;
12957 
12958 	/// ditto
12959 	bool altKey;
12960 
12961 	/// ditto
12962 	bool shiftKey;
12963 
12964 	/++
12965 		The raw bitflags that are parsed out into [ctrlKey], [altKey], and [shiftKey].
12966 
12967 		See [arsd.simpledisplay.ModifierState] for other possible flags.
12968 	+/
12969 	int state;
12970 
12971 	mixin Register;
12972 }
12973 
12974 /++
12975 	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].
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 	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.
12981 
12982 	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.
12983 
12984 	See_Also: [KeyUpEvent], [CharEvent]
12985 
12986 	History:
12987 		Added May 2, 2021. Previously, it was only seen as the base [Event] class on "keydown" event listeners.
12988 +/
12989 class KeyDownEvent : KeyEventBase {
12990 	enum EventString = "keydown";
12991 	this(Widget target) { super(EventString, target); }
12992 }
12993 
12994 /++
12995 	Indicates that the user has released a key on the keyboard. For available properties, see [KeyEventBase].
12996 
12997 
12998 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
12999 
13000 	See_Also: [KeyDownEvent], [CharEvent]
13001 
13002 	History:
13003 		Added May 2, 2021. Previously, it was only seen as the base [Event] class on "keyup" event listeners.
13004 +/
13005 class KeyUpEvent : KeyEventBase {
13006 	enum EventString = "keyup";
13007 	this(Widget target) { super(EventString, target); }
13008 }
13009 
13010 /++
13011 	Contains shared properties for various mouse events;
13012 
13013 
13014 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
13015 
13016 	History:
13017 		Added May 2, 2021. Previously, its properties were members of the [Event] base class.
13018 +/
13019 abstract class MouseEventBase : Event {
13020 	this(string name, Widget target) {
13021 		super(name, target);
13022 	}
13023 
13024 	// for mouse events
13025 	int clientX; /// The mouse event location relative to the target widget
13026 	int clientY; /// ditto
13027 
13028 	int viewportX; /// The mouse event location relative to the window origin
13029 	int viewportY; /// ditto
13030 
13031 	int button; /// See: [MouseEvent.button]
13032 	int buttonLinear; /// See: [MouseEvent.buttonLinear]
13033 
13034 	/++
13035 		Indicates the current state of the given keyboard modifier keys.
13036 
13037 		History:
13038 			Added to mouse events on September 28, 2010.
13039 	+/
13040 	bool ctrlKey;
13041 
13042 	/// ditto
13043 	bool altKey;
13044 
13045 	/// ditto
13046 	bool shiftKey;
13047 
13048 
13049 
13050 	int state; ///
13051 
13052 	/++
13053 		for consistent names with key event. 
13054 
13055 		History:
13056 			Added September 28, 2021 (dub v10.3)
13057 	+/
13058 	alias modifierState = state;
13059 
13060 	/++
13061 		Mouse wheel movement sends down/up/click events just like other buttons clicking. This method is to help you filter that out.
13062 
13063 		History:
13064 			Added May 15, 2021
13065 	+/
13066 	bool isMouseWheel() {
13067 		return button == MouseButton.wheelUp || button == MouseButton.wheelDown;
13068 	}
13069 
13070 	// private
13071 	override void adjustClientCoordinates(int deltaX, int deltaY) {
13072 		clientX += deltaX;
13073 		clientY += deltaY;
13074 	}
13075 
13076 	override void adjustScrolling() {
13077 	version(custom_widgets) { // TEMP
13078 		viewportX = clientX;
13079 		viewportY = clientY;
13080 		if(auto se = cast(ScrollableWidget) srcElement) {
13081 			clientX += se.scrollOrigin.x;
13082 			clientY += se.scrollOrigin.y;
13083 		} else if(auto se = cast(ScrollableContainerWidget) srcElement) {
13084 			//clientX += se.scrollX_;
13085 			//clientY += se.scrollY_;
13086 		}
13087 	}
13088 	}
13089 
13090 	mixin Register;
13091 }
13092 
13093 /++
13094 	Indicates that the user has worked with the mouse over your widget. For available properties, see [MouseEventBase].
13095 
13096 
13097 	$(WARNING
13098 		Important: MouseDownEvent, MouseUpEvent, ClickEvent, and DoubleClickEvent are all sent for all mouse buttons and
13099 		for wheel movement! You should check the [MouseEventBase.button|button] property in most your handlers to get correct
13100 		behavior.
13101 	)
13102 
13103 	[MouseDownEvent] is sent when the user presses a mouse button. It is also sent on mouse wheel movement.
13104 
13105 	[MouseUpEvent] is sent when the user releases a mouse button.
13106 
13107 	[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.)
13108 
13109 	[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.
13110 
13111 	[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.
13112 
13113 	[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.
13114 
13115 	[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.
13116 
13117 	[MouseEnterEvent] is sent when the mouse enters the bounding box of a widget.
13118 
13119 	[MouseLeaveEvent] is sent when the mouse leaves the bounding box of a widget.
13120 
13121 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
13122 
13123 	Rationale:
13124 
13125 		If you only want to do drag, mousedown/up works just fine being consistently sent.
13126 
13127 		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).
13128 
13129 		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.
13130 
13131 	History:
13132 		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.
13133 +/
13134 class MouseUpEvent : MouseEventBase {
13135 	enum EventString = "mouseup"; ///
13136 	this(Widget target) { super(EventString, target); }
13137 }
13138 /// ditto
13139 class MouseDownEvent : MouseEventBase {
13140 	enum EventString = "mousedown"; ///
13141 	this(Widget target) { super(EventString, target); }
13142 }
13143 /// ditto
13144 class MouseMoveEvent : MouseEventBase {
13145 	enum EventString = "mousemove"; ///
13146 	this(Widget target) { super(EventString, target); }
13147 }
13148 /// ditto
13149 class ClickEvent : MouseEventBase {
13150 	enum EventString = "click"; ///
13151 	this(Widget target) { super(EventString, target); }
13152 }
13153 /// ditto
13154 class DoubleClickEvent : MouseEventBase {
13155 	enum EventString = "dblclick"; ///
13156 	this(Widget target) { super(EventString, target); }
13157 }
13158 /// ditto
13159 class MouseOverEvent : Event {
13160 	enum EventString = "mouseover"; ///
13161 	this(Widget target) { super(EventString, target); }
13162 }
13163 /// ditto
13164 class MouseOutEvent : Event {
13165 	enum EventString = "mouseout"; ///
13166 	this(Widget target) { super(EventString, target); }
13167 }
13168 /// ditto
13169 class MouseEnterEvent : Event {
13170 	enum EventString = "mouseenter"; ///
13171 	this(Widget target) { super(EventString, target); }
13172 
13173 	override bool propagates() const { return false; }
13174 }
13175 /// ditto
13176 class MouseLeaveEvent : Event {
13177 	enum EventString = "mouseleave"; ///
13178 	this(Widget target) { super(EventString, target); }
13179 
13180 	override bool propagates() const { return false; }
13181 }
13182 
13183 private bool isAParentOf(Widget a, Widget b) {
13184 	if(a is null || b is null)
13185 		return false;
13186 
13187 	while(b !is null) {
13188 		if(a is b)
13189 			return true;
13190 		b = b.parent;
13191 	}
13192 
13193 	return false;
13194 }
13195 
13196 private struct WidgetAtPointResponse {
13197 	Widget widget;
13198 
13199 	// x, y relative to the widget in the response.
13200 	int x;
13201 	int y;
13202 }
13203 
13204 private WidgetAtPointResponse widgetAtPoint(Widget starting, int x, int y) {
13205 	assert(starting !is null);
13206 
13207 	starting.addScrollPosition(x, y);
13208 
13209 	auto child = starting.getChildAtPosition(x, y);
13210 	while(child) {
13211 		if(child.hidden)
13212 			continue;
13213 		starting = child;
13214 		x -= child.x;
13215 		y -= child.y;
13216 		auto r = starting.widgetAtPoint(x, y);//starting.getChildAtPosition(x, y);
13217 		child = r.widget;
13218 		if(child is starting)
13219 			break;
13220 	}
13221 	return WidgetAtPointResponse(starting, x, y);
13222 }
13223 
13224 version(win32_widgets) {
13225 private:
13226 	import core.sys.windows.commctrl;
13227 
13228 	pragma(lib, "comctl32");
13229 	shared static this() {
13230 		// http://msdn.microsoft.com/en-us/library/windows/desktop/bb775507(v=vs.85).aspx
13231 		INITCOMMONCONTROLSEX ic;
13232 		ic.dwSize = cast(DWORD) ic.sizeof;
13233 		ic.dwICC = ICC_UPDOWN_CLASS | ICC_WIN95_CLASSES | ICC_BAR_CLASSES | ICC_PROGRESS_CLASS | ICC_COOL_CLASSES | ICC_STANDARD_CLASSES | ICC_USEREX_CLASSES;
13234 		if(!InitCommonControlsEx(&ic)) {
13235 			//import std.stdio; writeln("ICC failed");
13236 		}
13237 	}
13238 
13239 
13240 	// everything from here is just win32 headers copy pasta
13241 private:
13242 extern(Windows):
13243 
13244 	alias HANDLE HMENU;
13245 	HMENU CreateMenu();
13246 	bool SetMenu(HWND, HMENU);
13247 	HMENU CreatePopupMenu();
13248 	enum MF_POPUP = 0x10;
13249 	enum MF_STRING = 0;
13250 
13251 
13252 	BOOL InitCommonControlsEx(const INITCOMMONCONTROLSEX*);
13253 	struct INITCOMMONCONTROLSEX {
13254 		DWORD dwSize;
13255 		DWORD dwICC;
13256 	}
13257 	enum HINST_COMMCTRL = cast(HINSTANCE) (-1);
13258 enum {
13259         IDB_STD_SMALL_COLOR,
13260         IDB_STD_LARGE_COLOR,
13261         IDB_VIEW_SMALL_COLOR = 4,
13262         IDB_VIEW_LARGE_COLOR = 5
13263 }
13264 enum {
13265         STD_CUT,
13266         STD_COPY,
13267         STD_PASTE,
13268         STD_UNDO,
13269         STD_REDOW,
13270         STD_DELETE,
13271         STD_FILENEW,
13272         STD_FILEOPEN,
13273         STD_FILESAVE,
13274         STD_PRINTPRE,
13275         STD_PROPERTIES,
13276         STD_HELP,
13277         STD_FIND,
13278         STD_REPLACE,
13279         STD_PRINT // = 14
13280 }
13281 
13282 alias HANDLE HIMAGELIST;
13283 	HIMAGELIST ImageList_Create(int, int, UINT, int, int);
13284 	int ImageList_Add(HIMAGELIST, HBITMAP, HBITMAP);
13285         BOOL ImageList_Destroy(HIMAGELIST);
13286 
13287 uint MAKELONG(ushort a, ushort b) {
13288         return cast(uint) ((b << 16) | a);
13289 }
13290 
13291 
13292 struct TBBUTTON {
13293 	int   iBitmap;
13294 	int   idCommand;
13295 	BYTE  fsState;
13296 	BYTE  fsStyle;
13297 	version(Win64)
13298 	BYTE[6] bReserved;
13299 	else
13300 	BYTE[2]  bReserved;
13301 	DWORD dwData;
13302 	INT_PTR   iString;
13303 }
13304 
13305 	enum {
13306 		TB_ADDBUTTONSA   = WM_USER + 20,
13307 		TB_INSERTBUTTONA = WM_USER + 21,
13308 		TB_GETIDEALSIZE = WM_USER + 99,
13309 	}
13310 
13311 struct SIZE {
13312 	LONG cx;
13313 	LONG cy;
13314 }
13315 
13316 
13317 enum {
13318 	TBSTATE_CHECKED       = 1,
13319 	TBSTATE_PRESSED       = 2,
13320 	TBSTATE_ENABLED       = 4,
13321 	TBSTATE_HIDDEN        = 8,
13322 	TBSTATE_INDETERMINATE = 16,
13323 	TBSTATE_WRAP          = 32
13324 }
13325 
13326 
13327 
13328 enum {
13329 	ILC_COLOR    = 0,
13330 	ILC_COLOR4   = 4,
13331 	ILC_COLOR8   = 8,
13332 	ILC_COLOR16  = 16,
13333 	ILC_COLOR24  = 24,
13334 	ILC_COLOR32  = 32,
13335 	ILC_COLORDDB = 254,
13336 	ILC_MASK     = 1,
13337 	ILC_PALETTE  = 2048
13338 }
13339 
13340 
13341 alias TBBUTTON*       PTBBUTTON, LPTBBUTTON;
13342 
13343 
13344 enum {
13345 	TB_ENABLEBUTTON          = WM_USER + 1,
13346 	TB_CHECKBUTTON,
13347 	TB_PRESSBUTTON,
13348 	TB_HIDEBUTTON,
13349 	TB_INDETERMINATE, //     = WM_USER + 5,
13350 	TB_ISBUTTONENABLED       = WM_USER + 9,
13351 	TB_ISBUTTONCHECKED,
13352 	TB_ISBUTTONPRESSED,
13353 	TB_ISBUTTONHIDDEN,
13354 	TB_ISBUTTONINDETERMINATE, // = WM_USER + 13,
13355 	TB_SETSTATE              = WM_USER + 17,
13356 	TB_GETSTATE              = WM_USER + 18,
13357 	TB_ADDBITMAP             = WM_USER + 19,
13358 	TB_DELETEBUTTON          = WM_USER + 22,
13359 	TB_GETBUTTON,
13360 	TB_BUTTONCOUNT,
13361 	TB_COMMANDTOINDEX,
13362 	TB_SAVERESTOREA,
13363 	TB_CUSTOMIZE,
13364 	TB_ADDSTRINGA,
13365 	TB_GETITEMRECT,
13366 	TB_BUTTONSTRUCTSIZE,
13367 	TB_SETBUTTONSIZE,
13368 	TB_SETBITMAPSIZE,
13369 	TB_AUTOSIZE, //          = WM_USER + 33,
13370 	TB_GETTOOLTIPS           = WM_USER + 35,
13371 	TB_SETTOOLTIPS           = WM_USER + 36,
13372 	TB_SETPARENT             = WM_USER + 37,
13373 	TB_SETROWS               = WM_USER + 39,
13374 	TB_GETROWS,
13375 	TB_GETBITMAPFLAGS,
13376 	TB_SETCMDID,
13377 	TB_CHANGEBITMAP,
13378 	TB_GETBITMAP,
13379 	TB_GETBUTTONTEXTA,
13380 	TB_REPLACEBITMAP, //     = WM_USER + 46,
13381 	TB_GETBUTTONSIZE         = WM_USER + 58,
13382 	TB_SETBUTTONWIDTH        = WM_USER + 59,
13383 	TB_GETBUTTONTEXTW        = WM_USER + 75,
13384 	TB_SAVERESTOREW          = WM_USER + 76,
13385 	TB_ADDSTRINGW            = WM_USER + 77,
13386 }
13387 
13388 extern(Windows)
13389 BOOL EnumChildWindows(HWND, WNDENUMPROC, LPARAM);
13390 
13391 alias extern(Windows) BOOL function (HWND, LPARAM) WNDENUMPROC;
13392 
13393 
13394 	enum {
13395 		TB_SETINDENT = WM_USER + 47,
13396 		TB_SETIMAGELIST,
13397 		TB_GETIMAGELIST,
13398 		TB_LOADIMAGES,
13399 		TB_GETRECT,
13400 		TB_SETHOTIMAGELIST,
13401 		TB_GETHOTIMAGELIST,
13402 		TB_SETDISABLEDIMAGELIST,
13403 		TB_GETDISABLEDIMAGELIST,
13404 		TB_SETSTYLE,
13405 		TB_GETSTYLE,
13406 		//TB_GETBUTTONSIZE,
13407 		//TB_SETBUTTONWIDTH,
13408 		TB_SETMAXTEXTROWS,
13409 		TB_GETTEXTROWS // = WM_USER + 61
13410 	}
13411 
13412 enum {
13413 	CCM_FIRST            = 0x2000,
13414 	CCM_LAST             = CCM_FIRST + 0x200,
13415 	CCM_SETBKCOLOR       = 8193,
13416 	CCM_SETCOLORSCHEME   = 8194,
13417 	CCM_GETCOLORSCHEME   = 8195,
13418 	CCM_GETDROPTARGET    = 8196,
13419 	CCM_SETUNICODEFORMAT = 8197,
13420 	CCM_GETUNICODEFORMAT = 8198,
13421 	CCM_SETVERSION       = 0x2007,
13422 	CCM_GETVERSION       = 0x2008,
13423 	CCM_SETNOTIFYWINDOW  = 0x2009
13424 }
13425 
13426 
13427 enum {
13428 	PBM_SETRANGE     = WM_USER + 1,
13429 	PBM_SETPOS,
13430 	PBM_DELTAPOS,
13431 	PBM_SETSTEP,
13432 	PBM_STEPIT,   // = WM_USER + 5
13433 	PBM_SETRANGE32   = 1030,
13434 	PBM_GETRANGE,
13435 	PBM_GETPOS,
13436 	PBM_SETBARCOLOR, // = 1033
13437 	PBM_SETBKCOLOR   = CCM_SETBKCOLOR
13438 }
13439 
13440 enum {
13441 	PBS_SMOOTH   = 1,
13442 	PBS_VERTICAL = 4
13443 }
13444 
13445 enum {
13446         ICC_LISTVIEW_CLASSES = 1,
13447         ICC_TREEVIEW_CLASSES = 2,
13448         ICC_BAR_CLASSES      = 4,
13449         ICC_TAB_CLASSES      = 8,
13450         ICC_UPDOWN_CLASS     = 16,
13451         ICC_PROGRESS_CLASS   = 32,
13452         ICC_HOTKEY_CLASS     = 64,
13453         ICC_ANIMATE_CLASS    = 128,
13454         ICC_WIN95_CLASSES    = 255,
13455         ICC_DATE_CLASSES     = 256,
13456         ICC_USEREX_CLASSES   = 512,
13457         ICC_COOL_CLASSES     = 1024,
13458 	ICC_STANDARD_CLASSES = 0x00004000,
13459 }
13460 
13461 	enum WM_USER = 1024;
13462 }
13463 
13464 version(win32_widgets)
13465 	pragma(lib, "comdlg32");
13466 
13467 
13468 ///
13469 enum GenericIcons : ushort {
13470 	None, ///
13471 	// these happen to match the win32 std icons numerically if you just subtract one from the value
13472 	Cut, ///
13473 	Copy, ///
13474 	Paste, ///
13475 	Undo, ///
13476 	Redo, ///
13477 	Delete, ///
13478 	New, ///
13479 	Open, ///
13480 	Save, ///
13481 	PrintPreview, ///
13482 	Properties, ///
13483 	Help, ///
13484 	Find, ///
13485 	Replace, ///
13486 	Print, ///
13487 }
13488 
13489 enum FileDialogType {
13490 	Automatic,
13491 	Open,
13492 	Save
13493 }
13494 string previousFileReferenced;
13495 
13496 /++
13497 	Used in automatic menu functions to indicate that the user should be able to browse for a file.
13498 
13499 	Params:
13500 		storage = an alias to a `static string` variable that stores the last file referenced. It will
13501 		use this to pre-fill the dialog with a suggestion.
13502 
13503 		Please note that it MUST be `static` or you will get compile errors.
13504 
13505 		filters = the filters param to [getFileName]
13506 
13507 		type = the type if dialog to show. If `FileDialogType.Automatic`, it the driver code will
13508 		guess based on the function name. If it has the word "Save" or "Export" in it, it will show
13509 		a save dialog box. Otherwise, it will show an open dialog box.
13510 +/
13511 struct FileName(alias storage = previousFileReferenced, string[] filters = null, FileDialogType type = FileDialogType.Automatic) {
13512 	string name;
13513 	alias name this;
13514 }
13515 
13516 /++
13517 	History:
13518 		onCancel was added November 6, 2021.
13519 
13520 		The dialog itself on Linux was modified on December 2, 2021 to include
13521 		a directory picker in addition to the command line completion view.
13522 
13523 		The `initialDirectory` argument was added November 9, 2022 (dub v10.10)
13524 	Future_directions:
13525 		I want to add some kind of custom preview and maybe thumbnail thing in the future,
13526 		at least on Linux, maybe on Windows too.
13527 +/
13528 void getOpenFileName(
13529 	void delegate(string) onOK,
13530 	string prefilledName = null,
13531 	string[] filters = null,
13532 	void delegate() onCancel = null,
13533 	string initialDirectory = null,
13534 )
13535 {
13536 	return getFileName(true, onOK, prefilledName, filters, onCancel, initialDirectory);
13537 }
13538 
13539 /// ditto
13540 void getSaveFileName(
13541 	void delegate(string) onOK,
13542 	string prefilledName = null,
13543 	string[] filters = null,
13544 	void delegate() onCancel = null,
13545 	string initialDirectory = null, 
13546 )
13547 {
13548 	return getFileName(false, onOK, prefilledName, filters, onCancel, initialDirectory);
13549 }
13550 
13551 void getFileName(
13552 	bool openOrSave,
13553 	void delegate(string) onOK,
13554 	string prefilledName = null,
13555 	string[] filters = null, // format here is like ["Text files\0*.txt;*.text", "Image files\0*.png;*.jpg"]
13556 	void delegate() onCancel = null,
13557 	string initialDirectory = null,
13558 )
13559 {
13560 
13561 	version(win32_widgets) {
13562 		import core.sys.windows.commdlg;
13563 	/*
13564 	Ofn.lStructSize = sizeof(OPENFILENAME); 
13565 	Ofn.hwndOwner = hWnd; 
13566 	Ofn.lpstrFilter = szFilter; 
13567 	Ofn.lpstrFile= szFile; 
13568 	Ofn.nMaxFile = sizeof(szFile)/ sizeof(*szFile); 
13569 	Ofn.lpstrFileTitle = szFileTitle; 
13570 	Ofn.nMaxFileTitle = sizeof(szFileTitle); 
13571 	Ofn.lpstrInitialDir = (LPSTR)NULL; 
13572 	Ofn.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT; 
13573 	Ofn.lpstrTitle = szTitle; 
13574 	 */
13575 
13576 
13577 		wchar[1024] file = 0;
13578 		wchar[1024] filterBuffer = 0;
13579 		makeWindowsString(prefilledName, file[]);
13580 		OPENFILENAME ofn;
13581 		ofn.lStructSize = ofn.sizeof;
13582 		if(filters.length) {
13583 			string filter;
13584 			foreach(i, f; filters) {
13585 				filter ~= f;
13586 				filter ~= "\0";
13587 			}
13588 			filter ~= "\0";
13589 			ofn.lpstrFilter = makeWindowsString(filter, filterBuffer[], 0 /* already terminated */).ptr;
13590 		}
13591 		ofn.lpstrFile = file.ptr;
13592 		ofn.nMaxFile = file.length;
13593 
13594 		wchar[1024] initialDir = 0;
13595 		if(initialDirectory !is null) {
13596 			makeWindowsString(initialDirectory, initialDir[]);
13597 			ofn.lpstrInitialDir = file.ptr;
13598 		}
13599 
13600 		if(openOrSave ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn)) 
13601 		{
13602 			string okString = makeUtf8StringFromWindowsString(ofn.lpstrFile);
13603 			if(okString.length && okString[$-1] == '\0')
13604 				okString = okString[0..$-1];
13605 			onOK(okString);
13606 		} else {
13607 			if(onCancel)
13608 				onCancel();
13609 		}
13610 	} else version(custom_widgets) {
13611 		if(filters.length == 0)
13612 			filters = ["All Files\0*.*"];
13613 		auto picker = new FilePicker(prefilledName, filters, initialDirectory);
13614 		picker.onOK = onOK;
13615 		picker.onCancel = onCancel;
13616 		picker.show();
13617 	}
13618 }
13619 
13620 version(custom_widgets)
13621 private
13622 class FilePicker : Dialog {
13623 	void delegate(string) onOK;
13624 	void delegate() onCancel;
13625 	LineEdit lineEdit;
13626 
13627 	enum GetFilesResult {
13628 		success,
13629 		fileNotFound
13630 	}
13631 	static GetFilesResult getFiles(string cwd, scope void delegate(string name, bool isDirectory) dg) {
13632 		version(Windows) {
13633 			WIN32_FIND_DATA data;
13634 			WCharzBuffer search = WCharzBuffer(cwd ~ "/*");
13635 			auto handle = FindFirstFileW(search.ptr, &data);
13636 			scope(exit) if(handle !is INVALID_HANDLE_VALUE) FindClose(handle);
13637 			if(handle is INVALID_HANDLE_VALUE) {
13638 				if(GetLastError() == ERROR_FILE_NOT_FOUND)
13639 					return GetFilesResult.fileNotFound;
13640 				throw new WindowsApiException("FindFirstFileW");
13641 			}
13642 
13643 			try_more:
13644 
13645 			string name = makeUtf8StringFromWindowsString(data.cFileName[0 .. findIndexOfZero(data.cFileName[])]);
13646 
13647 			dg(name, (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? true : false);
13648 
13649 			auto ret = FindNextFileW(handle, &data);
13650 			if(ret == 0) {
13651 				if(GetLastError() == ERROR_NO_MORE_FILES)
13652 					return GetFilesResult.success;
13653 				throw new WindowsApiException("FindNextFileW");
13654 			}
13655 
13656 			goto try_more;
13657 
13658 		} else version(Posix) {
13659 			import core.sys.posix.dirent;
13660 			auto dir = opendir((cwd ~ "\0").ptr);
13661 			scope(exit)
13662 				if(dir) closedir(dir);
13663 			if(dir is null)
13664 				throw new ErrnoApiException("opendir [" ~ cwd ~ "]");
13665 
13666 			auto dirent = readdir(dir);
13667 			if(dirent is null)
13668 				return GetFilesResult.fileNotFound;
13669 
13670 			try_more:
13671 
13672 			string name = dirent.d_name[0 .. findIndexOfZero(dirent.d_name[])].idup;
13673 
13674 			dg(name, dirent.d_type == DT_DIR);
13675 
13676 			dirent = readdir(dir);
13677 			if(dirent is null)
13678 				return GetFilesResult.success;
13679 
13680 			goto try_more;
13681 		} else static assert(0);
13682 	}
13683 
13684 	// returns common prefix
13685 	string loadFiles(string cwd, string[] filters...) {
13686 		string[] files;
13687 		string[] dirs;
13688 
13689 		string commonPrefix;
13690 
13691 		getFiles(cwd, (string name, bool isDirectory) {
13692 			if(name == ".")
13693 				return; // skip this as unnecessary
13694 			if(isDirectory)
13695 				dirs ~= name;
13696 			else {
13697 				foreach(filter; filters)
13698 				if(
13699 					filter.length <= 1 ||
13700 					filter == "*.*" ||
13701 					(filter[0] == '*' && name.endsWith(filter[1 .. $])) ||
13702 					(filter[$-1] == '*' && name.startsWith(filter[0 .. $ - 1]))
13703 				)
13704 				{
13705 					files ~= name;
13706 
13707 					if(filter.length > 0 && filter[$-1] == '*') {
13708 						if(commonPrefix is null) {
13709 							commonPrefix = name;
13710 						} else {
13711 							foreach(idx, char i; name) {
13712 								if(idx >= commonPrefix.length || i != commonPrefix[idx]) {
13713 									commonPrefix = commonPrefix[0 .. idx];
13714 									break;
13715 								}
13716 							}
13717 						}
13718 					}
13719 
13720 					break;
13721 				}
13722 			}
13723 		});
13724 
13725 		extern(C) static int comparator(scope const void* a, scope const void* b) {
13726 			auto sa = *cast(string*) a;
13727 			auto sb = *cast(string*) b;
13728 
13729 			for(int i = 0; i < sa.length; i++) {
13730 				if(i == sb.length)
13731 					return 1;
13732 				return sa[i] - sb[i];
13733 			}
13734 
13735 			return 0;
13736 		}
13737 
13738 		nonPhobosSort(files, &comparator);
13739 		nonPhobosSort(dirs, &comparator);
13740 
13741 		listWidget.clear();
13742 		dirWidget.clear();
13743 		foreach(name; dirs)
13744 			dirWidget.addOption(name);
13745 		foreach(name; files)
13746 			listWidget.addOption(name);
13747 
13748 		return commonPrefix;
13749 	}
13750 
13751 	ListWidget listWidget;
13752 	ListWidget dirWidget;
13753 
13754 	string currentDirectory;
13755 	string[] processedFilters;
13756 
13757 	//string[] filters = null, // format here is like ["Text files\0*.txt;*.text", "Image files\n*.png;*.jpg"]
13758 	this(string prefilledName, string[] filters, string initialDirectory, Window owner = null) {
13759 		super(300, 200, "Choose File..."); // owner);
13760 
13761 		foreach(filter; filters) {
13762 			while(filter.length && filter[0] != 0) {
13763 				filter = filter[1 .. $];
13764 			}
13765 			if(filter.length)
13766 				filter = filter[1 .. $]; // trim off the 0
13767 
13768 			while(filter.length) {
13769 				int idx = 0;
13770 				while(idx < filter.length && filter[idx] != ';') {
13771 					idx++;
13772 				}
13773 
13774 				processedFilters ~= filter[0 .. idx];
13775 				if(idx < filter.length)
13776 					idx++; // skip the ;
13777 				filter = filter[idx .. $];
13778 			}
13779 		}
13780 
13781 		currentDirectory = initialDirectory is null ? "." : initialDirectory;
13782 
13783 		{
13784 			auto hl = new HorizontalLayout(this);
13785 			dirWidget = new ListWidget(hl);
13786 			listWidget = new ListWidget(hl);
13787 
13788 			// double click events normally trigger something else but
13789 			// here user might be clicking kinda fast and we'd rather just
13790 			// keep it
13791 			dirWidget.addEventListener((scope DoubleClickEvent dev) {
13792 				auto ce = new ChangeEvent!void(dirWidget, () {});
13793 				ce.dispatch();
13794 			});
13795 
13796 			dirWidget.addEventListener((scope ChangeEvent!void sce) {
13797 				string v;
13798 				foreach(o; dirWidget.options)
13799 					if(o.selected) {
13800 						v = o.label;
13801 						break;
13802 					}
13803 				if(v.length) {
13804 					currentDirectory ~= "/" ~ v;
13805 					loadFiles(currentDirectory, processedFilters);
13806 				}
13807 			});
13808 
13809 			// double click here, on the other hand, selects the file
13810 			// and moves on
13811 			listWidget.addEventListener((scope DoubleClickEvent dev) {
13812 				OK();
13813 			});
13814 		}
13815 
13816 		lineEdit = new LineEdit(this);
13817 		lineEdit.focus();
13818 		lineEdit.addEventListener(delegate(CharEvent event) {
13819 			if(event.character == '\t' || event.character == '\n')
13820 				event.preventDefault();
13821 		});
13822 
13823 		listWidget.addEventListener(EventType.change, () {
13824 			foreach(o; listWidget.options)
13825 				if(o.selected)
13826 					lineEdit.content = o.label;
13827 		});
13828 
13829 		loadFiles(currentDirectory, processedFilters);
13830 
13831 		lineEdit.addEventListener((KeyDownEvent event) {
13832 			if(event.key == Key.Tab) {
13833 
13834 				auto current = lineEdit.content;
13835 				if(current.length >= 2 && current[0 ..2] == "./")
13836 					current = current[2 .. $];
13837 
13838 				auto commonPrefix = loadFiles(".", current ~ "*");
13839 
13840 				if(commonPrefix.length)
13841 					lineEdit.content = commonPrefix;
13842 
13843 				// FIXME: if that is a directory, add the slash? or even go inside?
13844 
13845 				event.preventDefault();
13846 			}
13847 		});
13848 
13849 		lineEdit.content = prefilledName;
13850 
13851 		auto hl = new HorizontalLayout(60, this);
13852 		auto cancelButton = new Button("Cancel", hl);
13853 		auto okButton = new Button("OK", hl);
13854 
13855 		cancelButton.addEventListener(EventType.triggered, &Cancel);
13856 		okButton.addEventListener(EventType.triggered, &OK);
13857 
13858 		this.addEventListener((KeyDownEvent event) {
13859 			if(event.key == Key.Enter || event.key == Key.PadEnter) {
13860 				event.preventDefault();
13861 				OK();
13862 			}
13863 			if(event.key == Key.Escape)
13864 				Cancel();
13865 		});
13866 
13867 	}
13868 
13869 	override void OK() {
13870 		if(lineEdit.content.length) {
13871 			string accepted;
13872 			auto c = lineEdit.content;
13873 			if(c.length && c[0] == '/')
13874 				accepted = c;
13875 			else
13876 				accepted = currentDirectory ~ "/" ~ lineEdit.content;
13877 
13878 			if(isDir(accepted)) {
13879 				// FIXME: would be kinda nice to support ~ and collapse these paths too
13880 				// FIXME: would also be nice to actually show the "Looking in..." directory and maybe the filters but later.
13881 				currentDirectory = accepted;
13882 				loadFiles(currentDirectory, processedFilters);
13883 				lineEdit.content = "";
13884 				return;
13885 			}
13886 
13887 			if(onOK)
13888 				onOK(accepted);
13889 		}
13890 		close();
13891 	}
13892 
13893 	override void Cancel() {
13894 		if(onCancel)
13895 			onCancel();
13896 		close();
13897 	}
13898 }
13899 
13900 private bool isDir(string name) {
13901 	version(Windows) {
13902 		auto ws = WCharzBuffer(name);
13903 		auto ret = GetFileAttributesW(ws.ptr);
13904 		if(ret == INVALID_FILE_ATTRIBUTES)
13905 			return false;
13906 		return (ret & FILE_ATTRIBUTE_DIRECTORY) != 0;
13907 	} else version(Posix) {
13908 		import core.sys.posix.sys.stat;
13909 		stat_t buf;
13910 		auto ret = stat((name ~ '\0').ptr, &buf);
13911 		if(ret == -1)
13912 			return false; // I could probably check more specific errors tbh
13913 		return (buf.st_mode & S_IFMT) == S_IFDIR;
13914 	} else return false;
13915 }
13916 
13917 /*
13918 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775947%28v=vs.85%29.aspx#check_boxes
13919 http://msdn.microsoft.com/en-us/library/windows/desktop/ms633574%28v=vs.85%29.aspx
13920 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775943%28v=vs.85%29.aspx
13921 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775951%28v=vs.85%29.aspx
13922 http://msdn.microsoft.com/en-us/library/windows/desktop/ms632680%28v=vs.85%29.aspx
13923 http://msdn.microsoft.com/en-us/library/windows/desktop/ms644996%28v=vs.85%29.aspx#message_box
13924 http://www.sbin.org/doc/Xlib/chapt_03.html
13925 
13926 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760433%28v=vs.85%29.aspx
13927 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760446%28v=vs.85%29.aspx
13928 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760443%28v=vs.85%29.aspx
13929 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760476%28v=vs.85%29.aspx
13930 */
13931 
13932 
13933 // These are all for setMenuAndToolbarFromAnnotatedCode
13934 /// This item in the menu will be preceded by a separator line
13935 /// Group: generating_from_code
13936 struct separator {}
13937 deprecated("It was misspelled, use separator instead") alias seperator = separator;
13938 /// Program-wide keyboard shortcut to trigger the action
13939 /// Group: generating_from_code
13940 struct accelerator { string keyString; }
13941 /// tells which menu the action will be on
13942 /// Group: generating_from_code
13943 struct menu { string name; }
13944 /// Describes which toolbar section the action appears on
13945 /// Group: generating_from_code
13946 struct toolbar { string groupName; }
13947 ///
13948 /// Group: generating_from_code
13949 struct icon { ushort id; }
13950 ///
13951 /// Group: generating_from_code
13952 struct label { string label; }
13953 ///
13954 /// Group: generating_from_code
13955 struct hotkey { dchar ch; }
13956 ///
13957 /// Group: generating_from_code
13958 struct tip { string tip; }
13959 
13960 
13961 /++
13962 	Observes and allows inspection of an object via automatic gui
13963 +/
13964 /// Group: generating_from_code
13965 ObjectInspectionWindow objectInspectionWindow(T)(T t) if(is(T == class)) {
13966 	return new ObjectInspectionWindowImpl!(T)(t);
13967 }
13968 
13969 class ObjectInspectionWindow : Window {
13970 	this(int a, int b, string c) {
13971 		super(a, b, c);
13972 	}
13973 
13974 	abstract void readUpdatesFromObject();
13975 }
13976 
13977 class ObjectInspectionWindowImpl(T) : ObjectInspectionWindow {
13978 	T t;
13979 	this(T t) {
13980 		this.t = t;
13981 
13982 		super(300, 400, "ObjectInspectionWindow - " ~ T.stringof);
13983 
13984 		foreach(memberName; __traits(derivedMembers, T)) {{
13985 			alias member = I!(__traits(getMember, t, memberName))[0];
13986 			alias type = typeof(member);
13987 			static if(is(type == int)) {
13988 				auto le = new LabeledLineEdit(memberName ~ ": ", this);
13989 				//le.addEventListener("char", (Event ev) {
13990 					//if((ev.character < '0' || ev.character > '9') && ev.character != '-')
13991 						//ev.preventDefault();
13992 				//});
13993 				le.addEventListener(EventType.change, (Event ev) {
13994 					__traits(getMember, t, memberName) = cast(type) stringToLong(ev.stringValue);
13995 				});
13996 
13997 				updateMemberDelegates[memberName] = () {
13998 					le.content = toInternal!string(__traits(getMember, t, memberName));
13999 				};
14000 			}
14001 		}}
14002 	}
14003 
14004 	void delegate()[string] updateMemberDelegates;
14005 
14006 	override void readUpdatesFromObject() {
14007 		foreach(k, v; updateMemberDelegates)
14008 			v();
14009 	}
14010 }
14011 
14012 /++
14013 	Creates a dialog based on a data structure.
14014 
14015 	---
14016 	dialog((YourStructure value) {
14017 		// the user filled in the struct and clicked OK,
14018 		// you can check the members now
14019 	});
14020 	---
14021 
14022 	Params:
14023 		initialData = the initial value to show in the dialog. It will not modify this unless
14024 		it is a class then it might, no promises.
14025 
14026 	History:
14027 		The overload that lets you specify `initialData` was added on December 30, 2021 (dub v10.5)
14028 +/
14029 /// Group: generating_from_code
14030 void dialog(T)(void delegate(T) onOK, void delegate() onCancel = null, string title = T.stringof) {
14031 	dialog(T.init, onOK, onCancel, title);
14032 }
14033 /// ditto
14034 void dialog(T)(T initialData, void delegate(T) onOK, void delegate() onCancel = null, string title = T.stringof) {
14035 	auto dg = new AutomaticDialog!T(initialData, onOK, onCancel, title);
14036 	dg.show();
14037 }
14038 
14039 private static template I(T...) { alias I = T; }
14040 
14041 
14042 private string beautify(string name, char space = ' ', bool allLowerCase = false) {
14043 	if(name == "id")
14044 		return allLowerCase ? name : "ID";
14045 
14046 	char[160] buffer;
14047 	int bufferIndex = 0;
14048 	bool shouldCap = true;
14049 	bool shouldSpace;
14050 	bool lastWasCap;
14051 	foreach(idx, char ch; name) {
14052 		if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
14053 
14054 		if((ch >= 'A' && ch <= 'Z') || ch == '_') {
14055 			if(lastWasCap) {
14056 				// two caps in a row, don't change. Prolly acronym.
14057 			} else {
14058 				if(idx)
14059 					shouldSpace = true; // new word, add space
14060 			}
14061 
14062 			lastWasCap = true;
14063 		} else {
14064 			lastWasCap = false;
14065 		}
14066 
14067 		if(shouldSpace) {
14068 			buffer[bufferIndex++] = space;
14069 			if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
14070 			shouldSpace = false;
14071 		}
14072 		if(shouldCap) {
14073 			if(ch >= 'a' && ch <= 'z')
14074 				ch -= 32;
14075 			shouldCap = false;
14076 		}
14077 		if(allLowerCase && ch >= 'A' && ch <= 'Z')
14078 			ch += 32;
14079 		buffer[bufferIndex++] = ch;
14080 	}
14081 	return buffer[0 .. bufferIndex].idup;
14082 }
14083 
14084 /++
14085 	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.
14086 +/
14087 class AutomaticDialog(T) : Dialog {
14088 	T t;
14089 
14090 	void delegate(T) onOK;
14091 	void delegate() onCancel;
14092 
14093 	override int paddingTop() { return defaultLineHeight; }
14094 	override int paddingBottom() { return defaultLineHeight; }
14095 	override int paddingRight() { return defaultLineHeight; }
14096 	override int paddingLeft() { return defaultLineHeight; }
14097 
14098 	this(T initialData, void delegate(T) onOK, void delegate() onCancel, string title) {
14099 		assert(onOK !is null);
14100 
14101 		t = initialData;
14102 
14103 		static if(is(T == class)) {
14104 			if(t is null)
14105 				t = new T();
14106 		}
14107 		this.onOK = onOK;
14108 		this.onCancel = onCancel;
14109 		super(400, cast(int)(__traits(allMembers, T).length * 2) * (defaultLineHeight + 4 + 2) + Window.lineHeight + 56, title);
14110 
14111 		static if(is(T == class))
14112 			this.addDataControllerWidget(t);
14113 		else
14114 			this.addDataControllerWidget(&t);
14115 
14116 		auto hl = new HorizontalLayout(this);
14117 		auto stretch = new HorizontalSpacer(hl); // to right align
14118 		auto ok = new CommandButton("OK", hl);
14119 		auto cancel = new CommandButton("Cancel", hl);
14120 		ok.addEventListener(EventType.triggered, &OK);
14121 		cancel.addEventListener(EventType.triggered, &Cancel);
14122 
14123 		this.addEventListener((KeyDownEvent ev) {
14124 			if(ev.key == Key.Enter || ev.key == Key.PadEnter) {
14125 				ok.focus();
14126 				OK();
14127 				ev.preventDefault();
14128 			}
14129 			if(ev.key == Key.Escape) {
14130 				Cancel();
14131 				ev.preventDefault();
14132 			}
14133 		});
14134 
14135 		//this.children[0].focus();
14136 	}
14137 
14138 	override void OK() {
14139 		onOK(t);
14140 		close();
14141 	}
14142 
14143 	override void Cancel() {
14144 		if(onCancel)
14145 			onCancel();
14146 		close();
14147 	}
14148 }
14149 
14150 private template baseClassCount(Class) {
14151 	private int helper() {
14152 		int count = 0;
14153 		static if(is(Class bases == super)) {
14154 			foreach(base; bases)
14155 				static if(is(base == class))
14156 					count += 1 + baseClassCount!base;
14157 		}
14158 		return count;
14159 	}
14160 
14161 	enum int baseClassCount = helper();
14162 }
14163 
14164 private long stringToLong(string s) {
14165 	long ret;
14166 	if(s.length == 0)
14167 		return ret;
14168 	bool negative = s[0] == '-';
14169 	if(negative)
14170 		s = s[1 .. $];
14171 	foreach(ch; s) {
14172 		if(ch >= '0' && ch <= '9') {
14173 			ret *= 10;
14174 			ret += ch - '0';
14175 		}
14176 	}
14177 	if(negative)
14178 		ret = -ret;
14179 	return ret;
14180 }
14181 
14182 
14183 interface ReflectableProperties {
14184 	/++
14185 		Iterates the event's properties as strings. Note that keys may be repeated and a get property request may
14186 		call your sink with `null`. It it does, it means the key either doesn't request or cannot be represented by
14187 		json in the current implementation.
14188 
14189 		This is auto-implemented for you if you mixin [RegisterGetters] in your child classes and only have
14190 		properties of type `bool`, `int`, `double`, or `string`. For other ones, you will need to do it yourself
14191 		as of the June 2, 2021 release.
14192 
14193 		History:
14194 			Added June 2, 2021.
14195 
14196 		See_Also: [getPropertyAsString], [setPropertyFromString]
14197 	+/
14198 	void getPropertiesList(scope void delegate(string name) sink) const;// @nogc pure nothrow;
14199 	/++
14200 		Requests a property to be delivered to you as a string, through your `sink` delegate.
14201 
14202 		If the `value` is null, it means the property could not be retreived. If `valueIsJson`, it should
14203 		be interpreted as json, otherwise, it is just a plain string.
14204 
14205 		The sink should always be called exactly once for each call (it is basically a return value, but it might
14206 		use a local buffer it maintains instead of allocating a return value).
14207 
14208 		History:
14209 			Added June 2, 2021.
14210 
14211 		See_Also: [getPropertiesList], [setPropertyFromString]
14212 	+/
14213 	void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink);
14214 	/++
14215 		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.
14216 
14217 		History:
14218 			Added June 2, 2021.
14219 
14220 		See_Also: [getPropertiesList], [getPropertyAsString], [SetPropertyResult]
14221 	+/
14222 	SetPropertyResult setPropertyFromString(string name, scope const(char)[] str, bool strIsJson);
14223 
14224 	/// [setPropertyFromString] possible return values
14225 	enum SetPropertyResult {
14226 		success = 0, /// the property has been successfully set to the request value
14227 		notPermitted = -1, /// the property exists but it cannot be changed at this time
14228 		notImplemented = -2, /// the set function is not implemented for the given property (which may or may not exist)
14229 		noSuchProperty = -3, /// there is no property by that name
14230 		wrongFormat = -4, /// the string was given in the wrong format, e.g. passing "two" for an int value
14231 		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)
14232 	}
14233 
14234 	/++
14235 		You can mix this in to get an implementation in child classes. This does [setPropertyFromString].
14236 
14237 		Your original base class, however, must implement its own methods. I recommend doing the initial ones by hand.
14238 
14239 		For [Widget] and [Event], the library provides [Widget.Register] and [Event.Register] that call these for you, so you should
14240 		rarely need to use these building blocks directly.
14241 	+/
14242 	mixin template RegisterSetters() {
14243 		override SetPropertyResult setPropertyFromString(string name, scope const(char)[] value, bool valueIsJson) {
14244 			switch(name) {
14245 				foreach(memberName; __traits(derivedMembers, typeof(this))) {
14246 					case memberName:
14247 						static if(is(typeof(__traits(getMember, this, memberName)) : const bool)) {
14248 							if(value != "true" && value != "false")
14249 								return SetPropertyResult.wrongFormat;
14250 							__traits(getMember, this, memberName) = value == "true" ? true : false;
14251 							return SetPropertyResult.success;
14252 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const long)) {
14253 							import core.stdc.stdlib;
14254 							char[128] zero = 0;
14255 							if(buffer.length + 1 >= zero.length)
14256 								return SetPropertyResult.wrongFormat;
14257 							zero[0 .. buffer.length] = buffer[];
14258 							__traits(getMember, this, memberName) = strtol(buffer.ptr, null, 10);
14259 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const double)) {
14260 							import core.stdc.stdlib;
14261 							char[128] zero = 0;
14262 							if(buffer.length + 1 >= zero.length)
14263 								return SetPropertyResult.wrongFormat;
14264 							zero[0 .. buffer.length] = buffer[];
14265 							__traits(getMember, this, memberName) = strtod(buffer.ptr, null, 10);
14266 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const string)) {
14267 							__traits(getMember, this, memberName) = value.idup;
14268 						} else {
14269 							return SetPropertyResult.notImplemented;
14270 						}
14271 
14272 				}
14273 				default:
14274 					return super.setPropertyFromString(name, value, valueIsJson);
14275 			}
14276 		}
14277 	}
14278 
14279 	/++
14280 		You can mix this in to get an implementation in child classes. This does [getPropertyAsString] and [getPropertiesList].
14281 
14282 		Your original base class, however, must implement its own methods. I recommend doing the initial ones by hand.
14283 
14284 		For [Widget] and [Event], the library provides [Widget.Register] and [Event.Register] that call these for you, so you should
14285 		rarely need to use these building blocks directly.
14286 	+/
14287 	mixin template RegisterGetters() {
14288 		override void getPropertiesList(scope void delegate(string name) sink) const {
14289 			super.getPropertiesList(sink);
14290 
14291 			foreach(memberName; __traits(derivedMembers, typeof(this))) {
14292 				sink(memberName);
14293 			}
14294 		}
14295 		override void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink) {
14296 			switch(name) {
14297 				foreach(memberName; __traits(derivedMembers, typeof(this))) {
14298 					case memberName:
14299 						static if(is(typeof(__traits(getMember, this, memberName)) : const bool)) {
14300 							sink(name, __traits(getMember, this, memberName) ? "true" : "false", true);
14301 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const long)) {
14302 							import core.stdc.stdio;
14303 							char[32] buffer;
14304 							auto len = snprintf(buffer.ptr, buffer.length, "%lld", cast(long) __traits(getMember, this, memberName));
14305 							sink(name, buffer[0 .. len], true);
14306 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const double)) {
14307 							import core.stdc.stdio;
14308 							char[32] buffer;
14309 							auto len = snprintf(buffer.ptr, buffer.length, "%f", cast(double) __traits(getMember, this, memberName));
14310 							sink(name, buffer[0 .. len], true);
14311 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const string)) {
14312 							sink(name, __traits(getMember, this, memberName), false);
14313 							//sinkJsonString(memberName, __traits(getMember, this, memberName), sink);
14314 						} else {
14315 							sink(name, null, true);
14316 						}
14317 
14318 					return;
14319 				}
14320 				default:
14321 					return super.getPropertyAsString(name, sink);
14322 			}
14323 		}
14324 	}
14325 }
14326 
14327 private struct Stack(T) {
14328 	this(int maxSize) {
14329 		internalLength = 0;
14330 		arr = initialBuffer[];
14331 	}
14332 
14333 	///.
14334 	void push(T t) {
14335 		if(internalLength >= arr.length) {
14336 			auto oldarr = arr;
14337 			if(arr.length < 4096)
14338 				arr = new T[arr.length * 2];
14339 			else
14340 				arr = new T[arr.length + 4096];
14341 			arr[0 .. oldarr.length] = oldarr[];
14342 		}
14343 
14344 		arr[internalLength] = t;
14345 		internalLength++;
14346 	}
14347 
14348 	///.
14349 	T pop() {
14350 		assert(internalLength);
14351 		internalLength--;
14352 		return arr[internalLength];
14353 	}
14354 
14355 	///.
14356 	T peek() {
14357 		assert(internalLength);
14358 		return arr[internalLength - 1];
14359 	}
14360 
14361 	///.
14362 	@property bool empty() {
14363 		return internalLength ? false : true;
14364 	}
14365 
14366 	///.
14367 	private T[] arr;
14368 	private size_t internalLength;
14369 	private T[64] initialBuffer;
14370 	// 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),
14371 	// using this saves us a bunch of trips to the GC. In my last profiling, I got about a 50x improvement in the push()
14372 	// function thanks to this, and push() was actually one of the slowest individual functions in the code!
14373 }
14374 
14375 /// 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.
14376 private struct WidgetStream {
14377 
14378 	///.
14379 	@property Widget front() {
14380 		return current.widget;
14381 	}
14382 
14383 	/// Use Widget.tree instead.
14384 	this(Widget start) {
14385 		current.widget = start;
14386 		current.childPosition = -1;
14387 		isEmpty = false;
14388 		stack = typeof(stack)(0);
14389 	}
14390 
14391 	/*
14392 		Handle it
14393 		handle its children
14394 
14395 	*/
14396 
14397 	///.
14398 	void popFront() {
14399 	    more:
14400 	    	if(isEmpty) return;
14401 
14402 		// FIXME: the profiler says this function is somewhat slow (noticeable because it can be called a lot of times)
14403 
14404 		current.childPosition++;
14405 		if(current.childPosition >= current.widget.children.length) {
14406 			if(stack.empty())
14407 				isEmpty = true;
14408 			else {
14409 				current = stack.pop();
14410 				goto more;
14411 			}
14412 		} else {
14413 			stack.push(current);
14414 			current.widget = current.widget.children[current.childPosition];
14415 			current.childPosition = -1;
14416 		}
14417 	}
14418 
14419 	///.
14420 	@property bool empty() {
14421 		return isEmpty;
14422 	}
14423 
14424 	private:
14425 
14426 	struct Current {
14427 		Widget widget;
14428 		int childPosition;
14429 	}
14430 
14431 	Current current;
14432 
14433 	Stack!(Current) stack;
14434 
14435 	bool isEmpty;
14436 }
14437 
14438 
14439 /+
14440 
14441 	I could fix up the hierarchy kinda like this
14442 
14443 	class Widget {
14444 		Widget[] children() { return null; }
14445 	}
14446 	interface WidgetContainer {
14447 		Widget asWidget();
14448 		void addChild(Widget w);
14449 
14450 		// alias asWidget this; // but meh
14451 	}
14452 
14453 	Widget can keep a (Widget parent) ctor, but it should prolly deprecate and tell people to instead change their ctors to take WidgetContainer instead.
14454 
14455 	class Layout : Widget, WidgetContainer {}
14456 
14457 	class Window : WidgetContainer {}
14458 
14459 
14460 	All constructors that previously took Widgets should now take WidgetContainers instead
14461 
14462 
14463 
14464 	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".
14465 +/
14466 
14467 /+
14468 	LAYOUTS 2.0
14469 
14470 	can just be assigned as a function. assigning a new one will cause it to be immediately called.
14471 
14472 	they simply are responsible for the recomputeChildLayout. If this pointer is null, it uses the default virtual one.
14473 
14474 	recomputeChildLayout only really needs a property accessor proxy... just the layout info too.
14475 
14476 	and even Paint can just use computedStyle...
14477 
14478 		background color
14479 		font
14480 		border color and style
14481 
14482 	And actually the style proxy can offer some helper routines to draw these like the draw 3d box
14483 		please note that many widgets and in some modes will completely ignore properties as they will.
14484 		they are just hints you set, not promises.
14485 
14486 
14487 
14488 
14489 
14490 	So generally the existing virtual functions are just the default for the class. But individual objects
14491 	or stylesheets can override this. The virtual ones count as tag-level specificity in css.
14492 +/
14493 
14494 /++
14495 	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.
14496 
14497 	History:
14498 		Added May 24, 2021.
14499 +/
14500 struct WidgetBackground {
14501 	/++
14502 		A background with the given solid color.
14503 	+/
14504 	this(Color color) {
14505 		this.color = color;
14506 	}
14507 
14508 	this(WidgetBackground bg) {
14509 		this = bg;
14510 	}
14511 
14512 	/++
14513 		Creates a widget from the string.
14514 
14515 		Currently, it only supports solid colors via [Color.fromString], but it will likely be expanded in the future to something more like css.
14516 	+/
14517 	static WidgetBackground fromString(string s) {
14518 		return WidgetBackground(Color.fromString(s));
14519 	}
14520 
14521 	private Color color;
14522 }
14523 
14524 /++
14525 	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!)
14526 
14527 	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.
14528 
14529 	You should not inherit from this directly, but instead use [VisualTheme].
14530 
14531 	History:
14532 		Added May 8, 2021
14533 +/
14534 abstract class BaseVisualTheme {
14535 	/// Don't implement this, instead use [VisualTheme] and implement `paint` methods on specific subclasses you want to override.
14536 	abstract void doPaint(Widget widget, WidgetPainter painter);
14537 
14538 	/+
14539 	/// Don't implement this, instead use [VisualTheme] and implement `StyleOverride` aliases on specific subclasses you want to override.
14540 	abstract void useStyleProperties(Widget w, scope void delegate(scope Widget.Style props) dg);
14541 	+/
14542 
14543 	/++
14544 		Returns the property as a string, or null if it was not overridden in the style definition. The idea here is something like css,
14545 		where the interpretation of the string varies for each property and may include things like measurement units.
14546 	+/
14547 	abstract string getPropertyString(Widget widget, string propertyName);
14548 
14549 	/++
14550 		Default background color of the window. Widgets also use this to simulate transparency.
14551 
14552 		Probably some shade of grey.
14553 	+/
14554 	abstract Color windowBackgroundColor();
14555 	abstract Color widgetBackgroundColor();
14556 	abstract Color foregroundColor();
14557 	abstract Color lightAccentColor();
14558 	abstract Color darkAccentColor();
14559 
14560 	/++
14561 		Color used to indicate active selections in lists and text boxes, etc.
14562 	+/
14563 	abstract Color selectionColor();
14564 
14565 	abstract OperatingSystemFont defaultFont();
14566 
14567 	private OperatingSystemFont defaultFontCache_;
14568 	private bool defaultFontCachePopulated;
14569 	private OperatingSystemFont defaultFontCached() {
14570 		if(!defaultFontCachePopulated) {
14571 			// FIXME: set this to false if X disconnect or if visual theme changes
14572 			defaultFontCache_ = defaultFont();
14573 			defaultFontCachePopulated = true;
14574 		}
14575 		return defaultFontCache_;
14576 	}
14577 }
14578 
14579 /+
14580 	A widget should have:
14581 		classList
14582 		dataset
14583 		attributes
14584 		computedStyles
14585 		state (persistent)
14586 		dynamic state (focused, hover, etc)
14587 +/
14588 
14589 // visualTheme.computedStyle(this).paddingLeft
14590 
14591 
14592 /++
14593 	This is your entry point to create your own visual theme for custom widgets.
14594 +/
14595 abstract class VisualTheme(CRTP) : BaseVisualTheme {
14596 	override string getPropertyString(Widget widget, string propertyName) {
14597 		return null;
14598 	}
14599 
14600 	/+
14601 		mixin StyleOverride!Widget
14602 	final override void useStyleProperties(Widget w, scope void delegate(scope Widget.Style props) dg) {
14603 		w.useStyleProperties(dg);
14604 	}
14605 	+/
14606 
14607 	final override void doPaint(Widget widget, WidgetPainter painter) {
14608 		auto derived = cast(CRTP) cast(void*) this;
14609 
14610 		scope void delegate(Widget, WidgetPainter) bestMatch;
14611 		int bestMatchScore;
14612 
14613 		static if(__traits(hasMember, CRTP, "paint"))
14614 		foreach(overload; __traits(getOverloads, CRTP, "paint")) {
14615 			static if(is(typeof(overload) Params == __parameters)) {
14616 				static assert(Params.length == 2);
14617 				static assert(is(Params[0] : Widget));
14618 				static assert(is(Params[1] == WidgetPainter));
14619 				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);
14620 
14621 				alias type = Params[0];
14622 				if(cast(type) widget) {
14623 					auto score = baseClassCount!type;
14624 
14625 					if(score > bestMatchScore) {
14626 						bestMatch = cast(typeof(bestMatch)) &__traits(child, derived, overload);
14627 						bestMatchScore = score;
14628 					}
14629 				}
14630 			} else static assert(0, "paint should be a method.");
14631 		}
14632 
14633 		if(bestMatch)
14634 			bestMatch(widget, painter);
14635 		else
14636 			widget.paint(painter);
14637 	}
14638 
14639 	// 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
14640 	override Color windowBackgroundColor() { return Color(212, 212, 212); }
14641 	override Color widgetBackgroundColor() { return Color.white; }
14642 	override Color foregroundColor() { return Color.black; }
14643 	override Color darkAccentColor() { return Color(172, 172, 172); }
14644 	override Color lightAccentColor() { return Color(223, 223, 223); }
14645 	override Color selectionColor() { return Color(0, 0, 128); }
14646 	override OperatingSystemFont defaultFont() { return null; } // will just use the default out of simpledisplay's xfontstr
14647 
14648 	private static struct Cached {
14649 		// i prolly want to do this
14650 	}
14651 }
14652 
14653 final class DefaultVisualTheme : VisualTheme!DefaultVisualTheme {
14654 	/+
14655 	OperatingSystemFont defaultFont() { return new OperatingSystemFont("Times New Roman", 8, FontWeight.medium); }
14656 	Color windowBackgroundColor() { return Color(242, 242, 242); }
14657 	Color darkAccentColor() { return windowBackgroundColor; }
14658 	Color lightAccentColor() { return windowBackgroundColor; }
14659 	+/
14660 }
14661 
14662 /++
14663 	Event fired when an [Observeable] variable changes. You will want to add an event listener referencing
14664 	the field like `widget.addEventListener((scope StateChanged!(Whatever.field) ev) { });`
14665 
14666 	History:
14667 		Moved from minigui_addons.webview to main minigui on November 27, 2021 (dub v10.4)
14668 +/
14669 class StateChanged(alias field) : Event {
14670 	enum EventString = __traits(identifier, __traits(parent, field)) ~ "." ~ __traits(identifier, field) ~ ":change";
14671 	override bool cancelable() const { return false; }
14672 	this(Widget target, typeof(field) newValue) {
14673 		this.newValue = newValue;
14674 		super(EventString, target);
14675 	}
14676 
14677 	typeof(field) newValue;
14678 }
14679 
14680 /++
14681 	Convenience function to add a `triggered` event listener.
14682 
14683 	Its implementation is simply `w.addEventListener("triggered", dg);`
14684 
14685 	History:
14686 		Added November 27, 2021 (dub v10.4)
14687 +/
14688 void addWhenTriggered(Widget w, void delegate() dg) {
14689 	w.addEventListener("triggered", dg);
14690 }
14691 
14692 /++
14693 	Observable varables can be added to widgets and when they are changed, it fires
14694 	off a [StateChanged] event so you can react to it.
14695 
14696 	It is implemented as a getter and setter property, along with another helper you
14697 	can use to subscribe whith is `name_changed`. You can also subscribe to the [StateChanged]
14698 	event through the usual means. Just give the name of the variable. See [StateChanged] for an
14699 	example.
14700 
14701 	History:
14702 		Moved from minigui_addons.webview to main minigui on November 27, 2021 (dub v10.4)
14703 +/
14704 mixin template Observable(T, string name) {
14705 	private T backing;
14706 
14707 	mixin(q{
14708 		void } ~ name ~ q{_changed (void delegate(T) dg) {
14709 			this.addEventListener((StateChanged!this_thing ev) {
14710 				dg(ev.newValue);
14711 			});
14712 		}
14713 
14714 		@property T } ~ name ~ q{ () {
14715 			return backing;
14716 		}
14717 
14718 		@property void } ~ name ~ q{ (T t) {
14719 			backing = t;
14720 			auto event = new StateChanged!this_thing(this, t);
14721 			event.dispatch();
14722 		}
14723 	});
14724 
14725 	mixin("private alias this_thing = " ~ name ~ ";");
14726 }
14727 
14728 
14729 private bool startsWith(string test, string thing) {
14730 	if(test.length < thing.length)
14731 		return false;
14732 	return test[0 .. thing.length] == thing;
14733 }
14734 
14735 private bool endsWith(string test, string thing) {
14736 	if(test.length < thing.length)
14737 		return false;
14738 	return test[$ - thing.length .. $] == thing;
14739 }
14740 
14741 // still do layout delegation
14742 // and... split off Window from Widget.
14743 
14744 version(minigui_screenshots)
14745 struct Screenshot {
14746 	string name;
14747 }
14748 
14749 version(minigui_screenshots)
14750 static if(__VERSION__ > 2092)
14751 mixin(q{
14752 shared static this() {
14753 	import core.runtime;
14754 
14755 	static UnitTestResult screenshotMagic() {
14756 		string name;
14757 
14758 		import arsd.png;
14759 
14760 		auto results = new Window();
14761 		auto button = new Button("do it", results);
14762 
14763 		Window.newWindowCreated = delegate(Window w) {
14764 			Timer timer;
14765 			timer = new Timer(250, {
14766 				auto img = w.win.takeScreenshot();
14767 				timer.destroy();
14768 
14769 				version(Windows)
14770 					writePng("/var/www/htdocs/minigui-screenshots/windows/" ~ name ~ ".png", img);
14771 				else
14772 					writePng("/var/www/htdocs/minigui-screenshots/linux/" ~ name ~ ".png", img);
14773 
14774 				w.close();
14775 			});
14776 		};
14777 
14778 		button.addWhenTriggered( {
14779 
14780 		foreach(test; __traits(getUnitTests, mixin(__MODULE__))) {
14781 			name = null;
14782 			static foreach(attr; __traits(getAttributes, test)) {
14783 				static if(is(typeof(attr) == Screenshot))
14784 					name = attr.name;
14785 			}
14786 			if(name.length) {
14787 				test();
14788 			}
14789 		}
14790 
14791 		});
14792 
14793 		results.loop();
14794 
14795 		return UnitTestResult(0, 0, false, false);
14796 	}
14797 
14798 
14799 	Runtime.extendedModuleUnitTester = &screenshotMagic;
14800 }
14801 });
14802 version(minigui_screenshots) {
14803 	version(unittest)
14804 		void main() {}
14805 	else static assert(0, "dont forget the -unittest flag to dmd");
14806 }
14807 
14808 // FIXME: i called hotkey accelerator in some places. hotkey = key when menu is active like E&xit. accelerator = global shortcut.
14809 // FIXME: make multiple accelerators disambiguate based ona rgs
14810 // FIXME: MainWindow ctor should have same arg order as Window
14811 // FIXME: mainwindow ctor w/ client area size instead of total size.
14812 // 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.
14813 // FIXME: tri-state checkbox
14814 // FIXME: subordinate controls grouping...