1 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb775498%28v=vs.85%29.aspx
2 
3 // if doing nested menus, make sure the straight line from where it pops up to any destination on the new popup is not going to disappear the menu until at least a delay
4 
5 // me@arsd:~/.kde/share/config$ vim kdeglobals
6 
7 // FIXME: i kinda like how you can show find locations in scrollbars in the chrome browisers i wanna support that here too.
8 
9 // https://www.freedesktop.org/wiki/Accessibility/AT-SPI2/
10 
11 // for responsive design, a collapsible widget that if it doesn't have enough room, it just automatically becomes a "more" button or whatever.
12 
13 // responsive minigui, menu search, and file open with a preview hook on the side.
14 
15 // FIXME: add menu checkbox and menu icon eventually
16 
17 /*
18 
19 im tempted to add some css kind of thing to minigui. i've not done in the past cuz i have a lot of virtual functins i use but i think i have an evil plan
20 
21 the virtual functions remain as the default calculated values. then the reads go through some proxy object that can override it...
22 */
23 
24 // FIXME: a popup with slightly shaped window pointing at the mouse might eb useful in places
25 
26 // FIXME: text label must be copyable to the clipboard, at least as a full chunk.
27 
28 // FIXME: opt-in file picker widget with image support
29 
30 // FIXME: number widget
31 
32 // https://www.codeguru.com/cpp/controls/buttonctrl/advancedbuttons/article.php/c5161/Native-Win32-ThemeAware-OwnerDraw-Controls-No-MFC.htm
33 // https://docs.microsoft.com/en-us/windows/win32/controls/using-visual-styles
34 
35 // osx style menu search.
36 
37 // would be cool for a scroll bar to have marking capabilities
38 // kinda like vim's marks just on clicks etc and visual representation
39 // generically. may be cool to add an up arrow to the bottom too
40 //
41 // leave a shadow of where you last were for going back easily
42 
43 // So a window needs to have a selection, and that can be represented by a type. This is manipulated by various
44 // functions like cut, copy, paste. Widgets can have a selection and that would assert teh selection ownership for
45 // the window.
46 
47 // so what about context menus?
48 
49 // https://docs.microsoft.com/en-us/windows/desktop/Controls/about-custom-draw
50 
51 // FIXME: make the scroll thing go to bottom when the content changes.
52 
53 // add a knob slider view... you click and go up and down so basically same as a vertical slider, just presented as a round image
54 
55 // FIXME: the scroll area MUST be fixed to use the proper apis under the hood.
56 
57 
58 // FIXME: add a command search thingy built in and implement tip.
59 // FIXME: omg omg what if menu functions have arguments and it can pop up a gui or command line script them?!
60 
61 // On Windows:
62 // FIXME: various labels look broken in high contrast mode
63 // FIXME: changing themes while the program is upen doesn't trigger a redraw
64 
65 // add note about manifest to documentation. also icons.
66 
67 // a pager control is just a horizontal scroll area just with arrows on the sides instead of a scroll bar
68 // FIXME: clear the corner of scrollbars if they pop up
69 
70 // minigui needs to have a stdout redirection for gui mode on windows writeln
71 
72 // I kinda wanna do state reacting. sort of. idk tho
73 
74 // need a viewer widget that works like a web page - arrows scroll down consistently
75 
76 // I want a nanovega widget, and a svg widget with some kind of event handlers attached to the inside.
77 
78 // FIXME: the menus should be a bit more discoverable, at least a single click to open the others instead of two.
79 // and help info about menu items.
80 // and search in menus?
81 
82 // FIXME: a scroll area event signaling when a thing comes into view might be good
83 // FIXME: arrow key navigation and accelerators in dialog boxes will be a must
84 
85 // FIXME: unify Windows style line endings
86 
87 /*
88 	TODO:
89 
90 	pie menu
91 
92 	class Form with submit behavior -- see AutomaticDialog
93 
94 	disabled widgets and menu items
95 
96 	event cleanup
97 	tooltips.
98 	api improvements
99 
100 	margins are kinda broken, they don't collapse like they should. at least.
101 
102 	a table form btw would be a horizontal layout of vertical layouts holding each column
103 	that would give the same width things
104 */
105 
106 /*
107 
108 1(15:19:48) NotSpooky: Menus, text entry, label, notebook, box, frame, file dialogs and layout (this one is very useful because I can draw lines between its child widgets
109 */
110 
111 /++
112 	minigui is a smallish GUI widget library, aiming to be on par with at least
113 	HTML4 forms and a few other expected gui components. It uses native controls
114 	on Windows and does its own thing on Linux (Mac is not currently supported but
115 	may be later, and should use native controls) to keep size down. The Linux
116 	appearance is similar to Windows 95 and avoids using images to maintain network
117 	efficiency on remote X connections, though you can customize that.
118 
119 
120 	minigui's only required dependencies are [arsd.simpledisplay] and [arsd.color],
121 	on which it is built. simpledisplay provides the low-level interfaces and minigui
122 	builds the concept of widgets inside the windows on top of it.
123 
124 	Its #1 goal is to be useful without being large and complicated like GTK and Qt.
125 	It isn't hugely concerned with appearance - on Windows, it just uses the native
126 	controls and native theme, and on Linux, it keeps it simple and I may change that
127 	at any time, though after May 2021, you can customize some things with css-inspired
128 	[Widget.Style] classes. (On Windows, if you compile with `-version=custom_widgets`,
129 	you can use the custom implementation there too, but... you shouldn't.)
130 
131 	The event model is similar to what you use in the browser with Javascript and the
132 	layout engine tries to automatically fit things in, similar to a css flexbox.
133 
134 	FOR BEST RESULTS: be sure to link with the appropriate subsystem command
135 	`-L/SUBSYSTEM:WINDOWS:5.0`, for example, because otherwise you'll get a
136 	console and other visual bugs.
137 
138 	HTML_To_Classes:
139 	$(SMALL_TABLE
140 		HTML Code | Minigui Class
141 
142 		`<input type="text">` | [LineEdit]
143 		`<textarea>` | [TextEdit]
144 		`<select>` | [DropDownSelection]
145 		`<input type="checkbox">` | [Checkbox]
146 		`<input type="radio">` | [Radiobox]
147 		`<button>` | [Button]
148 	)
149 
150 
151 	Stretchiness:
152 		The default is 4. You can use larger numbers for things that should
153 		consume a lot of space, and lower numbers for ones that are better at
154 		smaller sizes.
155 
156 	Overlapped_input:
157 		COMING EVENTUALLY:
158 		minigui will include a little bit of I/O functionality that just works
159 		with the event loop. If you want to get fancy, I suggest spinning up
160 		another thread and posting events back and forth.
161 
162 	$(H2 Add ons)
163 		See the `minigui_addons` directory in the arsd repo for some add on widgets
164 		you can import separately too.
165 
166 	$(H3 XML definitions)
167 		If you use [arsd.minigui_xml], you can create widget trees from XML at runtime.
168 
169 	$(H3 Scriptability)
170 		minigui is compatible with [arsd.script]. If you see `@scriptable` on a method
171 		in this documentation, it means you can call it from the script language.
172 
173 		Tip: to allow easy creation of widget trees from script, import [arsd.minigui_xml]
174 		and make [arsd.minigui_xml.makeWidgetFromString] available to your script:
175 
176 		---
177 		import arsd.minigui_xml;
178 		import arsd.script;
179 
180 		var globals = var.emptyObject;
181 		globals.makeWidgetFromString = &makeWidgetFromString;
182 
183 		// this now works
184 		interpret(`var window = makeWidgetFromString("<MainWindow />");`, globals);
185 		---
186 
187 		More to come.
188 
189 	History:
190 		Minigui had mostly additive changes or bug fixes since its inception until May 2021.
191 
192 		In May 2021 (dub v10.0), minigui got an overhaul. If it was versioned independently, I'd
193 		tag this as version 2.0.
194 
195 		Among the changes:
196 		$(LIST
197 			* The event model changed to prefer strongly-typed events, though the Javascript string style ones still work, using properties off them is deprecated. It will still compile and function, but you should change the handler to use the classes in its argument list. I adapted my code to use the new model in just a few minutes, so it shouldn't too hard.
198 
199 			See [Event] for details.
200 
201 			* A [DoubleClickEvent] was added. Previously, you'd get two rapidly repeated click events. Now, you get one click event followed by a double click event. If you must recreate the old way exactly, you can listen for a DoubleClickEvent, set a flag upon receiving one, then send yourself a synthetic ClickEvent on the next MouseUpEvent, but your program might be better served just working with [MouseDownEvent]s instead.
202 
203 			See [DoubleClickEvent] for details.
204 
205 			* Styling hints were added, and the few that existed before have been moved to a new helper class. Deprecated forwarders exist for the (few) old properties to help you transition. Note that most of these only affect a `custom_events` build, which is the default on Linux, but opt in only on Windows.
206 
207 			See [Widget.Style] for details.
208 
209 			// * A widget must now opt in to receiving keyboard focus, rather than opting out.
210 
211 			* Widgets now draw their keyboard focus by default instead of opt in. You may wish to set `tabStop = false;` if it wasn't supposed to receive it.
212 
213 			* Most Widget constructors no longer have a default `parent` argument. You must pass the parent to almost all widgets, or in rare cases, an explict `null`, but more often than not, you need the parent so the default argument was not very useful at best and misleading to a crash at worst.
214 
215 			* [LabeledLineEdit] changed its default layout to vertical instead of horizontal. You can restore the old behavior by passing a `TextAlignment` argument to the constructor.
216 
217 			* Several conversions of public fields to properties, deprecated, or made private. It is unlikely this will affect you, but the compiler will tell you if it does.
218 
219 			* Various non-breaking additions.
220 		)
221 +/
222 module arsd.minigui;
223 
224 import arsd.core;
225 
226 /++
227 	This hello world sample will have an oversized button, but that's ok, you see your first window!
228 +/
229 version(Demo)
230 unittest {
231 	import arsd.minigui;
232 
233 	void main() {
234 		auto window = new MainWindow();
235 
236 		auto hello = new TextLabel("Hello, world!", TextAlignment.Center, window);
237 		auto button = new Button("Close", window);
238 		button.addWhenTriggered({
239 			window.close();
240 		});
241 
242 		window.loop();
243 	}
244 
245 	main(); // exclude from docs
246 }
247 
248 /++
249 	This example shows one way you can partition your window into a header
250 	and sidebar. Here, the header and sidebar have a fixed width, while the
251 	rest of the content sizes with the window.
252 
253 	It might be a new way of thinking about window layout to do things this
254 	way - perhaps [GridLayout] more matches your style of thought - but the
255 	concept here is to partition the window into sub-boxes with a particular
256 	size, then partition those boxes into further boxes.
257 
258 	$(IMG //arsdnet.net/minigui-screenshots/windows/layout.png, The example window has a header across the top, then below it a sidebar to the left and a content area to the right.)
259 
260 	So to make the header, start with a child layout that has a max height.
261 	It will use that space from the top, then the remaining children will
262 	split the remaining area, meaning you can think of is as just being another
263 	box you can split again. Keep splitting until you have the look you desire.
264 +/
265 // https://github.com/adamdruppe/arsd/issues/310
266 version(minigui_screenshots)
267 @Screenshot("layout")
268 unittest {
269 	import arsd.minigui;
270 
271 	// This helper class is just to help make the layout boxes visible.
272 	// think of it like a <div style="background-color: whatever;"></div> in HTML.
273 	class ColorWidget : Widget {
274 		this(Color color, Widget parent) {
275 			this.color = color;
276 			super(parent);
277 		}
278 		Color color;
279 		class Style : Widget.Style {
280 			override WidgetBackground background() { return WidgetBackground(color); }
281 		}
282 		mixin OverrideStyle!Style;
283 	}
284 
285 	void main() {
286 		auto window = new Window;
287 
288 		// the key is to give it a max height. This is one way to do it:
289 		auto header = new class HorizontalLayout {
290 			this() { super(window); }
291 			override int maxHeight() { return 50; }
292 		};
293 		// this next line is a shortcut way of doing it too, but it only works
294 		// for HorizontalLayout and VerticalLayout, and is less explicit, so it
295 		// is good to know how to make a new class like above anyway.
296 		// auto header = new HorizontalLayout(50, window);
297 
298 		auto bar = new HorizontalLayout(window);
299 
300 		// or since this is so common, VerticalLayout and HorizontalLayout both
301 		// can just take an argument in their constructor for max width/height respectively
302 
303 		// (could have tone this above too, but I wanted to demo both techniques)
304 		auto left = new VerticalLayout(100, bar);
305 
306 		// and this is the main section's container. A plain Widget instance is good enough here.
307 		auto container = new Widget(bar);
308 
309 		// and these just add color to the containers we made above for the screenshot.
310 		// in a real application, you can just add your actual controls instead of these.
311 		auto headerColorBox = new ColorWidget(Color.teal, header);
312 		auto leftColorBox = new ColorWidget(Color.green, left);
313 		auto rightColorBox = new ColorWidget(Color.purple, container);
314 
315 		window.loop();
316 	}
317 
318 	main(); // exclude from docs
319 }
320 
321 
322 public import arsd.simpledisplay;
323 /++
324 	Convenience import to override the Windows GDI Rectangle function (you can still use it through fully-qualified imports)
325 
326 	History:
327 		Was private until May 15, 2021.
328 +/
329 public alias Rectangle = arsd.color.Rectangle; // I specifically want this in here, not the win32 GDI Rectangle()
330 
331 version(Windows) {
332 	import core.sys.windows.winnls;
333 	import core.sys.windows.windef;
334 	import core.sys.windows.basetyps;
335 	import core.sys.windows.winbase;
336 	import core.sys.windows.winuser;
337 	import core.sys.windows.wingdi;
338 	static import gdi = core.sys.windows.wingdi;
339 }
340 
341 version(Windows) {
342 	version(minigui_manifest) {} else version=minigui_no_manifest;
343 
344 	version(minigui_no_manifest) {} else
345 	static if(__VERSION__ >= 2_083)
346 	version(CRuntime_Microsoft) { // FIXME: mingw?
347 		// assume we want commctrl6 whenever possible since there's really no reason not to
348 		// and this avoids some of the manifest hassle
349 		pragma(linkerDirective, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"");
350 	}
351 }
352 
353 // this is a hack to call the original window procedure on native win32 widgets if our event listener thing prevents default.
354 private bool lastDefaultPrevented;
355 
356 /// Methods marked with this are available from scripts if added to the [arsd.script] engine.
357 alias scriptable = arsd_jsvar_compatible;
358 
359 version(Windows) {
360 	// use native widgets when available unless specifically asked otherwise
361 	version(custom_widgets) {
362 		enum bool UsingCustomWidgets = true;
363 		enum bool UsingWin32Widgets = false;
364 	} else {
365 		version = win32_widgets;
366 		enum bool UsingCustomWidgets = false;
367 		enum bool UsingWin32Widgets = true;
368 	}
369 	// and native theming when needed
370 	//version = win32_theming;
371 } else {
372 	enum bool UsingCustomWidgets = true;
373 	enum bool UsingWin32Widgets = false;
374 	version=custom_widgets;
375 }
376 
377 
378 
379 /*
380 
381 	The main goals of minigui.d are to:
382 		1) Provide basic widgets that just work in a lightweight lib.
383 		   I basically want things comparable to a plain HTML form,
384 		   plus the easy and obvious things you expect from Windows
385 		   apps like a menu.
386 		2) Use native things when possible for best functionality with
387 		   least library weight.
388 		3) Give building blocks to provide easy extension for your
389 		   custom widgets, or hooking into additional native widgets
390 		   I didn't wrap.
391 		4) Provide interfaces for easy interaction between third
392 		   party minigui extensions. (event model, perhaps
393 		   signals/slots, drop-in ease of use bits.)
394 		5) Zero non-system dependencies, including Phobos as much as
395 		   I reasonably can. It must only import arsd.color and
396 		   my simpledisplay.d. If you need more, it will have to be
397 		   an extension module.
398 		6) An easy layout system that generally works.
399 
400 	A stretch goal is to make it easy to make gui forms with code,
401 	some kind of resource file (xml?) and even a wysiwyg designer.
402 
403 	Another stretch goal is to make it easy to hook data into the gui,
404 	including from reflection. So like auto-generate a form from a
405 	function signature or struct definition, or show a list from an
406 	array that automatically updates as the array is changed. Then,
407 	your program focuses on the data more than the gui interaction.
408 
409 
410 
411 	STILL NEEDED:
412 		* combo box. (this is diff than select because you can free-form edit too. more like a lineedit with autoselect)
413 		* slider
414 		* listbox
415 		* spinner
416 		* label?
417 		* rich text
418 */
419 
420 
421 /+
422 	enum LayoutMethods {
423 		 verticalFlex,
424 		 horizontalFlex,
425 		 inlineBlock, // left to right, no stretch, goes to next line as needed
426 		 static, // just set to x, y
427 		 verticalNoStretch, // browser style default
428 
429 		 inlineBlockFlex, // goes left to right, flexing, but when it runs out of space, it spills into next line
430 
431 		 grid, // magic
432 	}
433 +/
434 
435 /++
436 	The `Widget` is the base class for minigui's functionality, ranging from UI components like checkboxes or text displays to abstract groupings of other widgets like a layout container or a html `<div>`. You will likely want to use pre-made widgets as well as creating your own.
437 
438 
439 	To create your own widget, you must inherit from it and create a constructor that passes a parent to `super`. Everything else after that is optional.
440 
441 	---
442 	class MinimalWidget : Widget {
443 		this(Widget parent) {
444 			super(parent);
445 		}
446 	}
447 	---
448 
449 	$(SIDEBAR
450 		I'm not entirely happy with leaf, container, and windows all coming from the same base Widget class, but I so far haven't thought of a better solution that's good enough to justify the breakage of a transition. It hasn't been a major problem in practice anyway.
451 	)
452 
453 	Broadly, there's two kinds of widgets: leaf widgets, which are intended to be the direct user-interactive components, and container widgets, which organize, lay out, and aggregate other widgets in the object tree. A special case of a container widget is [Window], which represents a separate top-level window on the screen. Both leaf and container widgets inherit from `Widget`, so this distinction is more conventional than formal.
454 
455 	Among the things you'll most likely want to change in your custom widget:
456 
457 	$(LIST
458 		* In your constructor, set `tabStop = false;` if the widget is not supposed to receive keyboard focus. (Please note its childen still can, so `tabStop = false;` is appropriate on most container widgets.)
459 
460 		You may explicitly set `tabStop = true;` to ensure you get it, even against future changes to the library, though that's the default right now.
461 
462 		Do this $(I after) calling the `super` constructor.
463 
464 		* Override [paint] if you want full control of the widget's drawing area (except the area obscured by children!), or [paintContent] if you want to participate in the styling engine's system. You'll also possibly want to make a subclass of [Style] and use [OverrideStyle] to change the default hints given to the styling engine for widget.
465 
466 		Generally, painting is a job for leaf widgets, since child widgets would obscure your drawing area anyway. However, it is your decision.
467 
468 		* Override default event handlers with your behavior. For example [defaultEventHandler_click] may be overridden to make clicks do something. Again, this is generally a job for leaf widgets rather than containers; most events are dispatched to the lowest leaf on the widget tree, but they also pass through all their parents. See [Event] for more details about the event model.
469 
470 		* You may also want to override the various layout hints like [minWidth], [maxHeight], etc. In particular [Padding] and [Margin] are often relevant for both container and leaf widgets and the default values of 0 are often not what you want.
471 	)
472 
473 	On Microsoft Windows, many widgets are also based on native controls. You can also do this if `static if(UsingWin32Widgets)` passes. You should use the helper function [createWin32Window] to create the window and let minigui do what it needs to do to create its bridge structures. This will populate [Widget.hwnd] which you can access later for communcating with the native window. You may also consider overriding [Widget.handleWmCommand] and [Widget.handleWmNotify] for the widget to translate those messages into appropriate minigui [Event]s.
474 
475 	It is also possible to embed a [SimpleWindow]-based native window inside a widget. See [OpenGlWidget]'s source code as an example.
476 
477 	Your own custom-drawn and native system controls can exist side-by-side.
478 
479 	Later I'll add more complete examples, but for now [TextLabel] and [LabeledPasswordEdit] are both simple widgets you can view implementation to get some ideas.
480 +/
481 class Widget : ReflectableProperties {
482 
483 	private bool willDraw() {
484 		return true;
485 	}
486 
487 	/+
488 	/++
489 		Calling this directly after constructor can give you a reflectable object as-needed so you don't pay for what you don't need.
490 
491 		History:
492 			Added September 15, 2021
493 			implemented.... ???
494 	+/
495 	void prepareReflection(this This)() {
496 
497 	}
498 	+/
499 
500 	private bool _enabled = true;
501 
502 	/++
503 		Determines whether the control is marked enabled. Disabled controls are generally displayed as greyed out and clicking on them does nothing. It is also possible for a control to be disabled because its parent is disabled, in which case this will still return `true`, but setting `enabled = true` may have no effect. Check [disabledBy] to see which parent caused it to be disabled.
504 
505 		I also recommend you set a [disabledReason] if you chose to set `enabled = false` to tell the user why the control does not work and what they can do to enable it.
506 
507 		History:
508 			Added November 23, 2021 (dub v10.4)
509 
510 			Warning: the specific behavior of disabling with parents may change in the future.
511 		Bugs:
512 			Currently only implemented for widgets backed by native Windows controls.
513 
514 		See_Also: [disabledReason], [disabledBy]
515 	+/
516 	@property bool enabled() {
517 		return disabledBy() is null;
518 	}
519 
520 	/// ditto
521 	@property void enabled(bool yes) {
522 		_enabled = yes;
523 		version(win32_widgets) {
524 			if(hwnd)
525 				EnableWindow(hwnd, yes);
526 		}
527 		setDynamicState(DynamicState.disabled, yes);
528 	}
529 
530 	private string disabledReason_;
531 
532 	/++
533 		If the widget is not [enabled] this string may be presented to the user when they try to use it. The exact manner and time it gets displayed is up to the implementation of the control.
534 
535 		Setting this does NOT disable the widget. You need to call `enabled = false;` separately. It does set the data though.
536 
537 		History:
538 			Added November 23, 2021 (dub v10.4)
539 		See_Also: [enabled], [disabledBy]
540 	+/
541 	@property string disabledReason() {
542 		auto w = disabledBy();
543 		return (w is null) ? null : w.disabledReason_;
544 	}
545 
546 	/// ditto
547 	@property void disabledReason(string reason) {
548 		disabledReason_ = reason;
549 	}
550 
551 	/++
552 		Returns the widget that disabled this. It might be this or one of its parents all the way up the chain, or `null` if the widget is not disabled by anything. You can check [disabledReason] on the return value (after the null check!) to get a hint to display to the user.
553 
554 		History:
555 			Added November 25, 2021 (dub v10.4)
556 		See_Also: [enabled], [disabledReason]
557 	+/
558 	Widget disabledBy() {
559 		Widget p = this;
560 		while(p) {
561 			if(!p._enabled)
562 				return p;
563 			p = p.parent;
564 		}
565 		return null;
566 	}
567 
568 	/// Implementations of [ReflectableProperties] interface. See the interface for details.
569 	SetPropertyResult setPropertyFromString(string name, scope const(char)[] value, bool valueIsJson) {
570 		if(valueIsJson)
571 			return SetPropertyResult.wrongFormat;
572 		switch(name) {
573 			case "name":
574 				this.name = value.idup;
575 				return SetPropertyResult.success;
576 			case "statusTip":
577 				this.statusTip = value.idup;
578 				return SetPropertyResult.success;
579 			default:
580 				return SetPropertyResult.noSuchProperty;
581 		}
582 	}
583 	/// ditto
584 	void getPropertiesList(scope void delegate(string name) sink) const {
585 		sink("name");
586 		sink("statusTip");
587 	}
588 	/// ditto
589 	void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink) {
590 		switch(name) {
591 			case "name":
592 				sink(name, this.name, false);
593 				return;
594 			case "statusTip":
595 				sink(name, this.statusTip, false);
596 				return;
597 			default:
598 				sink(name, null, true);
599 		}
600 	}
601 
602 	/++
603 		Scales the given value to the system-reported DPI for the monitor on which the widget resides.
604 
605 		History:
606 			Added November 25, 2021 (dub v10.5)
607 			`Point` overload added January 12, 2022 (dub v10.6)
608 	+/
609 	int scaleWithDpi(int value, int assumedDpi = 96) {
610 		// avoid potential overflow with common special values
611 		if(value == int.max)
612 			return int.max;
613 		if(value == int.min)
614 			return int.min;
615 		if(value == 0)
616 			return 0;
617 		return value * currentDpi(assumedDpi) / assumedDpi;
618 	}
619 
620 	/// ditto
621 	Point scaleWithDpi(Point value, int assumedDpi = 96) {
622 		return Point(scaleWithDpi(value.x, assumedDpi), scaleWithDpi(value.y, assumedDpi));
623 	}
624 
625 	/++
626 		Returns the current scaling factor as a logical dpi value for this widget. Generally speaking, this divided by 96 gives you the user scaling factor.
627 
628 		Not entirely stable.
629 
630 		History:
631 			Added August 25, 2023 (dub v11.1)
632 	+/
633 	final int currentDpi(int assumedDpi = 96) {
634 		// assert(parentWindow !is null);
635 		// assert(parentWindow.win !is null);
636 		auto divide = (parentWindow && parentWindow.win) ? parentWindow.win.actualDpi : assumedDpi;
637 		//divide = 138; // to test 1.5x
638 		// 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.
639 		// this also covers the case when actualDpi returns 0.
640 		if(divide < 96)
641 			divide = 96;
642 		return divide;
643 	}
644 
645 	// avoid this it just forwards to a soon-to-be-deprecated function and is not remotely stable
646 	// I'll think up something better eventually
647 
648 	// FIXME: the defaultLineHeight should probably be removed and replaced with the calculations on the outside based on defaultTextHeight.
649 	protected final int defaultLineHeight() {
650 		auto cs = getComputedStyle();
651 		if(cs.font && !cs.font.isNull)
652 			return cs.font.height() * 5 / 4;
653 		else
654 			return scaleWithDpi(Window.lineHeightNotDeprecatedButShouldBeSinceItIsJustAFallback * 5/4);
655 	}
656 
657 	/++
658 
659 		History:
660 			Added August 25, 2023 (dub v11.1)
661 	+/
662 	protected final int defaultTextHeight(int numberOfLines = 1) {
663 		auto cs = getComputedStyle();
664 		if(cs.font && !cs.font.isNull)
665 			return cs.font.height() * numberOfLines;
666 		else
667 			return Window.lineHeightNotDeprecatedButShouldBeSinceItIsJustAFallback * numberOfLines;
668 	}
669 
670 	protected final int defaultTextWidth(const(char)[] text) {
671 		auto cs = getComputedStyle();
672 		if(cs.font && !cs.font.isNull)
673 			return cs.font.stringWidth(text);
674 		else
675 			return scaleWithDpi(Window.lineHeightNotDeprecatedButShouldBeSinceItIsJustAFallback * cast(int) text.length / 2);
676 	}
677 
678 	/++
679 		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.
680 
681 		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.
682 
683 		History:
684 			Added May 22, 2021
685 	+/
686 	protected bool encapsulatedChildren() {
687 		return false;
688 	}
689 
690 	private void privateDpiChanged() {
691 		dpiChanged();
692 		foreach(child; children)
693 			child.privateDpiChanged();
694 	}
695 
696 	/++
697 		Virtual hook to update any caches or fonts you need on the event of a dpi scaling change.
698 
699 		History:
700 			Added January 12, 2022 (dub v10.6)
701 	+/
702 	protected void dpiChanged() {
703 
704 	}
705 
706 	// Default layout properties {
707 
708 		int minWidth() { return 0; }
709 		int minHeight() {
710 			// default widgets have a vertical layout, therefore the minimum height is the sum of the contents
711 			int sum = this.paddingTop + this.paddingBottom;
712 			foreach(child; children) {
713 				if(child.hidden)
714 					continue;
715 				sum += child.minHeight();
716 				sum += child.marginTop();
717 				sum += child.marginBottom();
718 			}
719 
720 			return sum;
721 		}
722 		int maxWidth() { return int.max; }
723 		int maxHeight() { return int.max; }
724 		int widthStretchiness() { return 4; }
725 		int heightStretchiness() { return 4; }
726 
727 		/++
728 			Where stretchiness will grow from the flex basis, this shrinkiness will let it get smaller if needed to make room for other items.
729 
730 			History:
731 				Added June 15, 2021 (dub v10.1)
732 		+/
733 		int widthShrinkiness() { return 0; }
734 		/// ditto
735 		int heightShrinkiness() { return 0; }
736 
737 		/++
738 			The initial size of the widget for layout calculations. Default is 0.
739 
740 			See_Also: [https://developer.mozilla.org/en-US/docs/Web/CSS/flex-basis|CSS flex-basis]
741 
742 			History:
743 				Added June 15, 2021 (dub v10.1)
744 		+/
745 		int flexBasisWidth() { return 0; }
746 		/// ditto
747 		int flexBasisHeight() { return 0; }
748 
749 		/++
750 			Not stable.
751 
752 			Values are scaled with dpi after assignment. If you override the virtual functions, this may be ignored.
753 
754 			So if you set defaultPadding to 4 and the user is on 150% zoom, it will multiply to return 6.
755 
756 			History:
757 				Added January 5, 2023
758 		+/
759 		Rectangle defaultMargin;
760 		/// ditto
761 		Rectangle defaultPadding;
762 
763 		int marginLeft() { return scaleWithDpi(defaultMargin.left); }
764 		int marginRight() { return scaleWithDpi(defaultMargin.right); }
765 		int marginTop() { return scaleWithDpi(defaultMargin.top); }
766 		int marginBottom() { return scaleWithDpi(defaultMargin.bottom); }
767 		int paddingLeft() { return scaleWithDpi(defaultPadding.left); }
768 		int paddingRight() { return scaleWithDpi(defaultPadding.right); }
769 		int paddingTop() { return scaleWithDpi(defaultPadding.top); }
770 		int paddingBottom() { return scaleWithDpi(defaultPadding.bottom); }
771 		//LinePreference linePreference() { return LinePreference.PreferOwnLine; }
772 
773 		private bool recomputeChildLayoutRequired = true;
774 		private static class RecomputeEvent {}
775 		private __gshared rce = new RecomputeEvent();
776 		protected final void queueRecomputeChildLayout() {
777 			recomputeChildLayoutRequired = true;
778 
779 			if(this.parentWindow) {
780 				auto sw = this.parentWindow.win;
781 				assert(sw !is null);
782 				if(!sw.eventQueued!RecomputeEvent) {
783 					sw.postEvent(rce);
784 					// writeln("redraw requested from ", file,":",line," ", this.parentWindow.win.impl.window);
785 				}
786 			}
787 
788 		}
789 
790 		protected final void recomputeChildLayoutEntry() {
791 			if(recomputeChildLayoutRequired) {
792 				recomputeChildLayout();
793 				recomputeChildLayoutRequired = false;
794 				redraw();
795 			} else {
796 				// I still need to check the tree just in case one of them was queued up
797 				// and the event came up here instead of there.
798 				foreach(child; children)
799 					child.recomputeChildLayoutEntry();
800 			}
801 		}
802 
803 		// this function should (almost) never be called directly anymore... call recomputeChildLayoutEntry when executing it and queueRecomputeChildLayout if you just want it done soon
804 		void recomputeChildLayout() {
805 			.recomputeChildLayout!"height"(this);
806 		}
807 
808 	// }
809 
810 
811 	/++
812 		Returns the style's tag name string this object uses.
813 
814 		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.
815 
816 		This tag may never be used, it is just available for the [VisualTheme.getPropertyString] if it chooses to do something like CSS.
817 
818 		History:
819 			Added May 10, 2021
820 	+/
821 	string styleTagName() const {
822 		string n = typeid(this).name;
823 		foreach_reverse(idx, ch; n)
824 			if(ch == '.') {
825 				n = n[idx + 1 .. $];
826 				break;
827 			}
828 		return n;
829 	}
830 
831 	/// API for the [styleClassList]
832 	static struct ClassList {
833 		private Widget widget;
834 
835 		///
836 		void add(string s) {
837 			widget.styleClassList_ ~= s;
838 		}
839 
840 		///
841 		void remove(string s) {
842 			foreach(idx, s1; widget.styleClassList_)
843 				if(s1 == s) {
844 					widget.styleClassList_[idx] = widget.styleClassList_[$-1];
845 					widget.styleClassList_ = widget.styleClassList_[0 .. $-1];
846 					widget.styleClassList_.assumeSafeAppend();
847 					return;
848 				}
849 		}
850 
851 		/// Returns true if it was added, false if it was removed.
852 		bool toggle(string s) {
853 			if(contains(s)) {
854 				remove(s);
855 				return false;
856 			} else {
857 				add(s);
858 				return true;
859 			}
860 		}
861 
862 		///
863 		bool contains(string s) const {
864 			foreach(s1; widget.styleClassList_)
865 				if(s1 == s)
866 					return true;
867 			return false;
868 
869 		}
870 	}
871 
872 	private string[] styleClassList_;
873 
874 	/++
875 		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.
876 
877 		It has no inherent meaning, it is really just a place to put some metadata tags on individual objects.
878 
879 		History:
880 			Added May 10, 2021
881 	+/
882 	inout(ClassList) styleClassList() inout {
883 		return cast(inout(ClassList)) ClassList(cast() this);
884 	}
885 
886 	/++
887 		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.
888 
889 		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.
890 
891 		The upper 32 bits are available for your own extensions.
892 
893 		History:
894 			Added May 10, 2021
895 	+/
896 	enum DynamicState : ulong {
897 		focus = (1 << 0), /// the widget currently has the keyboard focus
898 		hover = (1 << 1), /// the mouse is currently hovering over the widget (may not always be updated)
899 		valid = (1 << 2), /// the widget's content has been validated and it passed (do not set if not validation has been performed!)
900 		invalid = (1 << 3), /// the widget's content has been validated and it failed (do not set if not validation has been performed!)
901 		checked = (1 << 4), /// the widget is toggleable and currently toggled on
902 		selected = (1 << 5), /// the widget represents one option of many and is currently selected, but is not necessarily focused nor checked.
903 		disabled = (1 << 6), /// the widget is currently unable to perform its designated task
904 		indeterminate = (1 << 7), /// the widget has tri-state and is between checked and not checked
905 		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.
906 
907 		USER_BEGIN = (1UL << 32),
908 	}
909 
910 	// I want to add the primary and cancel styles to buttons at least at some point somehow.
911 
912 	/// ditto
913 	@property ulong dynamicState() { return dynamicState_; }
914 	/// ditto
915 	@property ulong dynamicState(ulong newValue) {
916 		if(dynamicState != newValue) {
917 			auto old = dynamicState_;
918 			dynamicState_ = newValue;
919 
920 			useStyleProperties((scope Widget.Style s) {
921 				if(s.variesWithState(old ^ newValue))
922 					redraw();
923 			});
924 		}
925 		return dynamicState_;
926 	}
927 
928 	/// ditto
929 	void setDynamicState(ulong flags, bool state) {
930 		auto ds = dynamicState_;
931 		if(state)
932 			ds |= flags;
933 		else
934 			ds &= ~flags;
935 
936 		dynamicState = ds;
937 	}
938 
939 	private ulong dynamicState_;
940 
941 	deprecated("Use dynamic styles instead now") {
942 		Color backgroundColor() { return backgroundColor_; }
943 		void backgroundColor(Color c){ this.backgroundColor_ = c; }
944 
945 		MouseCursor cursor() { return GenericCursor.Default; }
946 	} private Color backgroundColor_ = Color.transparent;
947 
948 
949 	/++
950 		Style properties are defined as an accessory class so they can be referenced and overridden independently, but they are nested so you can refer to them easily by name (e.g. generic `Widget.Style` vs `Button.Style` and such).
951 
952 		It is here so there can be a specificity switch.
953 
954 		See [OverrideStyle] for a helper function to use your own.
955 
956 		History:
957 			Added May 11, 2021
958 	+/
959 	static class Style/* : StyleProperties*/ {
960 		public Widget widget; // public because the mixin template needs access to it
961 
962 		/++
963 			You must override this to trigger automatic redraws if you ever uses the `dynamicState` flag in your style.
964 
965 			History:
966 				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.
967 		+/
968 		bool variesWithState(ulong dynamicStateFlags) {
969 			version(win32_widgets) {
970 				if(widget.hwnd)
971 					return false;
972 			}
973 			return widget.tabStop && ((dynamicStateFlags & DynamicState.focus) ? true : false);
974 		}
975 
976 		///
977 		Color foregroundColor() {
978 			return WidgetPainter.visualTheme.foregroundColor;
979 		}
980 
981 		///
982 		WidgetBackground background() {
983 			// the default is a "transparent" background, which means
984 			// it goes as far up as it can to get the color
985 			if (widget.backgroundColor_ != Color.transparent)
986 				return WidgetBackground(widget.backgroundColor_);
987 			if (widget.parent)
988 				return widget.parent.getComputedStyle.background;
989 			return WidgetBackground(widget.backgroundColor_);
990 		}
991 
992 		private static OperatingSystemFont fontCached_;
993 		private OperatingSystemFont fontCached() {
994 			if(fontCached_ is null)
995 				fontCached_ = font();
996 			return fontCached_;
997 		}
998 
999 		/++
1000 			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.
1001 		+/
1002 		OperatingSystemFont font() {
1003 			return null;
1004 		}
1005 
1006 		/++
1007 			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.
1008 
1009 			You can return a member of [GenericCursor] or your own [MouseCursor] instance.
1010 
1011 			History:
1012 				Was previously a method directly on [Widget], moved to [Widget.Style] on May 12, 2021
1013 		+/
1014 		MouseCursor cursor() {
1015 			return GenericCursor.Default;
1016 		}
1017 
1018 		FrameStyle borderStyle() {
1019 			return FrameStyle.none;
1020 		}
1021 
1022 		/++
1023 		+/
1024 		Color borderColor() {
1025 			return Color.transparent;
1026 		}
1027 
1028 		FrameStyle outlineStyle() {
1029 			if(widget.dynamicState & DynamicState.focus)
1030 				return FrameStyle.dotted;
1031 			else
1032 				return FrameStyle.none;
1033 		}
1034 
1035 		Color outlineColor() {
1036 			return foregroundColor;
1037 		}
1038 	}
1039 
1040 	/++
1041 		This mixin overrides the [useStyleProperties] method to direct it toward your own style class.
1042 		The basic usage is simple:
1043 
1044 		---
1045 		static class Style : YourParentClass.Style { /* YourParentClass is frequently Widget, of course, but not always */
1046 			// override style hints as-needed here
1047 		}
1048 		OverrideStyle!Style; // add the method
1049 		---
1050 
1051 		$(TIP
1052 			While the class is not forced to be `static`, for best results, it should be. A non-static class
1053 			can not be inherited by other objects whereas the static one can. A property on the base class,
1054 			called [Widget.Style.widget|widget], is available for you to access its properties.
1055 		)
1056 
1057 		This exists just because [useStyleProperties] has a somewhat convoluted signature and its overrides must
1058 		repeat them. Moreover, its implementation uses a stack class to optimize GC pressure from small fetches
1059 		and that's a little tedious to repeat in your child classes too when you only care about changing the type.
1060 
1061 
1062 		It also has a further facility to pick a wholly differnet class based on the [DynamicState] of the Widget.
1063 		You may also just override `variesWithState` when you use this flag.
1064 
1065 		---
1066 		mixin OverrideStyle!(
1067 			DynamicState.focus, YourFocusedStyle,
1068 			DynamicState.hover, YourHoverStyle,
1069 			YourDefaultStyle
1070 		)
1071 		---
1072 
1073 		It checks if `dynamicState` matches the state and if so, returns the object given.
1074 
1075 		If there is no state mask given, the next one matches everything. The first match given is used.
1076 
1077 		However, since in most cases you'll want check state inside your individual methods, you probably won't
1078 		find much use for this whole-class swap out.
1079 
1080 		History:
1081 			Added May 16, 2021
1082 	+/
1083 	static protected mixin template OverrideStyle(S...) {
1084 		static import amg = arsd.minigui;
1085 		override void useStyleProperties(scope void delegate(scope amg.Widget.Style props) dg) {
1086 			ulong mask = 0;
1087 			foreach(idx, thing; S) {
1088 				static if(is(typeof(thing) : ulong)) {
1089 					mask = thing;
1090 				} else {
1091 					if(!(idx & 1) || (this.dynamicState & mask) == mask) {
1092 						//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.");
1093 						scope amg.Widget.Style s = new thing();
1094 						s.widget = this;
1095 						dg(s);
1096 						return;
1097 					}
1098 				}
1099 			}
1100 		}
1101 	}
1102 	/++
1103 		You can override this by hand, or use the [OverrideStyle] helper which is a bit less verbose.
1104 	+/
1105 	void useStyleProperties(scope void delegate(scope Style props) dg) {
1106 		scope Style s = new Style();
1107 		s.widget = this;
1108 		dg(s);
1109 	}
1110 
1111 
1112 	protected void sendResizeEvent() {
1113 		this.emit!ResizeEvent();
1114 	}
1115 
1116 	Menu contextMenu(int x, int y) { return null; }
1117 
1118 	final bool showContextMenu(int x, int y, int screenX = -2, int screenY = -2) {
1119 		if(parentWindow is null || parentWindow.win is null) return false;
1120 
1121 		auto menu = this.contextMenu(x, y);
1122 		if(menu is null)
1123 			return false;
1124 
1125 		version(win32_widgets) {
1126 			// FIXME: if it is -1, -1, do it at the current selection location instead
1127 			// tho the corner of the window, whcih it does now, isn't the literal worst.
1128 
1129 			if(screenX < 0 && screenY < 0) {
1130 				auto p = this.globalCoordinates();
1131 				if(screenX == -2)
1132 					p.x += x;
1133 				if(screenY == -2)
1134 					p.y += y;
1135 
1136 				screenX = p.x;
1137 				screenY = p.y;
1138 			}
1139 
1140 			if(!TrackPopupMenuEx(menu.handle, 0, screenX, screenY, parentWindow.win.impl.hwnd, null))
1141 				throw new Exception("TrackContextMenuEx");
1142 		} else version(custom_widgets) {
1143 			menu.popup(this, x, y);
1144 		}
1145 
1146 		return true;
1147 	}
1148 
1149 	/++
1150 		Removes this widget from its parent.
1151 
1152 		History:
1153 			`removeWidget` was made `final` on May 11, 2021.
1154 	+/
1155 	@scriptable
1156 	final void removeWidget() {
1157 		auto p = this.parent;
1158 		if(p) {
1159 			int item;
1160 			for(item = 0; item < p._children.length; item++)
1161 				if(p._children[item] is this)
1162 					break;
1163 			auto idx = item;
1164 			for(; item < p._children.length - 1; item++)
1165 				p._children[item] = p._children[item + 1];
1166 			p._children = p._children[0 .. $-1];
1167 
1168 			this.parent.widgetRemoved(idx, this);
1169 			//this.parent = null;
1170 
1171 			p.queueRecomputeChildLayout();
1172 		}
1173 		version(win32_widgets) {
1174 			removeAllChildren();
1175 			if(hwnd) {
1176 				DestroyWindow(hwnd);
1177 				hwnd = null;
1178 			}
1179 		}
1180 	}
1181 
1182 	/++
1183 		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.
1184 
1185 		History:
1186 			Added September 19, 2021
1187 	+/
1188 	protected void widgetRemoved(size_t oldIndex, Widget oldReference) { }
1189 
1190 	/++
1191 		Removes all child widgets from `this`. You should not use the removed widgets again.
1192 
1193 		Note that on Windows, it also destroys the native handles for the removed children recursively.
1194 
1195 		History:
1196 			Added July 1, 2021 (dub v10.2)
1197 	+/
1198 	void removeAllChildren() {
1199 		version(win32_widgets)
1200 		foreach(child; _children) {
1201 			child.removeAllChildren();
1202 			if(child.hwnd) {
1203 				DestroyWindow(child.hwnd);
1204 				child.hwnd = null;
1205 			}
1206 		}
1207 		auto orig = this._children;
1208 		this._children = null;
1209 		foreach(idx, w; orig)
1210 			this.widgetRemoved(idx, w);
1211 
1212 		queueRecomputeChildLayout();
1213 	}
1214 
1215 	/++
1216 		Calls [getByName] with the generic type of Widget. Meant for script interop where instantiating a template is impossible.
1217 	+/
1218 	@scriptable
1219 	Widget getChildByName(string name) {
1220 		return getByName(name);
1221 	}
1222 	/++
1223 		Finds the nearest descendant with the requested type and [name]. May return `this`.
1224 	+/
1225 	final WidgetClass getByName(WidgetClass = Widget)(string name) {
1226 		if(this.name == name)
1227 			if(auto c = cast(WidgetClass) this)
1228 				return c;
1229 		foreach(child; children) {
1230 			auto w = child.getByName(name);
1231 			if(auto c = cast(WidgetClass) w)
1232 				return c;
1233 		}
1234 		return null;
1235 	}
1236 
1237 	/++
1238 		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.
1239 		Names should be unique in a window.
1240 
1241 		See_Also: [getByName], [getChildByName]
1242 	+/
1243 	@scriptable string name;
1244 
1245 	private EventHandler[][string] bubblingEventHandlers;
1246 	private EventHandler[][string] capturingEventHandlers;
1247 
1248 	/++
1249 		Default event handlers. These are called on the appropriate
1250 		event unless [Event.preventDefault] is called on the event at
1251 		some point through the bubbling process.
1252 
1253 
1254 		If you are implementing your own widget and want to add custom
1255 		events, you should follow the same pattern here: create a virtual
1256 		function named `defaultEventHandler_eventname` with the implementation,
1257 		then, override [setupDefaultEventHandlers] and add a wrapped caller to
1258 		`defaultEventHandlers["eventname"]`. It should be wrapped like so:
1259 		`defaultEventHandlers["eventname"] = (Widget t, Event event) { t.defaultEventHandler_name(event); };`.
1260 		This ensures virtual dispatch based on the correct subclass.
1261 
1262 		Also, don't forget to call `super.setupDefaultEventHandlers();` too in your
1263 		overridden version.
1264 
1265 		You only need to do that on parent classes adding NEW event types. If you
1266 		just want to change the default behavior of an existing event type in a subclass,
1267 		you override the function (and optionally call `super.method_name`) like normal.
1268 
1269 	+/
1270 	protected EventHandler[string] defaultEventHandlers;
1271 
1272 	/// ditto
1273 	void setupDefaultEventHandlers() {
1274 		defaultEventHandlers["click"] = (Widget t, Event event) { t.defaultEventHandler_click(cast(ClickEvent) event); };
1275 		defaultEventHandlers["dblclick"] = (Widget t, Event event) { t.defaultEventHandler_dblclick(cast(DoubleClickEvent) event); };
1276 		defaultEventHandlers["keydown"] = (Widget t, Event event) { t.defaultEventHandler_keydown(cast(KeyDownEvent) event); };
1277 		defaultEventHandlers["keyup"] = (Widget t, Event event) { t.defaultEventHandler_keyup(cast(KeyUpEvent) event); };
1278 		defaultEventHandlers["mouseover"] = (Widget t, Event event) { t.defaultEventHandler_mouseover(cast(MouseOverEvent) event); };
1279 		defaultEventHandlers["mouseout"] = (Widget t, Event event) { t.defaultEventHandler_mouseout(cast(MouseOutEvent) event); };
1280 		defaultEventHandlers["mousedown"] = (Widget t, Event event) { t.defaultEventHandler_mousedown(cast(MouseDownEvent) event); };
1281 		defaultEventHandlers["mouseup"] = (Widget t, Event event) { t.defaultEventHandler_mouseup(cast(MouseUpEvent) event); };
1282 		defaultEventHandlers["mouseenter"] = (Widget t, Event event) { t.defaultEventHandler_mouseenter(cast(MouseEnterEvent) event); };
1283 		defaultEventHandlers["mouseleave"] = (Widget t, Event event) { t.defaultEventHandler_mouseleave(cast(MouseLeaveEvent) event); };
1284 		defaultEventHandlers["mousemove"] = (Widget t, Event event) { t.defaultEventHandler_mousemove(cast(MouseMoveEvent) event); };
1285 		defaultEventHandlers["char"] = (Widget t, Event event) { t.defaultEventHandler_char(cast(CharEvent) event); };
1286 		defaultEventHandlers["triggered"] = (Widget t, Event event) { t.defaultEventHandler_triggered(event); };
1287 		defaultEventHandlers["change"] = (Widget t, Event event) { t.defaultEventHandler_change(event); };
1288 		defaultEventHandlers["focus"] = (Widget t, Event event) { t.defaultEventHandler_focus(event); };
1289 		defaultEventHandlers["blur"] = (Widget t, Event event) { t.defaultEventHandler_blur(event); };
1290 		defaultEventHandlers["focusin"] = (Widget t, Event event) { t.defaultEventHandler_focusin(event); };
1291 		defaultEventHandlers["focusout"] = (Widget t, Event event) { t.defaultEventHandler_focusout(event); };
1292 	}
1293 
1294 	/// ditto
1295 	void defaultEventHandler_click(ClickEvent event) {}
1296 	/// ditto
1297 	void defaultEventHandler_dblclick(DoubleClickEvent event) {}
1298 	/// ditto
1299 	void defaultEventHandler_keydown(KeyDownEvent event) {}
1300 	/// ditto
1301 	void defaultEventHandler_keyup(KeyUpEvent event) {}
1302 	/// ditto
1303 	void defaultEventHandler_mousedown(MouseDownEvent event) {
1304 		if(event.button == MouseButton.left) {
1305 			if(this.tabStop)
1306 				this.focus();
1307 		}
1308 	}
1309 	/// ditto
1310 	void defaultEventHandler_mouseover(MouseOverEvent event) {}
1311 	/// ditto
1312 	void defaultEventHandler_mouseout(MouseOutEvent event) {}
1313 	/// ditto
1314 	void defaultEventHandler_mouseup(MouseUpEvent event) {}
1315 	/// ditto
1316 	void defaultEventHandler_mousemove(MouseMoveEvent event) {}
1317 	/// ditto
1318 	void defaultEventHandler_mouseenter(MouseEnterEvent event) {}
1319 	/// ditto
1320 	void defaultEventHandler_mouseleave(MouseLeaveEvent event) {}
1321 	/// ditto
1322 	void defaultEventHandler_char(CharEvent event) {}
1323 	/// ditto
1324 	void defaultEventHandler_triggered(Event event) {}
1325 	/// ditto
1326 	void defaultEventHandler_change(Event event) {}
1327 	/// ditto
1328 	void defaultEventHandler_focus(Event event) {}
1329 	/// ditto
1330 	void defaultEventHandler_blur(Event event) {}
1331 	/// ditto
1332 	void defaultEventHandler_focusin(Event event) {}
1333 	/// ditto
1334 	void defaultEventHandler_focusout(Event event) {}
1335 
1336 	/++
1337 		[Event]s use a Javascript-esque model. See more details on the [Event] page.
1338 
1339 		[addEventListener] returns an opaque handle that you can later pass to [removeEventListener].
1340 
1341 		addDirectEventListener just inserts a check `if(e.target !is this) return;` meaning it opts out
1342 		of participating in handler delegation.
1343 
1344 		$(TIP
1345 			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.
1346 		)
1347 	+/
1348 	EventListener addDirectEventListener(string event, void delegate() handler, bool useCapture = false) {
1349 		return addEventListener(event, (Widget, scope Event e) {
1350 			if(e.srcElement is this)
1351 				handler();
1352 		}, useCapture);
1353 	}
1354 
1355 	/// ditto
1356 	EventListener addDirectEventListener(string event, void delegate(Event) handler, bool useCapture = false) {
1357 		return addEventListener(event, (Widget, Event e) {
1358 			if(e.srcElement is this)
1359 				handler(e);
1360 		}, useCapture);
1361 	}
1362 
1363 	/// ditto
1364 	EventListener addDirectEventListener(Handler)(Handler handler, bool useCapture = false) {
1365 		static if(is(Handler Fn == delegate)) {
1366 		static if(is(Fn Params == __parameters)) {
1367 			return addEventListener(EventString!(Params[0]), (Widget, Event e) {
1368 				if(e.srcElement !is this)
1369 					return;
1370 				auto ty = cast(Params[0]) e;
1371 				if(ty !is null)
1372 					handler(ty);
1373 			}, useCapture);
1374 		} else static assert(0);
1375 		} else static assert(0, "Your handler wasn't usable because it wasn't passed a delegate. Use the delegate keyword at the call site.");
1376 	}
1377 
1378 	/// ditto
1379 	@scriptable
1380 	EventListener addEventListener(string event, void delegate() handler, bool useCapture = false) {
1381 		return addEventListener(event, (Widget, scope Event) { handler(); }, useCapture);
1382 	}
1383 
1384 	/// ditto
1385 	EventListener addEventListener(Handler)(Handler handler, bool useCapture = false) {
1386 		static if(is(Handler Fn == delegate)) {
1387 		static if(is(Fn Params == __parameters)) {
1388 			return addEventListener(EventString!(Params[0]), (Widget, Event e) {
1389 				auto ty = cast(Params[0]) e;
1390 				if(ty !is null)
1391 					handler(ty);
1392 			}, useCapture);
1393 		} else static assert(0);
1394 		} else static assert(0, "Your handler wasn't usable because it wasn't passed a delegate. Use the delegate keyword at the call site.");
1395 	}
1396 
1397 	/// ditto
1398 	EventListener addEventListener(string event, void delegate(Event) handler, bool useCapture = false) {
1399 		return addEventListener(event, (Widget, Event e) { handler(e); }, useCapture);
1400 	}
1401 
1402 	/// ditto
1403 	EventListener addEventListener(string event, EventHandler handler, bool useCapture = false) {
1404 		if(event.length > 2 && event[0..2] == "on")
1405 			event = event[2 .. $];
1406 
1407 		if(useCapture)
1408 			capturingEventHandlers[event] ~= handler;
1409 		else
1410 			bubblingEventHandlers[event] ~= handler;
1411 
1412 		return EventListener(this, event, handler, useCapture);
1413 	}
1414 
1415 	/// ditto
1416 	void removeEventListener(string event, EventHandler handler, bool useCapture = false) {
1417 		if(event.length > 2 && event[0..2] == "on")
1418 			event = event[2 .. $];
1419 
1420 		if(useCapture) {
1421 			if(event in capturingEventHandlers)
1422 			foreach(ref evt; capturingEventHandlers[event])
1423 				if(evt is handler) evt = null;
1424 		} else {
1425 			if(event in bubblingEventHandlers)
1426 			foreach(ref evt; bubblingEventHandlers[event])
1427 				if(evt is handler) evt = null;
1428 		}
1429 	}
1430 
1431 	/// ditto
1432 	void removeEventListener(EventListener listener) {
1433 		removeEventListener(listener.event, listener.handler, listener.useCapture);
1434 	}
1435 
1436 	static if(UsingSimpledisplayX11) {
1437 		void discardXConnectionState() {
1438 			foreach(child; children)
1439 				child.discardXConnectionState();
1440 		}
1441 
1442 		void recreateXConnectionState() {
1443 			foreach(child; children)
1444 				child.recreateXConnectionState();
1445 			redraw();
1446 		}
1447 	}
1448 
1449 	/++
1450 		Returns the coordinates of this widget on the screen, relative to the upper left corner of the whole screen.
1451 
1452 		History:
1453 			`globalCoordinates` was made `final` on May 11, 2021.
1454 	+/
1455 	Point globalCoordinates() {
1456 		int x = this.x;
1457 		int y = this.y;
1458 		auto p = this.parent;
1459 		while(p) {
1460 			x += p.x;
1461 			y += p.y;
1462 			p = p.parent;
1463 		}
1464 
1465 		static if(UsingSimpledisplayX11) {
1466 			auto dpy = XDisplayConnection.get;
1467 			arsd.simpledisplay.Window dummyw;
1468 			XTranslateCoordinates(dpy, this.parentWindow.win.impl.window, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
1469 		} else {
1470 			POINT pt;
1471 			pt.x = x;
1472 			pt.y = y;
1473 			MapWindowPoints(this.parentWindow.win.impl.hwnd, null, &pt, 1);
1474 			x = pt.x;
1475 			y = pt.y;
1476 		}
1477 
1478 		return Point(x, y);
1479 	}
1480 
1481 	version(win32_widgets)
1482 	int handleWmDrawItem(DRAWITEMSTRUCT* dis) { return 0; }
1483 
1484 	version(win32_widgets)
1485 	/// Called when a WM_COMMAND is sent to the associated hwnd.
1486 	void handleWmCommand(ushort cmd, ushort id) {}
1487 
1488 	version(win32_widgets)
1489 	/++
1490 		Called when a WM_NOTIFY is sent to the associated hwnd.
1491 
1492 		History:
1493 	+/
1494 	int handleWmNotify(NMHDR* hdr, int code, out int mustReturn) { return 0; }
1495 
1496 	version(win32_widgets)
1497 	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); }
1498 
1499 	/++
1500 		This tip is displayed in the status bar (if there is one in the containing window) when the mouse moves over this widget.
1501 
1502 		Updates to this variable will only be made visible on the next mouse enter event.
1503 	+/
1504 	@scriptable string statusTip;
1505 	// string toolTip;
1506 	// string helpText;
1507 
1508 	/++
1509 		If true, this widget can be focused via keyboard control with the tab key.
1510 
1511 		If false, it is assumed the widget itself does will never receive the keyboard focus (though its childen are free to).
1512 	+/
1513 	bool tabStop = true;
1514 	/++
1515 		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.)
1516 	+/
1517 	int tabOrder;
1518 
1519 	version(win32_widgets) {
1520 		static Widget[HWND] nativeMapping;
1521 		/// The native handle, if there is one.
1522 		HWND hwnd;
1523 		WNDPROC originalWindowProcedure;
1524 
1525 		SimpleWindow simpleWindowWrappingHwnd;
1526 
1527 		// please note it IGNORES your return value and does NOT forward it to Windows!
1528 		int hookedWndProc(UINT iMessage, WPARAM wParam, LPARAM lParam) {
1529 			return 0;
1530 		}
1531 	}
1532 	private bool implicitlyCreated;
1533 
1534 	/// 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.
1535 	int x;
1536 	/// ditto
1537 	int y;
1538 	private int _width;
1539 	private int _height;
1540 	private Widget[] _children;
1541 	private Widget _parent;
1542 	private Window _parentWindow;
1543 
1544 	/++
1545 		Returns the window to which this widget is attached.
1546 
1547 		History:
1548 			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.
1549 	+/
1550 	final @property inout(Window) parentWindow() inout @nogc nothrow pure { return _parentWindow; }
1551 	private @property void parentWindow(Window parent) {
1552 		_parentWindow = parent;
1553 		foreach(child; children)
1554 			child.parentWindow = parent; // please note that this is recursive
1555 	}
1556 
1557 	/++
1558 		Returns the list of the widget's children.
1559 
1560 		History:
1561 			Prior to May 11, 2021, the `Widget[] children` was directly available. Now, only this property getter is available and the actual store is private.
1562 
1563 			Children should be added by the constructor most the time, but if that's impossible, use [addChild] and [removeWidget] to manage the list.
1564 	+/
1565 	final @property inout(Widget)[] children() inout @nogc nothrow pure { return _children; }
1566 
1567 	/++
1568 		Returns the widget's parent.
1569 
1570 		History:
1571 			Prior to May 11, 2021, the `Widget parent` variable was directly available. Now, only this property getter is permitted.
1572 
1573 			The parent should only be managed by the [addChild] and [removeWidget] method.
1574 	+/
1575 	final @property inout(Widget) parent() inout nothrow @nogc pure @safe return { return _parent; }
1576 
1577 	/// The widget's current size.
1578 	final @scriptable public @property int width() const nothrow @nogc pure @safe { return _width; }
1579 	/// ditto
1580 	final @scriptable public @property int height() const nothrow @nogc pure @safe { return _height; }
1581 
1582 	/// Only the layout manager should be calling these.
1583 	final protected @property int width(int a) @safe { return _width = a; }
1584 	/// ditto
1585 	final protected @property int height(int a) @safe { return _height = a; }
1586 
1587 	/++
1588 		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.
1589 
1590 		It is also responsible for calling [sendResizeEvent] to notify other listeners that the widget has changed size.
1591 	+/
1592 	protected void registerMovement() {
1593 		version(win32_widgets) {
1594 			if(hwnd) {
1595 				auto pos = getChildPositionRelativeToParentHwnd(this);
1596 				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
1597 			}
1598 		}
1599 		sendResizeEvent();
1600 	}
1601 
1602 	/// Creates the widget and adds it to the parent.
1603 	this(Widget parent) {
1604 		if(parent !is null)
1605 			parent.addChild(this);
1606 		setupDefaultEventHandlers();
1607 	}
1608 
1609 	/// 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.
1610 	@scriptable
1611 	bool isFocused() {
1612 		return parentWindow && parentWindow.focusedWidget is this;
1613 	}
1614 
1615 	private bool showing_ = true;
1616 	///
1617 	bool showing() { return showing_; }
1618 	///
1619 	bool hidden() { return !showing_; }
1620 	/++
1621 		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.
1622 	+/
1623 	void showing(bool s, bool recalculate = true) {
1624 		auto so = showing_;
1625 		showing_ = s;
1626 		if(s != so) {
1627 			version(win32_widgets)
1628 			if(hwnd)
1629 				ShowWindow(hwnd, s ? SW_SHOW : SW_HIDE);
1630 
1631 			if(parent && recalculate) {
1632 				parent.queueRecomputeChildLayout();
1633 				parent.redraw();
1634 			}
1635 
1636 			foreach(child; children)
1637 				child.showing(s, false);
1638 
1639 		}
1640 		queueRecomputeChildLayout();
1641 		redraw();
1642 	}
1643 	/// Convenience method for `showing = true`
1644 	@scriptable
1645 	void show() {
1646 		showing = true;
1647 	}
1648 	/// Convenience method for `showing = false`
1649 	@scriptable
1650 	void hide() {
1651 		showing = false;
1652 	}
1653 
1654 	///
1655 	@scriptable
1656 	void focus() {
1657 		assert(parentWindow !is null);
1658 		if(isFocused())
1659 			return;
1660 
1661 		if(parentWindow.focusedWidget) {
1662 			// FIXME: more details here? like from and to
1663 			auto from = parentWindow.focusedWidget;
1664 			parentWindow.focusedWidget.setDynamicState(DynamicState.focus, false);
1665 			parentWindow.focusedWidget = null;
1666 			from.emit!BlurEvent();
1667 			this.emit!FocusOutEvent();
1668 		}
1669 
1670 
1671 		version(win32_widgets) {
1672 			if(this.hwnd !is null)
1673 				SetFocus(this.hwnd);
1674 		}
1675 		//else static if(UsingSimpledisplayX11)
1676 			//this.parentWindow.win.focus();
1677 
1678 		parentWindow.focusedWidget = this;
1679 		parentWindow.focusedWidget.setDynamicState(DynamicState.focus, true);
1680 		this.emit!FocusEvent();
1681 		this.emit!FocusInEvent();
1682 	}
1683 
1684 	/+
1685 	/++
1686 		Unfocuses the widget. This may reset
1687 	+/
1688 	@scriptable
1689 	void blur() {
1690 
1691 	}
1692 	+/
1693 
1694 
1695 	/++
1696 		This is called when the widget is added to a window. It gives you a chance to set up event hooks.
1697 
1698 		Update on May 11, 2021: I'm considering removing this method. You can usually achieve these things through looser-coupled methods.
1699 	+/
1700 	void attachedToWindow(Window w) {}
1701 	/++
1702 		Callback when the widget is added to another widget.
1703 
1704 		Update on May 11, 2021: I'm considering removing this method since I've never actually found it useful.
1705 	+/
1706 	void addedTo(Widget w) {}
1707 
1708 	/++
1709 		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.
1710 
1711 		This is available primarily to be overridden. For example, [MainWindow] overrides it to redirect its children into a central widget.
1712 	+/
1713 	protected void addChild(Widget w, int position = int.max) {
1714 		assert(w._parent !is this, "Child cannot be added twice to the same parent");
1715 		assert(w !is this, "Child cannot be its own parent!");
1716 		w._parent = this;
1717 		if(position == int.max || position == children.length) {
1718 			_children ~= w;
1719 		} else {
1720 			assert(position < _children.length);
1721 			_children.length = _children.length + 1;
1722 			for(int i = cast(int) _children.length - 1; i > position; i--)
1723 				_children[i] = _children[i - 1];
1724 			_children[position] = w;
1725 		}
1726 
1727 		this.parentWindow = this._parentWindow;
1728 
1729 		w.addedTo(this);
1730 
1731 		if(this.hidden)
1732 			w.showing = false;
1733 
1734 		if(parentWindow !is null) {
1735 			w.attachedToWindow(parentWindow);
1736 			parentWindow.queueRecomputeChildLayout();
1737 			parentWindow.redraw();
1738 		}
1739 	}
1740 
1741 	/++
1742 		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.
1743 	+/
1744 	Widget getChildAtPosition(int x, int y) {
1745 		// it goes backward so the last one to show gets picked first
1746 		// might use z-index later
1747 		foreach_reverse(child; children) {
1748 			if(child.hidden)
1749 				continue;
1750 			if(child.x <= x && child.y <= y
1751 				&& ((x - child.x) < child.width)
1752 				&& ((y - child.y) < child.height))
1753 			{
1754 				return child;
1755 			}
1756 		}
1757 
1758 		return null;
1759 	}
1760 
1761 	/++
1762 		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.
1763 
1764 		History:
1765 			Added July 2, 2021 (v10.2)
1766 	+/
1767 	protected void addScrollPosition(ref int x, ref int y) {};
1768 
1769 	/++
1770 		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.
1771 
1772 		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.
1773 
1774 		[paint] is not called for system widgets as the OS library draws them instead.
1775 
1776 
1777 		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.
1778 
1779 		You should also look at [WidgetPainter.visualTheme] to be theme aware.
1780 
1781 		History:
1782 			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.
1783 	+/
1784 	void paint(WidgetPainter painter) {
1785 		version(win32_widgets)
1786 			if(hwnd) {
1787 				return;
1788 			}
1789 		painter.drawThemed(&paintContent); // note this refers to the following overload
1790 	}
1791 
1792 	/++
1793 		Responsible for drawing the content as the theme engine is responsible for other elements.
1794 
1795 		$(WARNING If you override [paint], this method may never be used as it is only called from inside the default implementation of `paint`.)
1796 
1797 		Params:
1798 			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.
1799 
1800 			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.
1801 
1802 			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.
1803 
1804 		Returns:
1805 			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.
1806 
1807 		History:
1808 			Added May 15, 2021
1809 	+/
1810 	Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
1811 		return bounds;
1812 	}
1813 
1814 	deprecated("Change ScreenPainter to WidgetPainter")
1815 	final void paint(ScreenPainter) { assert(0, "Change ScreenPainter to WidgetPainter and recompile your code"); }
1816 
1817 	/// I don't actually like the name of this
1818 	/// this draws a background on it
1819 	void erase(WidgetPainter painter) {
1820 		version(win32_widgets)
1821 			if(hwnd) return; // Windows will do it. I think.
1822 
1823 		auto c = getComputedStyle().background.color;
1824 		painter.fillColor = c;
1825 		painter.outlineColor = c;
1826 
1827 		version(win32_widgets) {
1828 			HANDLE b, p;
1829 			if(c.a == 0 && parent is parentWindow) {
1830 				// I don't remember why I had this really...
1831 				b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
1832 				p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
1833 			}
1834 		}
1835 		painter.drawRectangle(Point(0, 0), width, height);
1836 		version(win32_widgets) {
1837 			if(c.a == 0 && parent is parentWindow) {
1838 				SelectObject(painter.impl.hdc, p);
1839 				SelectObject(painter.impl.hdc, b);
1840 			}
1841 		}
1842 	}
1843 
1844 	///
1845 	WidgetPainter draw() {
1846 		int x = this.x, y = this.y;
1847 		auto parent = this.parent;
1848 		while(parent) {
1849 			x += parent.x;
1850 			y += parent.y;
1851 			parent = parent.parent;
1852 		}
1853 
1854 		auto painter = parentWindow.win.draw(true);
1855 		painter.originX = x;
1856 		painter.originY = y;
1857 		painter.setClipRectangle(Point(0, 0), width, height);
1858 		return WidgetPainter(painter, this);
1859 	}
1860 
1861 	/// 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.
1862 	protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force, bool invalidate) {
1863 		if(hidden)
1864 			return;
1865 
1866 		int paintX = x;
1867 		int paintY = y;
1868 		if(this.useNativeDrawing()) {
1869 			paintX = 0;
1870 			paintY = 0;
1871 			lox = 0;
1872 			loy = 0;
1873 			containment = Rectangle(0, 0, int.max, int.max);
1874 		}
1875 
1876 		painter.originX = lox + paintX;
1877 		painter.originY = loy + paintY;
1878 
1879 		bool actuallyPainted = false;
1880 
1881 		const clip = containment.intersectionOf(Rectangle(Point(lox + paintX, loy + paintY), Size(width, height)));
1882 		if(clip == Rectangle.init) {
1883 			// writeln(this, " clipped out");
1884 			return;
1885 		}
1886 
1887 		bool invalidateChildren = invalidate;
1888 
1889 		if(redrawRequested || force) {
1890 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
1891 
1892 			painter.drawingUpon = this;
1893 
1894 			erase(painter);
1895 			if(painter.visualTheme)
1896 				painter.visualTheme.doPaint(this, painter);
1897 			else
1898 				paint(painter);
1899 
1900 			if(invalidate) {
1901 				// sdpyPrintDebugString("invalidate " ~ typeid(this).name);
1902 				auto region = Rectangle(Point(clip.upperLeft.x - painter.originX, clip.upperRight.y - painter.originY), Size(clip.width, clip.height));
1903 				painter.invalidateRect(region);
1904 				// children are contained inside this, so no need to do extra work
1905 				invalidateChildren = false;
1906 			}
1907 
1908 			redrawRequested = false;
1909 			actuallyPainted = true;
1910 		}
1911 
1912 		foreach(child; children) {
1913 			version(win32_widgets)
1914 				if(child.useNativeDrawing()) continue;
1915 			child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted, invalidateChildren);
1916 		}
1917 
1918 		version(win32_widgets)
1919 		foreach(child; children) {
1920 			if(child.useNativeDrawing) {
1921 				painter = WidgetPainter(child.simpleWindowWrappingHwnd.draw(true), child);
1922 				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
1923 			}
1924 		}
1925 	}
1926 
1927 	protected bool useNativeDrawing() nothrow {
1928 		version(win32_widgets)
1929 			return hwnd !is null;
1930 		else
1931 			return false;
1932 	}
1933 
1934 	private static class RedrawEvent {}
1935 	private __gshared re = new RedrawEvent();
1936 
1937 	private bool redrawRequested;
1938 	///
1939 	final void redraw(string file = __FILE__, size_t line = __LINE__) {
1940 		redrawRequested = true;
1941 
1942 		if(this.parentWindow) {
1943 			auto sw = this.parentWindow.win;
1944 			assert(sw !is null);
1945 			if(!sw.eventQueued!RedrawEvent) {
1946 				sw.postEvent(re);
1947 				//  writeln("redraw requested from ", file,":",line," ", this.parentWindow.win.impl.window);
1948 			}
1949 		}
1950 	}
1951 
1952 	private SimpleWindow drawableWindow;
1953 
1954 	/++
1955 		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.
1956 
1957 		Returns:
1958 			`true` if you should do your default behavior.
1959 
1960 		History:
1961 			Added May 5, 2021
1962 
1963 		Bugs:
1964 			It does not do the static checks on gdc right now.
1965 	+/
1966 	final protected bool emit(EventType, this This, Args...)(Args args) {
1967 		version(GNU) {} else
1968 		static assert(classStaticallyEmits!(This, EventType), "The " ~ This.stringof ~ " class is not declared to emit " ~ EventType.stringof);
1969 		auto e = new EventType(this, args);
1970 		e.dispatch();
1971 		return !e.defaultPrevented;
1972 	}
1973 	/// ditto
1974 	final protected bool emit(string eventString, this This)() {
1975 		auto e = new Event(eventString, this);
1976 		e.dispatch();
1977 		return !e.defaultPrevented;
1978 	}
1979 
1980 	/++
1981 		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.
1982 
1983 		History:
1984 			Added May 5, 2021
1985 	+/
1986 	final public EventListener subscribe(EventType, this This)(void delegate(EventType) handler) {
1987 		static assert(classStaticallyEmits!(This, EventType), "The " ~ This.stringof ~ " class is not declared to emit " ~ EventType.stringof);
1988 		return addEventListener(handler);
1989 	}
1990 
1991 	/++
1992 		Gets the computed style properties from the visual theme.
1993 
1994 		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].)
1995 
1996 		History:
1997 			Added May 8, 2021
1998 	+/
1999 	final StyleInformation getComputedStyle() {
2000 		return StyleInformation(this);
2001 	}
2002 
2003 	int focusableWidgets(scope int delegate(Widget) dg) {
2004 		foreach(widget; WidgetStream(this)) {
2005 			if(widget.tabStop && !widget.hidden) {
2006 				int result = dg(widget);
2007 				if (result)
2008 					return result;
2009 			}
2010 		}
2011 		return 0;
2012 	}
2013 
2014 	/++
2015 		Calculates the border box (that is, the full width/height of the widget, from border edge to border edge)
2016 		for the given content box (the area between the padding)
2017 
2018 		History:
2019 			Added January 4, 2023 (dub v11.0)
2020 	+/
2021 	Rectangle borderBoxForContentBox(Rectangle contentBox) {
2022 		auto cs = getComputedStyle();
2023 
2024 		auto borderWidth = getBorderWidth(cs.borderStyle);
2025 
2026 		auto rect = contentBox;
2027 
2028 		rect.left -= borderWidth;
2029 		rect.right += borderWidth;
2030 		rect.top -= borderWidth;
2031 		rect.bottom += borderWidth;
2032 
2033 		auto insideBorderRect = rect;
2034 
2035 		rect.left -= cs.paddingLeft;
2036 		rect.right += cs.paddingRight;
2037 		rect.top -= cs.paddingTop;
2038 		rect.bottom += cs.paddingBottom;
2039 
2040 		return rect;
2041 	}
2042 
2043 
2044 	// FIXME: I kinda want to hide events from implementation widgets
2045 	// so it just catches them all and stops propagation...
2046 	// i guess i can do it with a event listener on star.
2047 
2048 	mixin Emits!KeyDownEvent; ///
2049 	mixin Emits!KeyUpEvent; ///
2050 	mixin Emits!CharEvent; ///
2051 
2052 	mixin Emits!MouseDownEvent; ///
2053 	mixin Emits!MouseUpEvent; ///
2054 	mixin Emits!ClickEvent; ///
2055 	mixin Emits!DoubleClickEvent; ///
2056 	mixin Emits!MouseMoveEvent; ///
2057 	mixin Emits!MouseOverEvent; ///
2058 	mixin Emits!MouseOutEvent; ///
2059 	mixin Emits!MouseEnterEvent; ///
2060 	mixin Emits!MouseLeaveEvent; ///
2061 
2062 	mixin Emits!ResizeEvent; ///
2063 
2064 	mixin Emits!BlurEvent; ///
2065 	mixin Emits!FocusEvent; ///
2066 
2067 	mixin Emits!FocusInEvent; ///
2068 	mixin Emits!FocusOutEvent; ///
2069 }
2070 
2071 /+
2072 /++
2073 	Interface to indicate that the widget has a simple value property.
2074 
2075 	History:
2076 		Added August 26, 2021
2077 +/
2078 interface HasValue!T {
2079 	/// Getter
2080 	@property T value();
2081 	/// Setter
2082 	@property void value(T);
2083 }
2084 
2085 /++
2086 	Interface to indicate that the widget has a range of possible values for its simple value property.
2087 	This would be present on something like a slider or possibly a number picker.
2088 
2089 	History:
2090 		Added September 11, 2021
2091 +/
2092 interface HasRangeOfValues!T : HasValue!T {
2093 	/// The minimum and maximum values in the range, inclusive.
2094 	@property T minValue();
2095 	@property void minValue(T); /// ditto
2096 	@property T maxValue(); /// ditto
2097 	@property void maxValue(T); /// ditto
2098 
2099 	/// The smallest step the user interface allows. User may still type in values without this limitation.
2100 	@property void step(T);
2101 	@property T step(); /// ditto
2102 }
2103 
2104 /++
2105 	Interface to indicate that the widget has a list of possible values the user can choose from.
2106 	This would be present on something like a drop-down selector.
2107 
2108 	The value is NOT necessarily one of the items on the list. Consider the case of a free-entry
2109 	combobox.
2110 
2111 	History:
2112 		Added September 11, 2021
2113 +/
2114 interface HasListOfValues!T : HasValue!T {
2115 	@property T[] values;
2116 	@property void values(T[]);
2117 
2118 	@property int selectedIndex(); // note it may return -1!
2119 	@property void selectedIndex(int);
2120 }
2121 +/
2122 
2123 /++
2124 	History:
2125 		Added September 2021 (dub v10.4)
2126 +/
2127 class GridLayout : Layout {
2128 
2129 	// 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.
2130 
2131 	/++
2132 		If a widget is too small to fill a grid cell, the graviy tells where it "sticks" to.
2133 	+/
2134 	enum Gravity {
2135 		Center    = 0,
2136 		NorthWest = North | West,
2137 		North     = 0b10_00,
2138 		NorthEast = North | East,
2139 		West      = 0b00_10,
2140 		East      = 0b00_01,
2141 		SouthWest = South | West,
2142 		South     = 0b01_00,
2143 		SouthEast = South | East,
2144 	}
2145 
2146 	/++
2147 		The width and height are in some proportional units and can often just be 12.
2148 	+/
2149 	this(int width, int height, Widget parent) {
2150 		this.gridWidth = width;
2151 		this.gridHeight = height;
2152 		super(parent);
2153 	}
2154 
2155 	/++
2156 		Sets the position of the given child.
2157 
2158 		The units of these arguments are in the proportional grid units you set in the constructor.
2159 	+/
2160 	Widget setChildPosition(return Widget child, int x, int y, int width, int height, Gravity gravity = Gravity.Center) {
2161 		// ensure it is in bounds
2162 		// then ensure no overlaps
2163 
2164 		ChildPosition p = ChildPosition(child, x, y, width, height, gravity);
2165 
2166 		foreach(ref position; positions) {
2167 			if(position.widget is child) {
2168 				position = p;
2169 				goto set;
2170 			}
2171 		}
2172 
2173 		positions ~= p;
2174 
2175 		set:
2176 
2177 		// FIXME: should this batch?
2178 		queueRecomputeChildLayout();
2179 
2180 		return child;
2181 	}
2182 
2183 	override void addChild(Widget w, int position = int.max) {
2184 		super.addChild(w, position);
2185 		//positions ~= ChildPosition(w);
2186 		if(position != int.max) {
2187 			// FIXME: align it so they actually match.
2188 		}
2189 	}
2190 
2191 	override void widgetRemoved(size_t idx, Widget w) {
2192 		// FIXME: keep the positions array aligned
2193 		// positions[idx].widget = null;
2194 	}
2195 
2196 	override void recomputeChildLayout() {
2197 		registerMovement();
2198 		int onGrid = cast(int) positions.length;
2199 		c: foreach(child; children) {
2200 			// just snap it to the grid
2201 			if(onGrid)
2202 			foreach(position; positions)
2203 				if(position.widget is child) {
2204 					child.x = this.width * position.x / this.gridWidth;
2205 					child.y = this.height * position.y / this.gridHeight;
2206 					child.width = this.width * position.width / this.gridWidth;
2207 					child.height = this.height * position.height / this.gridHeight;
2208 
2209 					auto diff = child.width - child.maxWidth();
2210 					// FIXME: gravity?
2211 					if(diff > 0) {
2212 						child.width = child.width - diff;
2213 
2214 						if(position.gravity & Gravity.West) {
2215 							// nothing needed, already aligned
2216 						} else if(position.gravity & Gravity.East) {
2217 							child.x += diff;
2218 						} else {
2219 							child.x += diff / 2;
2220 						}
2221 					}
2222 
2223 					diff = child.height - child.maxHeight();
2224 					// FIXME: gravity?
2225 					if(diff > 0) {
2226 						child.height = child.height - diff;
2227 
2228 						if(position.gravity & Gravity.North) {
2229 							// nothing needed, already aligned
2230 						} else if(position.gravity & Gravity.South) {
2231 							child.y += diff;
2232 						} else {
2233 							child.y += diff / 2;
2234 						}
2235 					}
2236 
2237 
2238 					child.recomputeChildLayout();
2239 					onGrid--;
2240 					continue c;
2241 				}
2242 			// the position isn't given on the grid array, we'll just fill in from where the explicit ones left off.
2243 		}
2244 	}
2245 
2246 	private struct ChildPosition {
2247 		Widget widget;
2248 		int x;
2249 		int y;
2250 		int width;
2251 		int height;
2252 		Gravity gravity;
2253 	}
2254 	private ChildPosition[] positions;
2255 
2256 	int gridWidth = 12;
2257 	int gridHeight = 12;
2258 }
2259 
2260 ///
2261 abstract class ComboboxBase : Widget {
2262 	// if the user can enter arbitrary data, we want to use  2 == CBS_DROPDOWN
2263 	// or to always show the list, we want CBS_SIMPLE == 1
2264 	version(win32_widgets)
2265 		this(uint style, Widget parent) {
2266 			super(parent);
2267 			createWin32Window(this, "ComboBox"w, null, style);
2268 		}
2269 	else version(custom_widgets)
2270 		this(Widget parent) {
2271 			super(parent);
2272 
2273 			addEventListener((KeyDownEvent event) {
2274 				if(event.key == Key.Up) {
2275 					if(selection_ > -1) { // -1 means select blank
2276 						selection_--;
2277 						fireChangeEvent();
2278 					}
2279 					event.preventDefault();
2280 				}
2281 				if(event.key == Key.Down) {
2282 					if(selection_ + 1 < options.length) {
2283 						selection_++;
2284 						fireChangeEvent();
2285 					}
2286 					event.preventDefault();
2287 				}
2288 
2289 			});
2290 
2291 		}
2292 	else static assert(false);
2293 
2294 	/++
2295 		Returns the current list of options in the selection.
2296 
2297 		History:
2298 			Property accessor added March 1, 2022 (dub v10.7). Prior to that, it was private.
2299 	+/
2300 	final @property string[] options() const {
2301 		return cast(string[]) options_;
2302 	}
2303 
2304 	private string[] options_;
2305 	private int selection_ = -1;
2306 
2307 	/++
2308 		Adds an option to the end of options array.
2309 	+/
2310 	void addOption(string s) {
2311 		options_ ~= s;
2312 		version(win32_widgets)
2313 		SendMessageW(hwnd, 323 /*CB_ADDSTRING*/, 0, cast(LPARAM) toWstringzInternal(s));
2314 	}
2315 
2316 	/++
2317 		Gets the current selection as an index into the [options] array. Returns -1 if nothing is selected.
2318 	+/
2319 	int getSelection() {
2320 		return selection_;
2321 	}
2322 
2323 	/++
2324 		Returns the current selection as a string.
2325 
2326 		History:
2327 			Added November 17, 2021
2328 	+/
2329 	string getSelectionString() {
2330 		return selection_ == -1 ? null : options[selection_];
2331 	}
2332 
2333 	/++
2334 		Sets the current selection to an index in the options array, or to the given option if present.
2335 		Please note that the string version may do a linear lookup.
2336 
2337 		Returns:
2338 			the index you passed in
2339 
2340 		History:
2341 			The `string` based overload was added on March 1, 2022 (dub v10.7).
2342 
2343 			The return value was `void` prior to March 1, 2022.
2344 	+/
2345 	int setSelection(int idx) {
2346 		selection_ = idx;
2347 		version(win32_widgets)
2348 		SendMessageW(hwnd, 334 /*CB_SETCURSEL*/, idx, 0);
2349 
2350 		auto t = new SelectionChangedEvent(this, selection_, selection_ == -1 ? null : options[selection_]);
2351 		t.dispatch();
2352 
2353 		return idx;
2354 	}
2355 
2356 	/// ditto
2357 	int setSelection(string s) {
2358 		if(s !is null)
2359 		foreach(idx, item; options)
2360 			if(item == s) {
2361 				return setSelection(cast(int) idx);
2362 			}
2363 		return setSelection(-1);
2364 	}
2365 
2366 	/++
2367 		This event is fired when the selection changes. Note it inherits
2368 		from ChangeEvent!string, meaning you can use that as well, and it also
2369 		fills in [Event.intValue].
2370 	+/
2371 	static class SelectionChangedEvent : ChangeEvent!string {
2372 		this(Widget target, int iv, string sv) {
2373 			super(target, &stringValue);
2374 			this.iv = iv;
2375 			this.sv = sv;
2376 		}
2377 		immutable int iv;
2378 		immutable string sv;
2379 
2380 		override @property string stringValue() { return sv; }
2381 		override @property int intValue() { return iv; }
2382 	}
2383 
2384 	version(win32_widgets)
2385 	override void handleWmCommand(ushort cmd, ushort id) {
2386 		if(cmd == CBN_SELCHANGE) {
2387 			selection_ = cast(int) SendMessageW(hwnd, 327 /* CB_GETCURSEL */, 0, 0);
2388 			fireChangeEvent();
2389 		}
2390 	}
2391 
2392 	private void fireChangeEvent() {
2393 		if(selection_ >= options.length)
2394 			selection_ = -1;
2395 
2396 		auto t = new SelectionChangedEvent(this, selection_, selection_ == -1 ? null : options[selection_]);
2397 		t.dispatch();
2398 	}
2399 
2400 	version(win32_widgets) {
2401 		override int minHeight() { return defaultLineHeight + 6; }
2402 		override int maxHeight() { return defaultLineHeight + 6; }
2403 	} else {
2404 		override int minHeight() { return defaultLineHeight + 4; }
2405 		override int maxHeight() { return defaultLineHeight + 4; }
2406 	}
2407 
2408 	version(custom_widgets) {
2409 
2410 		// FIXME: this should scroll if there's too many elements to reasonably fit on screen
2411 
2412 		SimpleWindow dropDown;
2413 		void popup() {
2414 			auto w = width;
2415 			// FIXME: suggestedDropdownHeight see below
2416 			auto h = cast(int) this.options.length * defaultLineHeight + 8;
2417 
2418 			auto coord = this.globalCoordinates();
2419 			auto dropDown = new SimpleWindow(
2420 				w, h,
2421 				null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.dropdownMenu, WindowFlags.dontAutoShow, parentWindow ? parentWindow.win : null);
2422 
2423 			dropDown.move(coord.x, coord.y + this.height);
2424 
2425 			{
2426 				auto cs = getComputedStyle();
2427 				auto painter = dropDown.draw();
2428 				draw3dFrame(0, 0, w, h, painter, FrameStyle.risen, getComputedStyle().background.color);
2429 				auto p = Point(4, 4);
2430 				painter.outlineColor = cs.foregroundColor;
2431 				foreach(option; options) {
2432 					painter.drawText(p, option);
2433 					p.y += defaultLineHeight;
2434 				}
2435 			}
2436 
2437 			dropDown.setEventHandlers(
2438 				(MouseEvent event) {
2439 					if(event.type == MouseEventType.buttonReleased) {
2440 						dropDown.close();
2441 						auto element = (event.y - 4) / defaultLineHeight;
2442 						if(element >= 0 && element <= options.length) {
2443 							selection_ = element;
2444 
2445 							fireChangeEvent();
2446 						}
2447 					}
2448 				}
2449 			);
2450 
2451 			dropDown.visibilityChanged = (bool visible) {
2452 				if(visible) {
2453 					this.redraw();
2454 					dropDown.grabInput();
2455 				} else {
2456 					dropDown.releaseInputGrab();
2457 				}
2458 			};
2459 
2460 			dropDown.show();
2461 		}
2462 
2463 	}
2464 }
2465 
2466 /++
2467 	A drop-down list where the user must select one of the
2468 	given options. Like `<select>` in HTML.
2469 +/
2470 class DropDownSelection : ComboboxBase {
2471 	this(Widget parent) {
2472 		version(win32_widgets)
2473 			super(3 /* CBS_DROPDOWNLIST */ | WS_VSCROLL, parent);
2474 		else version(custom_widgets) {
2475 			super(parent);
2476 
2477 			addEventListener("focus", () { this.redraw; });
2478 			addEventListener("blur", () { this.redraw; });
2479 			addEventListener(EventType.change, () { this.redraw; });
2480 			addEventListener("mousedown", () { this.focus(); this.popup(); });
2481 			addEventListener((KeyDownEvent event) {
2482 				if(event.key == Key.Space)
2483 					popup();
2484 			});
2485 		} else static assert(false);
2486 	}
2487 
2488 	mixin Padding!q{2};
2489 	static class Style : Widget.Style {
2490 		override FrameStyle borderStyle() { return FrameStyle.risen; }
2491 	}
2492 	mixin OverrideStyle!Style;
2493 
2494 	version(custom_widgets)
2495 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
2496 		auto cs = getComputedStyle();
2497 
2498 		painter.drawText(bounds.upperLeft, selection_ == -1 ? "" : options[selection_]);
2499 
2500 		painter.outlineColor = cs.foregroundColor;
2501 		painter.fillColor = cs.foregroundColor;
2502 
2503 		/+
2504 		Point[4] triangle;
2505 		enum padding = 6;
2506 		enum paddingV = 7;
2507 		enum triangleWidth = 10;
2508 		triangle[0] = Point(width - padding - triangleWidth, paddingV);
2509 		triangle[1] = Point(width - padding - triangleWidth / 2, height - paddingV);
2510 		triangle[2] = Point(width - padding - 0, paddingV);
2511 		triangle[3] = triangle[0];
2512 		painter.drawPolygon(triangle[]);
2513 		+/
2514 
2515 		auto offset = Point((this.width - scaleWithDpi(16)), (this.height - scaleWithDpi(16)) / 2);
2516 
2517 		painter.drawPolygon(
2518 			scaleWithDpi(Point(2, 6) + offset),
2519 			scaleWithDpi(Point(7, 11) + offset),
2520 			scaleWithDpi(Point(12, 6) + offset),
2521 			scaleWithDpi(Point(2, 6) + offset)
2522 		);
2523 
2524 
2525 		return bounds;
2526 	}
2527 
2528 	version(win32_widgets)
2529 	override void registerMovement() {
2530 		version(win32_widgets) {
2531 			if(hwnd) {
2532 				auto pos = getChildPositionRelativeToParentHwnd(this);
2533 				// the height given to this from Windows' perspective is supposed
2534 				// to include the drop down's height. so I add to it to give some
2535 				// room for that.
2536 				// FIXME: maybe make the subclass provide a suggestedDropdownHeight thing
2537 				MoveWindow(hwnd, pos[0], pos[1], width, height + 200, true);
2538 			}
2539 		}
2540 		sendResizeEvent();
2541 	}
2542 }
2543 
2544 /++
2545 	A text box with a drop down arrow listing selections.
2546 	The user can choose from the list, or type their own.
2547 +/
2548 class FreeEntrySelection : ComboboxBase {
2549 	this(Widget parent) {
2550 		version(win32_widgets)
2551 			super(2 /* CBS_DROPDOWN */, parent);
2552 		else version(custom_widgets) {
2553 			super(parent);
2554 			auto hl = new HorizontalLayout(this);
2555 			lineEdit = new LineEdit(hl);
2556 
2557 			tabStop = false;
2558 
2559 			lineEdit.addEventListener("focus", &lineEdit.selectAll);
2560 
2561 			auto btn = new class ArrowButton {
2562 				this() {
2563 					super(ArrowDirection.down, hl);
2564 				}
2565 				override int maxHeight() {
2566 					return lineEdit.maxHeight;
2567 				}
2568 			};
2569 			//btn.addDirectEventListener("focus", &lineEdit.focus);
2570 			btn.addEventListener("triggered", &this.popup);
2571 			addEventListener(EventType.change, (Event event) {
2572 				lineEdit.content = event.stringValue;
2573 				lineEdit.focus();
2574 				redraw();
2575 			});
2576 		}
2577 		else static assert(false);
2578 	}
2579 
2580 	version(custom_widgets) {
2581 		LineEdit lineEdit;
2582 	}
2583 }
2584 
2585 /++
2586 	A combination of free entry with a list below it.
2587 +/
2588 class ComboBox : ComboboxBase {
2589 	this(Widget parent) {
2590 		version(win32_widgets)
2591 			super(1 /* CBS_SIMPLE */ | CBS_NOINTEGRALHEIGHT, parent);
2592 		else version(custom_widgets) {
2593 			super(parent);
2594 			lineEdit = new LineEdit(this);
2595 			listWidget = new ListWidget(this);
2596 			listWidget.multiSelect = false;
2597 			listWidget.addEventListener(EventType.change, delegate(Widget, Event) {
2598 				string c = null;
2599 				foreach(option; listWidget.options)
2600 					if(option.selected) {
2601 						c = option.label;
2602 						break;
2603 					}
2604 				lineEdit.content = c;
2605 			});
2606 
2607 			listWidget.tabStop = false;
2608 			this.tabStop = false;
2609 			listWidget.addEventListener("focus", &lineEdit.focus);
2610 			this.addEventListener("focus", &lineEdit.focus);
2611 
2612 			addDirectEventListener(EventType.change, {
2613 				listWidget.setSelection(selection_);
2614 				if(selection_ != -1)
2615 					lineEdit.content = options[selection_];
2616 				lineEdit.focus();
2617 				redraw();
2618 			});
2619 
2620 			lineEdit.addEventListener("focus", &lineEdit.selectAll);
2621 
2622 			listWidget.addDirectEventListener(EventType.change, {
2623 				int set = -1;
2624 				foreach(idx, opt; listWidget.options)
2625 					if(opt.selected) {
2626 						set = cast(int) idx;
2627 						break;
2628 					}
2629 				if(set != selection_)
2630 					this.setSelection(set);
2631 			});
2632 		} else static assert(false);
2633 	}
2634 
2635 	override int minHeight() { return defaultLineHeight * 3; }
2636 	override int maxHeight() { return cast(int) options.length * defaultLineHeight + defaultLineHeight; }
2637 	override int heightStretchiness() { return 5; }
2638 
2639 	version(custom_widgets) {
2640 		LineEdit lineEdit;
2641 		ListWidget listWidget;
2642 
2643 		override void addOption(string s) {
2644 			listWidget.options ~= ListWidget.Option(s);
2645 			ComboboxBase.addOption(s);
2646 		}
2647 	}
2648 }
2649 
2650 /+
2651 class Spinner : Widget {
2652 	version(win32_widgets)
2653 	this(Widget parent) {
2654 		super(parent);
2655 		parentWindow = parent.parentWindow;
2656 		auto hlayout = new HorizontalLayout(this);
2657 		lineEdit = new LineEdit(hlayout);
2658 		upDownControl = new UpDownControl(hlayout);
2659 	}
2660 
2661 	LineEdit lineEdit;
2662 	UpDownControl upDownControl;
2663 }
2664 
2665 class UpDownControl : Widget {
2666 	version(win32_widgets)
2667 	this(Widget parent) {
2668 		super(parent);
2669 		parentWindow = parent.parentWindow;
2670 		createWin32Window(this, "msctls_updown32"w, null, 4/*UDS_ALIGNRIGHT*/| 2 /* UDS_SETBUDDYINT */ | 16 /* UDS_AUTOBUDDY */ | 32 /* UDS_ARROWKEYS */);
2671 	}
2672 
2673 	override int minHeight() { return defaultLineHeight; }
2674 	override int maxHeight() { return defaultLineHeight * 3/2; }
2675 
2676 	override int minWidth() { return defaultLineHeight * 3/2; }
2677 	override int maxWidth() { return defaultLineHeight * 3/2; }
2678 }
2679 +/
2680 
2681 /+
2682 class DataView : Widget {
2683 	// this is the omnibus data viewer
2684 	// the internal data layout is something like:
2685 	// string[string][] but also each node can have parents
2686 }
2687 +/
2688 
2689 
2690 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb775491(v=vs.85).aspx#PROGRESS_CLASS
2691 
2692 // http://svn.dsource.org/projects/bindings/trunk/win32/commctrl.d
2693 
2694 // FIXME: menus should prolly capture the mouse. ugh i kno.
2695 /*
2696 	TextEdit needs:
2697 
2698 	* caret manipulation
2699 	* selection control
2700 	* convenience functions for appendText, insertText, insertTextAtCaret, etc.
2701 
2702 	For example:
2703 
2704 	connect(paste, &textEdit.insertTextAtCaret);
2705 
2706 	would be nice.
2707 
2708 
2709 
2710 	I kinda want an omnibus dataview that combines list, tree,
2711 	and table - it can be switched dynamically between them.
2712 
2713 	Flattening policy: only show top level, show recursive, show grouped
2714 	List styles: plain list (e.g. <ul>), tiles (some details next to it), icons (like Windows explorer)
2715 
2716 	Single select, multi select, organization, drag+drop
2717 */
2718 
2719 //static if(UsingSimpledisplayX11)
2720 version(win32_widgets) {}
2721 else version(custom_widgets) {
2722 	enum scrollClickRepeatInterval = 50;
2723 
2724 deprecated("Get these properties off `Widget.getComputedStyle` instead. The defaults are now set in the `WidgetPainter.visualTheme`.") {
2725 	enum windowBackgroundColor = Color(212, 212, 212); // used to be 192
2726 	enum activeTabColor = lightAccentColor;
2727 	enum hoveringColor = Color(228, 228, 228);
2728 	enum buttonColor = windowBackgroundColor;
2729 	enum depressedButtonColor = darkAccentColor;
2730 	enum activeListXorColor = Color(255, 255, 127);
2731 	enum progressBarColor = Color(0, 0, 128);
2732 	enum activeMenuItemColor = Color(0, 0, 128);
2733 
2734 }}
2735 else static assert(false);
2736 deprecated("Get these properties off the `visualTheme` instead.") {
2737 	// these are used by horizontal rule so not just custom_widgets. for now at least.
2738 	enum darkAccentColor = Color(172, 172, 172);
2739 	enum lightAccentColor = Color(223, 223, 223); // used to be 223
2740 }
2741 
2742 private const(wchar)* toWstringzInternal(in char[] s) {
2743 	wchar[] str;
2744 	str.reserve(s.length + 1);
2745 	foreach(dchar ch; s)
2746 		str ~= ch;
2747 	str ~= '\0';
2748 	return str.ptr;
2749 }
2750 
2751 static if(SimpledisplayTimerAvailable)
2752 void setClickRepeat(Widget w, int interval, int delay = 250) {
2753 	Timer timer;
2754 	int delayRemaining = delay / interval;
2755 	if(delayRemaining <= 1)
2756 		delayRemaining = 2;
2757 
2758 	immutable originalDelayRemaining = delayRemaining;
2759 
2760 	w.addDirectEventListener((scope MouseDownEvent ev) {
2761 		if(ev.srcElement !is w)
2762 			return;
2763 		if(timer !is null) {
2764 			timer.destroy();
2765 			timer = null;
2766 		}
2767 		delayRemaining = originalDelayRemaining;
2768 		timer = new Timer(interval, () {
2769 			if(delayRemaining > 0)
2770 				delayRemaining--;
2771 			else {
2772 				auto ev = new Event("triggered", w);
2773 				ev.sendDirectly();
2774 			}
2775 		});
2776 	});
2777 
2778 	w.addDirectEventListener((scope MouseUpEvent ev) {
2779 		if(ev.srcElement !is w)
2780 			return;
2781 		if(timer !is null) {
2782 			timer.destroy();
2783 			timer = null;
2784 		}
2785 	});
2786 
2787 	w.addDirectEventListener((scope MouseLeaveEvent ev) {
2788 		if(ev.srcElement !is w)
2789 			return;
2790 		if(timer !is null) {
2791 			timer.destroy();
2792 			timer = null;
2793 		}
2794 	});
2795 
2796 }
2797 else
2798 void setClickRepeat(Widget w, int interval, int delay = 250) {}
2799 
2800 enum FrameStyle {
2801 	none, ///
2802 	risen, /// a 3d pop-out effect (think Windows 95 button)
2803 	sunk, /// a 3d sunken effect (think Windows 95 button as you click on it)
2804 	solid, ///
2805 	dotted, ///
2806 	fantasy, /// a style based on a popular fantasy video game
2807 }
2808 
2809 version(custom_widgets)
2810 deprecated
2811 void draw3dFrame(Widget widget, ScreenPainter painter, FrameStyle style) {
2812 	draw3dFrame(0, 0, widget.width, widget.height, painter, style, WidgetPainter.visualTheme.windowBackgroundColor);
2813 }
2814 
2815 version(custom_widgets)
2816 void draw3dFrame(Widget widget, ScreenPainter painter, FrameStyle style, Color background) {
2817 	draw3dFrame(0, 0, widget.width, widget.height, painter, style, background);
2818 }
2819 
2820 version(custom_widgets)
2821 deprecated
2822 void draw3dFrame(int x, int y, int width, int height, ScreenPainter painter, FrameStyle style) {
2823 	draw3dFrame(x, y, width, height, painter, style, WidgetPainter.visualTheme.windowBackgroundColor);
2824 }
2825 
2826 int getBorderWidth(FrameStyle style) {
2827 	final switch(style) {
2828 		case FrameStyle.sunk, FrameStyle.risen:
2829 			return 2;
2830 		case FrameStyle.none:
2831 			return 0;
2832 		case FrameStyle.solid:
2833 			return 1;
2834 		case FrameStyle.dotted:
2835 			return 1;
2836 		case FrameStyle.fantasy:
2837 			return 3;
2838 	}
2839 }
2840 
2841 int draw3dFrame(int x, int y, int width, int height, ScreenPainter painter, FrameStyle style, Color background, Color border = Color.transparent) {
2842 	int borderWidth = getBorderWidth(style);
2843 	final switch(style) {
2844 		case FrameStyle.sunk, FrameStyle.risen:
2845 			// outer layer
2846 			painter.outlineColor = style == FrameStyle.sunk ? Color.white : Color.black;
2847 		break;
2848 		case FrameStyle.none:
2849 			painter.outlineColor = background;
2850 		break;
2851 		case FrameStyle.solid:
2852 			painter.pen = Pen(border, 1);
2853 		break;
2854 		case FrameStyle.dotted:
2855 			painter.pen = Pen(border, 1, Pen.Style.Dotted);
2856 		break;
2857 		case FrameStyle.fantasy:
2858 			painter.pen = Pen(border, 3);
2859 		break;
2860 	}
2861 
2862 	painter.fillColor = background;
2863 	painter.drawRectangle(Point(x + 0, y + 0), width, height);
2864 
2865 
2866 	if(style == FrameStyle.sunk || style == FrameStyle.risen) {
2867 		// 3d effect
2868 		auto vt = WidgetPainter.visualTheme;
2869 
2870 		painter.outlineColor = (style == FrameStyle.sunk) ? vt.darkAccentColor : vt.lightAccentColor;
2871 		painter.drawLine(Point(x + 0, y + 0), Point(x + width, y + 0));
2872 		painter.drawLine(Point(x + 0, y + 0), Point(x + 0, y + height - 1));
2873 
2874 		// inner layer
2875 		//right, bottom
2876 		painter.outlineColor = (style == FrameStyle.sunk) ? vt.lightAccentColor : vt.darkAccentColor;
2877 		painter.drawLine(Point(x + width - 2, y + 2), Point(x + width - 2, y + height - 2));
2878 		painter.drawLine(Point(x + 2, y + height - 2), Point(x + width - 2, y + height - 2));
2879 		// left, top
2880 		painter.outlineColor = (style == FrameStyle.sunk) ? Color.black : Color.white;
2881 		painter.drawLine(Point(x + 1, y + 1), Point(x + width, y + 1));
2882 		painter.drawLine(Point(x + 1, y + 1), Point(x + 1, y + height - 2));
2883 	} else if(style == FrameStyle.fantasy) {
2884 		painter.pen = Pen(Color.white, 1, Pen.Style.Solid);
2885 		painter.fillColor = Color.transparent;
2886 		painter.drawRectangle(Point(x + 1, y + 1), Point(x + width - 1, y + height - 1));
2887 	}
2888 
2889 	return borderWidth;
2890 }
2891 
2892 /++
2893 	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.
2894 
2895 	See_Also:
2896 		[MenuItem]
2897 		[ToolButton]
2898 		[Menu.addItem]
2899 +/
2900 class Action {
2901 	version(win32_widgets) {
2902 		private int id;
2903 		private static int lastId = 9000;
2904 		private static Action[int] mapping;
2905 	}
2906 
2907 	KeyEvent accelerator;
2908 
2909 	// FIXME: disable message
2910 	// and toggle thing?
2911 	// ??? and trigger arguments too ???
2912 
2913 	/++
2914 		Params:
2915 			label = the textual label
2916 			icon = icon ID. See [GenericIcons]. There is currently no way to do custom icons.
2917 			triggered = initial handler, more can be added via the [triggered] member.
2918 	+/
2919 	this(string label, ushort icon = 0, void delegate() triggered = null) {
2920 		this.label = label;
2921 		this.iconId = icon;
2922 		if(triggered !is null)
2923 			this.triggered ~= triggered;
2924 		version(win32_widgets) {
2925 			id = ++lastId;
2926 			mapping[id] = this;
2927 		}
2928 	}
2929 
2930 	private string label;
2931 	private ushort iconId;
2932 	// icon
2933 
2934 	// when it is triggered, the triggered event is fired on the window
2935 	/// The list of handlers when it is triggered.
2936 	void delegate()[] triggered;
2937 }
2938 
2939 /*
2940 	plan:
2941 		keyboard accelerators
2942 
2943 		* menus (and popups and tooltips)
2944 		* status bar
2945 		* toolbars and buttons
2946 
2947 		sortable table view
2948 
2949 		maybe notification area icons
2950 		basic clipboard
2951 
2952 		* radio box
2953 		splitter
2954 		toggle buttons (optionally mutually exclusive, like in Paint)
2955 		label, rich text display, multi line plain text (selectable)
2956 		* fieldset
2957 		* nestable grid layout
2958 		single line text input
2959 		* multi line text input
2960 		slider
2961 		spinner
2962 		list box
2963 		drop down
2964 		combo box
2965 		auto complete box
2966 		* progress bar
2967 
2968 		terminal window/widget (on unix it might even be a pty but really idk)
2969 
2970 		ok button
2971 		cancel button
2972 
2973 		keyboard hotkeys
2974 
2975 		scroll widget
2976 
2977 		event redirections and network transparency
2978 		script integration
2979 */
2980 
2981 
2982 /*
2983 	MENUS
2984 
2985 	auto bar = new MenuBar(window);
2986 	window.menuBar = bar;
2987 
2988 	auto fileMenu = bar.addItem(new Menu("&File"));
2989 	fileMenu.addItem(new MenuItem("&Exit"));
2990 
2991 
2992 	EVENTS
2993 
2994 	For controls, you should usually use "triggered" rather than "click", etc., because
2995 	triggered handles both keyboard (focus and press as well as hotkeys) and mouse activation.
2996 	This is the case on menus and pushbuttons.
2997 
2998 	"click", on the other hand, currently only fires when it is literally clicked by the mouse.
2999 */
3000 
3001 
3002 /*
3003 enum LinePreference {
3004 	AlwaysOnOwnLine, // always on its own line
3005 	PreferOwnLine, // it will always start a new line, and if max width <= line width, it will expand all the way
3006 	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
3007 }
3008 */
3009 
3010 /++
3011 	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.
3012 
3013 	---
3014 	class MyWidget : Widget {
3015 		this(Widget parent) { super(parent); }
3016 
3017 		// set paddingLeft, paddingRight, paddingTop, and paddingBottom all to `return 4;` in one go:
3018 		mixin Padding!q{4};
3019 
3020 		// set marginLeft, marginRight, marginTop, and marginBottom all to `return 8;` in one go:
3021 		mixin Margin!q{8};
3022 
3023 		// but if I specify one outside, it overrides the override, so now marginLeft is 2,
3024 		// while Top/Bottom/Right remain 8 from the mixin above.
3025 		override int marginLeft() { return 2; }
3026 	}
3027 	---
3028 
3029 
3030 	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]).
3031 
3032 	Padding is the area inside a widget where its background is drawn, but the content avoids.
3033 
3034 	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!).
3035 
3036 	* 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.
3037 +/
3038 mixin template Padding(string code) {
3039 	override int paddingLeft() { return mixin(code);}
3040 	override int paddingRight() { return mixin(code);}
3041 	override int paddingTop() { return mixin(code);}
3042 	override int paddingBottom() { return mixin(code);}
3043 }
3044 
3045 /// ditto
3046 mixin template Margin(string code) {
3047 	override int marginLeft() { return mixin(code);}
3048 	override int marginRight() { return mixin(code);}
3049 	override int marginTop() { return mixin(code);}
3050 	override int marginBottom() { return mixin(code);}
3051 }
3052 
3053 private
3054 void recomputeChildLayout(string relevantMeasure)(Widget parent) {
3055 	enum calcingV = relevantMeasure == "height";
3056 
3057 	parent.registerMovement();
3058 
3059 	if(parent.children.length == 0)
3060 		return;
3061 
3062 	auto parentStyle = parent.getComputedStyle();
3063 
3064 	enum firstThingy = relevantMeasure == "height" ? "Top" : "Left";
3065 	enum secondThingy = relevantMeasure == "height" ? "Bottom" : "Right";
3066 
3067 	enum otherFirstThingy = relevantMeasure == "height" ? "Left" : "Top";
3068 	enum otherSecondThingy = relevantMeasure == "height" ? "Right" : "Bottom";
3069 
3070 	// my own width and height should already be set by the caller of this function...
3071 	int spaceRemaining = mixin("parent." ~ relevantMeasure) -
3072 		mixin("parentStyle.padding"~firstThingy~"()") -
3073 		mixin("parentStyle.padding"~secondThingy~"()");
3074 
3075 	int stretchinessSum;
3076 	int stretchyChildSum;
3077 	int lastMargin = 0;
3078 
3079 	int shrinkinessSum;
3080 	int shrinkyChildSum;
3081 
3082 	// set initial size
3083 	foreach(child; parent.children) {
3084 
3085 		auto childStyle = child.getComputedStyle();
3086 
3087 		if(cast(StaticPosition) child)
3088 			continue;
3089 		if(child.hidden)
3090 			continue;
3091 
3092 		const iw = child.flexBasisWidth();
3093 		const ih = child.flexBasisHeight();
3094 
3095 		static if(calcingV) {
3096 			child.width = parent.width -
3097 				mixin("childStyle.margin"~otherFirstThingy~"()") -
3098 				mixin("childStyle.margin"~otherSecondThingy~"()") -
3099 				mixin("parentStyle.padding"~otherFirstThingy~"()") -
3100 				mixin("parentStyle.padding"~otherSecondThingy~"()");
3101 
3102 			if(child.width < 0)
3103 				child.width = 0;
3104 			if(child.width > childStyle.maxWidth())
3105 				child.width = childStyle.maxWidth();
3106 
3107 			if(iw > 0) {
3108 				auto totalPossible = child.width;
3109 				if(child.width > iw && child.widthStretchiness() == 0)
3110 					child.width = iw;
3111 			}
3112 
3113 			child.height = mymax(childStyle.minHeight(), ih);
3114 		} else {
3115 			// set to take all the space
3116 			child.height = parent.height -
3117 				mixin("childStyle.margin"~firstThingy~"()") -
3118 				mixin("childStyle.margin"~secondThingy~"()") -
3119 				mixin("parentStyle.padding"~firstThingy~"()") -
3120 				mixin("parentStyle.padding"~secondThingy~"()");
3121 
3122 			// then clamp it
3123 			if(child.height < 0)
3124 				child.height = 0;
3125 			if(child.height > childStyle.maxHeight())
3126 				child.height = childStyle.maxHeight();
3127 
3128 			// and if possible, respect the ideal target
3129 			if(ih > 0) {
3130 				auto totalPossible = child.height;
3131 				if(child.height > ih && child.heightStretchiness() == 0)
3132 					child.height = ih;
3133 			}
3134 
3135 			// if we have an ideal, try to respect it, otehrwise, just use the minimum
3136 			child.width = mymax(childStyle.minWidth(), iw);
3137 		}
3138 
3139 		spaceRemaining -= mixin("child." ~ relevantMeasure);
3140 
3141 		int thisMargin = mymax(lastMargin, mixin("childStyle.margin"~firstThingy~"()"));
3142 		auto margin = mixin("childStyle.margin" ~ secondThingy ~ "()");
3143 		lastMargin = margin;
3144 		spaceRemaining -= thisMargin + margin;
3145 
3146 		auto s = mixin("child." ~ relevantMeasure ~ "Stretchiness()");
3147 		stretchinessSum += s;
3148 		if(s > 0)
3149 			stretchyChildSum++;
3150 
3151 		auto s2 = mixin("child." ~ relevantMeasure ~ "Shrinkiness()");
3152 		shrinkinessSum += s2;
3153 		if(s2 > 0)
3154 			shrinkyChildSum++;
3155 	}
3156 
3157 	if(spaceRemaining < 0 && shrinkyChildSum) {
3158 		// shrink to get into the space if it is possible
3159 		auto toRemove = -spaceRemaining;
3160 		auto removalPerItem  = toRemove * shrinkinessSum / shrinkyChildSum;
3161 		auto remainder = toRemove * shrinkinessSum % shrinkyChildSum;
3162 
3163 		// FIXME: wtf why am i shrinking things with no shrinkiness?
3164 
3165 		foreach(child; parent.children) {
3166 			auto childStyle = child.getComputedStyle();
3167 			if(cast(StaticPosition) child)
3168 				continue;
3169 			if(child.hidden)
3170 				continue;
3171 			static if(calcingV) {
3172 				auto maximum = childStyle.maxHeight();
3173 			} else {
3174 				auto maximum = childStyle.maxWidth();
3175 			}
3176 
3177 			if(mixin("child._" ~ relevantMeasure) >= maximum)
3178 				continue;
3179 
3180 			mixin("child._" ~ relevantMeasure) -= removalPerItem + remainder; // this is removing more than needed to trigger the next thing. ugh.
3181 
3182 			spaceRemaining += removalPerItem + remainder;
3183 		}
3184 	}
3185 
3186 	// stretch to fill space
3187 	while(spaceRemaining > 0 && stretchinessSum && stretchyChildSum) {
3188 		auto spacePerChild = spaceRemaining / stretchinessSum;
3189 		bool spreadEvenly;
3190 		bool giveToBiggest;
3191 		if(spacePerChild <= 0) {
3192 			spacePerChild = spaceRemaining / stretchyChildSum;
3193 			spreadEvenly = true;
3194 		}
3195 		if(spacePerChild <= 0) {
3196 			giveToBiggest = true;
3197 		}
3198 		int previousSpaceRemaining = spaceRemaining;
3199 		stretchinessSum = 0;
3200 		Widget mostStretchy;
3201 		int mostStretchyS;
3202 		foreach(child; parent.children) {
3203 			auto childStyle = child.getComputedStyle();
3204 			if(cast(StaticPosition) child)
3205 				continue;
3206 			if(child.hidden)
3207 				continue;
3208 			static if(calcingV) {
3209 				auto maximum = childStyle.maxHeight();
3210 			} else {
3211 				auto maximum = childStyle.maxWidth();
3212 			}
3213 
3214 			if(mixin("child." ~ relevantMeasure) >= maximum) {
3215 				auto adj = mixin("child." ~ relevantMeasure) - maximum;
3216 				mixin("child._" ~ relevantMeasure) -= adj;
3217 				spaceRemaining += adj;
3218 				continue;
3219 			}
3220 			auto s = mixin("child." ~ relevantMeasure ~ "Stretchiness()");
3221 			if(s <= 0)
3222 				continue;
3223 			auto spaceAdjustment = spacePerChild * (spreadEvenly ? 1 : s);
3224 			mixin("child._" ~ relevantMeasure) += spaceAdjustment;
3225 			spaceRemaining -= spaceAdjustment;
3226 			if(mixin("child." ~ relevantMeasure) > maximum) {
3227 				auto diff = mixin("child." ~ relevantMeasure) - maximum;
3228 				mixin("child._" ~ relevantMeasure) -= diff;
3229 				spaceRemaining += diff;
3230 			} else if(mixin("child._" ~ relevantMeasure) < maximum) {
3231 				stretchinessSum += mixin("child." ~ relevantMeasure ~ "Stretchiness()");
3232 				if(mostStretchy is null || s >= mostStretchyS) {
3233 					mostStretchy = child;
3234 					mostStretchyS = s;
3235 				}
3236 			}
3237 		}
3238 
3239 		if(giveToBiggest && mostStretchy !is null) {
3240 			auto child = mostStretchy;
3241 			auto childStyle = child.getComputedStyle();
3242 			int spaceAdjustment = spaceRemaining;
3243 
3244 			static if(calcingV)
3245 				auto maximum = childStyle.maxHeight();
3246 			else
3247 				auto maximum = childStyle.maxWidth();
3248 
3249 			mixin("child._" ~ relevantMeasure) += spaceAdjustment;
3250 			spaceRemaining -= spaceAdjustment;
3251 			if(mixin("child._" ~ relevantMeasure) > maximum) {
3252 				auto diff = mixin("child." ~ relevantMeasure) - maximum;
3253 				mixin("child._" ~ relevantMeasure) -= diff;
3254 				spaceRemaining += diff;
3255 			}
3256 		}
3257 
3258 		if(spaceRemaining == previousSpaceRemaining) {
3259 			if(mostStretchy !is null) {
3260 				static if(calcingV)
3261 					auto maximum = mostStretchy.maxHeight();
3262 				else
3263 					auto maximum = mostStretchy.maxWidth();
3264 
3265 				mixin("mostStretchy._" ~ relevantMeasure) += spaceRemaining;
3266 				if(mixin("mostStretchy._" ~ relevantMeasure) > maximum)
3267 					mixin("mostStretchy._" ~ relevantMeasure) = maximum;
3268 			}
3269 			break; // apparently nothing more we can do
3270 		}
3271 	}
3272 
3273 	foreach(child; parent.children) {
3274 		auto childStyle = child.getComputedStyle();
3275 		if(cast(StaticPosition) child)
3276 			continue;
3277 		if(child.hidden)
3278 			continue;
3279 
3280 		static if(calcingV)
3281 			auto maximum = childStyle.maxHeight();
3282 		else
3283 			auto maximum = childStyle.maxWidth();
3284 		if(mixin("child._" ~ relevantMeasure) > maximum)
3285 			mixin("child._" ~ relevantMeasure) = maximum;
3286 	}
3287 
3288 	// position
3289 	lastMargin = 0;
3290 	int currentPos = mixin("parent.padding"~firstThingy~"()");
3291 	foreach(child; parent.children) {
3292 		auto childStyle = child.getComputedStyle();
3293 		if(cast(StaticPosition) child) {
3294 			child.recomputeChildLayout();
3295 			continue;
3296 		}
3297 		if(child.hidden)
3298 			continue;
3299 		auto margin = mixin("childStyle.margin" ~ secondThingy ~ "()");
3300 		int thisMargin = mymax(lastMargin, mixin("childStyle.margin"~firstThingy~"()"));
3301 		currentPos += thisMargin;
3302 		static if(calcingV) {
3303 			child.x = parentStyle.paddingLeft() + childStyle.marginLeft();
3304 			child.y = currentPos;
3305 		} else {
3306 			child.x = currentPos;
3307 			child.y = parentStyle.paddingTop() + childStyle.marginTop();
3308 
3309 		}
3310 		currentPos += mixin("child." ~ relevantMeasure);
3311 		currentPos += margin;
3312 		lastMargin = margin;
3313 
3314 		child.recomputeChildLayout();
3315 	}
3316 }
3317 
3318 int mymax(int a, int b) { return a > b ? a : b; }
3319 int mymax(int a, int b, int c) {
3320 	auto d = mymax(a, b);
3321 	return c > d ? c : d;
3322 }
3323 
3324 // OK so we need to make getting at the native window stuff possible in simpledisplay.d
3325 // and here, it must be integrable with the layout, the event system, and not be painted over.
3326 version(win32_widgets) {
3327 
3328 	// this function just does stuff that a parent window needs for redirection
3329 	int WindowProcedureHelper(Widget this_, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
3330 		this_.hookedWndProc(msg, wParam, lParam);
3331 
3332 		switch(msg) {
3333 
3334 			case WM_VSCROLL, WM_HSCROLL:
3335 				auto pos = HIWORD(wParam);
3336 				auto m = LOWORD(wParam);
3337 
3338 				auto scrollbarHwnd = cast(HWND) lParam;
3339 
3340 				if(auto widgetp = scrollbarHwnd in Widget.nativeMapping) {
3341 
3342 					//auto smw = cast(ScrollMessageWidget) widgetp.parent;
3343 
3344 					switch(m) {
3345 						/+
3346 						// I don't think those messages are ever actually sent normally by the widget itself,
3347 						// they are more used for the keyboard interface. methinks.
3348 						case SB_BOTTOM:
3349 							// writeln("end");
3350 							auto event = new Event("scrolltoend", *widgetp);
3351 							event.dispatch();
3352 							//if(!event.defaultPrevented)
3353 						break;
3354 						case SB_TOP:
3355 							// writeln("top");
3356 							auto event = new Event("scrolltobeginning", *widgetp);
3357 							event.dispatch();
3358 						break;
3359 						case SB_ENDSCROLL:
3360 							// idk
3361 						break;
3362 						+/
3363 						case SB_LINEDOWN:
3364 							(*widgetp).emitCommand!"scrolltonextline"();
3365 						return 0;
3366 						case SB_LINEUP:
3367 							(*widgetp).emitCommand!"scrolltopreviousline"();
3368 						return 0;
3369 						case SB_PAGEDOWN:
3370 							(*widgetp).emitCommand!"scrolltonextpage"();
3371 						return 0;
3372 						case SB_PAGEUP:
3373 							(*widgetp).emitCommand!"scrolltopreviouspage"();
3374 						return 0;
3375 						case SB_THUMBPOSITION:
3376 							auto ev = new ScrollToPositionEvent(*widgetp, pos);
3377 							ev.dispatch();
3378 						return 0;
3379 						case SB_THUMBTRACK:
3380 							// eh kinda lying but i like the real time update display
3381 							auto ev = new ScrollToPositionEvent(*widgetp, pos);
3382 							ev.dispatch();
3383 
3384 							// the event loop doesn't seem to carry on with a requested redraw..
3385 							// so we request it to get our dirty bit set...
3386 							// then we need to immediately actually redraw it too for instant feedback to user
3387 							SimpleWindow.processAllCustomEvents();
3388 							SimpleWindow.processAllCustomEvents();
3389 							//if(this_.parentWindow)
3390 								//this_.parentWindow.actualRedraw();
3391 
3392 							// and this ensures the WM_PAINT message is sent fairly quickly
3393 							// still seems to lag a little in large windows but meh it basically works.
3394 							if(this_.parentWindow) {
3395 								// FIXME: if painting is slow, this does still lag
3396 								// we probably will want to expose some user hook to ScrollWindowEx
3397 								// or something.
3398 								UpdateWindow(this_.parentWindow.hwnd);
3399 							}
3400 						return 0;
3401 						default:
3402 					}
3403 				}
3404 			break;
3405 
3406 			case WM_CONTEXTMENU:
3407 				auto hwndFrom = cast(HWND) wParam;
3408 
3409 				auto xPos = cast(short) LOWORD(lParam);
3410 				auto yPos = cast(short) HIWORD(lParam);
3411 
3412 				if(auto widgetp = hwndFrom in Widget.nativeMapping) {
3413 					POINT p;
3414 					p.x = xPos;
3415 					p.y = yPos;
3416 					ScreenToClient(hwnd, &p);
3417 					auto clientX = cast(ushort) p.x;
3418 					auto clientY = cast(ushort) p.y;
3419 
3420 					auto wap = widgetAtPoint(*widgetp, clientX, clientY);
3421 
3422 					if(wap.widget.showContextMenu(wap.x, wap.y, xPos, yPos)) {
3423 						return 0;
3424 					}
3425 				}
3426 			break;
3427 
3428 			case WM_DRAWITEM:
3429 				auto dis = cast(DRAWITEMSTRUCT*) lParam;
3430 				if(auto widgetp = dis.hwndItem in Widget.nativeMapping) {
3431 					return (*widgetp).handleWmDrawItem(dis);
3432 				}
3433 			break;
3434 
3435 			case WM_NOTIFY:
3436 				auto hdr = cast(NMHDR*) lParam;
3437 				auto hwndFrom = hdr.hwndFrom;
3438 				auto code = hdr.code;
3439 
3440 				if(auto widgetp = hwndFrom in Widget.nativeMapping) {
3441 					return (*widgetp).handleWmNotify(hdr, code, mustReturn);
3442 				}
3443 			break;
3444 			case WM_COMMAND:
3445 				auto handle = cast(HWND) lParam;
3446 				auto cmd = HIWORD(wParam);
3447 				return processWmCommand(hwnd, handle, cmd, LOWORD(wParam));
3448 
3449 			default:
3450 				// pass it on
3451 		}
3452 		return 0;
3453 	}
3454 
3455 
3456 
3457 	extern(Windows)
3458 	private
3459 	// this is called by native child windows, whereas the other hook is done by simpledisplay windows
3460 	// but can i merge them?!
3461 	LRESULT HookedWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
3462 		// try { writeln(iMessage); } catch(Exception e) {};
3463 
3464 		if(auto te = hWnd in Widget.nativeMapping) {
3465 			try {
3466 
3467 				te.hookedWndProc(iMessage, wParam, lParam);
3468 
3469 				int mustReturn;
3470 				auto ret = WindowProcedureHelper(*te, hWnd, iMessage, wParam, lParam, mustReturn);
3471 				if(mustReturn)
3472 					return ret;
3473 
3474 				if(iMessage == WM_SETFOCUS) {
3475 					auto lol = *te;
3476 					while(lol !is null && lol.implicitlyCreated)
3477 						lol = lol.parent;
3478 					lol.focus();
3479 					//(*te).parentWindow.focusedWidget = lol;
3480 				}
3481 
3482 
3483 				if(iMessage == WM_CTLCOLORBTN || iMessage == WM_CTLCOLORSTATIC) {
3484 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
3485 					return cast(typeof(return)) GetSysColorBrush(COLOR_3DFACE); // this is the window background color...
3486 						//GetStockObject(NULL_BRUSH);
3487 				}
3488 
3489 				auto pos = getChildPositionRelativeToParentOrigin(*te);
3490 				lastDefaultPrevented = false;
3491 				// try { writeln(typeid(*te)); } catch(Exception e) {}
3492 				if(SimpleWindow.triggerEvents(hWnd, iMessage, wParam, lParam, pos[0], pos[1], (*te).parentWindow.win) || !lastDefaultPrevented)
3493 					return CallWindowProcW((*te).originalWindowProcedure, hWnd, iMessage, wParam, lParam);
3494 				else {
3495 					// it was something we recognized, should only call the window procedure if the default was not prevented
3496 				}
3497 			} catch(Exception e) {
3498 				assert(0, e.toString());
3499 			}
3500 			return 0;
3501 		}
3502 		assert(0, "shouldn't be receiving messages for this window....");
3503 		//assert(0, to!string(hWnd) ~ " :: " ~ to!string(TextEdit.nativeMapping)); // not supposed to happen
3504 	}
3505 
3506 	extern(Windows)
3507 	private
3508 	// see for info https://jeffpar.github.io/kbarchive/kb/079/Q79982/
3509 	LRESULT HookedWndProcBSGROUPBOX_HACK(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
3510 		if(iMessage == WM_ERASEBKGND) {
3511 			auto dc = GetDC(hWnd);
3512 			auto b = SelectObject(dc, GetSysColorBrush(COLOR_3DFACE));
3513 			auto p = SelectObject(dc, GetStockObject(NULL_PEN));
3514 			RECT r;
3515 			GetWindowRect(hWnd, &r);
3516 			// since the pen is null, to fill the whole space, we need the +1 on both.
3517 			gdi.Rectangle(dc, 0, 0, r.right - r.left + 1, r.bottom - r.top + 1);
3518 			SelectObject(dc, p);
3519 			SelectObject(dc, b);
3520 			ReleaseDC(hWnd, dc);
3521 			InvalidateRect(hWnd, null, false); // redraw the border
3522 			return 1;
3523 		}
3524 		return HookedWndProc(hWnd, iMessage, wParam, lParam);
3525 	}
3526 
3527 	/++
3528 		Calls MS Windows' CreateWindowExW function to create a native backing for the given widget. It will create
3529 		needed mappings, window procedure hooks, and other private member variables needed to tie it into the rest
3530 		of minigui's expectations.
3531 
3532 		This should be called in your widget's constructor AFTER you call `super(parent);`. The parent window
3533 		member MUST already be initialized for this function to succeed, which is done by [Widget]'s base constructor.
3534 
3535 		It assumes `className` is zero-terminated. It should come from a `"wide string literal"w`.
3536 
3537 		To check if you can use this, use `static if(UsingWin32Widgets)`.
3538 	+/
3539 	void createWin32Window(Widget p, const(wchar)[] className, string windowText, DWORD style, DWORD extStyle = 0) {
3540 		assert(p.parentWindow !is null);
3541 		assert(p.parentWindow.win.impl.hwnd !is null);
3542 
3543 		auto bsgroupbox = style == BS_GROUPBOX;
3544 
3545 		HWND phwnd;
3546 
3547 		auto wtf = p.parent;
3548 		while(wtf) {
3549 			if(wtf.hwnd !is null) {
3550 				phwnd = wtf.hwnd;
3551 				break;
3552 			}
3553 			wtf = wtf.parent;
3554 		}
3555 
3556 		if(phwnd is null)
3557 			phwnd = p.parentWindow.win.impl.hwnd;
3558 
3559 		assert(phwnd !is null);
3560 
3561 		WCharzBuffer wt = WCharzBuffer(windowText);
3562 
3563 		style |= WS_VISIBLE | WS_CHILD;
3564 		//if(className != WC_TABCONTROL)
3565 			style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
3566 		p.hwnd = CreateWindowExW(extStyle, className.ptr, wt.ptr, style,
3567 				CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
3568 				phwnd, null, cast(HINSTANCE) GetModuleHandle(null), null);
3569 
3570 		assert(p.hwnd !is null);
3571 
3572 
3573 		static HFONT font;
3574 		if(font is null) {
3575 			NONCLIENTMETRICS params;
3576 			params.cbSize = params.sizeof;
3577 			if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
3578 				font = CreateFontIndirect(&params.lfMessageFont);
3579 			}
3580 		}
3581 
3582 		if(font)
3583 			SendMessage(p.hwnd, WM_SETFONT, cast(uint) font, true);
3584 
3585 		p.simpleWindowWrappingHwnd = new SimpleWindow(p.hwnd);
3586 		p.simpleWindowWrappingHwnd.beingOpenKeepsAppOpen = false;
3587 		Widget.nativeMapping[p.hwnd] = p;
3588 
3589 		if(bsgroupbox)
3590 		p.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(p.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProcBSGROUPBOX_HACK);
3591 		else
3592 		p.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(p.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc);
3593 
3594 		EnumChildWindows(p.hwnd, &childHandler, cast(LPARAM) cast(void*) p);
3595 
3596 		p.registerMovement();
3597 	}
3598 }
3599 
3600 version(win32_widgets)
3601 private
3602 extern(Windows) BOOL childHandler(HWND hwnd, LPARAM lparam) {
3603 	if(hwnd is null || hwnd in Widget.nativeMapping)
3604 		return true;
3605 	auto parent = cast(Widget) cast(void*) lparam;
3606 	Widget p = new Widget(null);
3607 	p._parent = parent;
3608 	p.parentWindow = parent.parentWindow;
3609 	p.hwnd = hwnd;
3610 	p.implicitlyCreated = true;
3611 	Widget.nativeMapping[p.hwnd] = p;
3612 	p.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(p.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc);
3613 	return true;
3614 }
3615 
3616 /++
3617 	Encapsulates the simpledisplay [ScreenPainter] for use on a [Widget], with [VisualTheme] and invalidated area awareness.
3618 +/
3619 struct WidgetPainter {
3620 	this(ScreenPainter screenPainter, Widget drawingUpon) {
3621 		this.drawingUpon = drawingUpon;
3622 		this.screenPainter = screenPainter;
3623 		if(auto font = visualTheme.defaultFontCached(drawingUpon.currentDpi))
3624 			this.screenPainter.setFont(font);
3625 	}
3626 
3627 	/++
3628 		EXPERIMENTAL. subject to change.
3629 
3630 		When you draw a cursor, you can draw this to notify your window of where it is,
3631 		for IME systems to use.
3632 	+/
3633 	void notifyCursorPosition(int x, int y, int width, int height) {
3634 		if(auto a = drawingUpon.parentWindow)
3635 		if(auto w = a.inputProxy) {
3636 			w.setIMEPopupLocation(x + screenPainter.originX + width, y + screenPainter.originY + height);
3637 		}
3638 	}
3639 
3640 
3641 	///
3642 	ScreenPainter screenPainter;
3643 	/// Forward to the screen painter for other methods
3644 	alias screenPainter this;
3645 
3646 	private Widget drawingUpon;
3647 
3648 	/++
3649 		This is the list of rectangles that actually need to be redrawn.
3650 
3651 		Not actually implemented yet.
3652 	+/
3653 	Rectangle[] invalidatedRectangles;
3654 
3655 	private static BaseVisualTheme _visualTheme;
3656 
3657 	/++
3658 		Functions to access the visual theme and helpers to easily use it.
3659 
3660 		These are aware of the current widget's computed style out of the theme.
3661 	+/
3662 	static @property BaseVisualTheme visualTheme() {
3663 		if(_visualTheme is null)
3664 			_visualTheme = new DefaultVisualTheme();
3665 		return _visualTheme;
3666 	}
3667 
3668 	/// ditto
3669 	static @property void visualTheme(BaseVisualTheme theme) {
3670 		_visualTheme = theme;
3671 
3672 		// FIXME: notify all windows about the new theme
3673 	}
3674 
3675 	/// ditto
3676 	Color themeForeground() {
3677 		return drawingUpon.getComputedStyle().foregroundColor();
3678 	}
3679 
3680 	/// ditto
3681 	Color themeBackground() {
3682 		return drawingUpon.getComputedStyle().background.color;
3683 	}
3684 
3685 	int isDarkTheme() {
3686 		return 0; // unspecified, yes, no as enum. FIXME
3687 	}
3688 
3689 	/++
3690 		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.
3691 
3692 		It gives your draw delegate a [Rectangle] representing the coordinates inside your border and padding.
3693 
3694 		If you change teh clip rectangle, you should change it back before you return.
3695 
3696 
3697 		The sequence it uses is:
3698 			background
3699 			content (delegated to you)
3700 			border
3701 			focused outline
3702 			selected overlay
3703 
3704 		Example code:
3705 
3706 		---
3707 		void paint(WidgetPainter painter) {
3708 			painter.drawThemed((bounds) {
3709 				return bounds; // if the selection overlay should be contained, you can return it here.
3710 			});
3711 		}
3712 		---
3713 	+/
3714 	void drawThemed(scope Rectangle delegate(const Rectangle bounds) drawBody) {
3715 		drawThemed((WidgetPainter painter, const Rectangle bounds) {
3716 			return drawBody(bounds);
3717 		});
3718 	}
3719 	// this overload is actually mroe for setting the delegate to a virtual function
3720 	void drawThemed(scope Rectangle delegate(WidgetPainter painter, const Rectangle bounds) drawBody) {
3721 		Rectangle rect = Rectangle(0, 0, drawingUpon.width, drawingUpon.height);
3722 
3723 		auto cs = drawingUpon.getComputedStyle();
3724 
3725 		auto bg = cs.background.color;
3726 
3727 		auto borderWidth = draw3dFrame(0, 0, drawingUpon.width, drawingUpon.height, this, cs.borderStyle, bg, cs.borderColor);
3728 
3729 		rect.left += borderWidth;
3730 		rect.right -= borderWidth;
3731 		rect.top += borderWidth;
3732 		rect.bottom -= borderWidth;
3733 
3734 		auto insideBorderRect = rect;
3735 
3736 		rect.left += cs.paddingLeft;
3737 		rect.right -= cs.paddingRight;
3738 		rect.top += cs.paddingTop;
3739 		rect.bottom -= cs.paddingBottom;
3740 
3741 		this.outlineColor = this.themeForeground;
3742 		this.fillColor = bg;
3743 
3744 		auto widgetFont = cs.fontCached;
3745 		if(widgetFont !is null)
3746 			this.setFont(widgetFont);
3747 
3748 		rect = drawBody(this, rect);
3749 
3750 		if(widgetFont !is null) {
3751 			if(auto vtFont = visualTheme.defaultFontCached(drawingUpon.currentDpi))
3752 				this.setFont(vtFont);
3753 			else
3754 				this.setFont(null);
3755 		}
3756 
3757 		if(auto os = cs.outlineStyle()) {
3758 			this.pen = Pen(cs.outlineColor(), 1, os == FrameStyle.dotted ? Pen.Style.Dotted : Pen.Style.Solid);
3759 			this.fillColor = Color.transparent;
3760 			this.drawRectangle(insideBorderRect);
3761 		}
3762 	}
3763 
3764 	/++
3765 		First, draw the background.
3766 		Then draw your content.
3767 		Next, draw the border.
3768 		And the focused indicator.
3769 		And the is-selected box.
3770 
3771 		If it is focused i can draw the outline too...
3772 
3773 		If selected i can even do the xor action but that's at the end.
3774 	+/
3775 	void drawThemeBackground() {
3776 
3777 	}
3778 
3779 	void drawThemeBorder() {
3780 
3781 	}
3782 
3783 	// all this stuff is a dangerous experiment....
3784 	static class ScriptableVersion {
3785 		ScreenPainterImplementation* p;
3786 		int originX, originY;
3787 
3788 		@scriptable:
3789 		void drawRectangle(int x, int y, int width, int height) {
3790 			p.drawRectangle(x + originX, y + originY, width, height);
3791 		}
3792 		void drawLine(int x1, int y1, int x2, int y2) {
3793 			p.drawLine(x1 + originX, y1 + originY, x2 + originX, y2 + originY);
3794 		}
3795 		void drawText(int x, int y, string text) {
3796 			p.drawText(x + originX, y + originY, 100000, 100000, text, 0);
3797 		}
3798 		void setOutlineColor(int r, int g, int b) {
3799 			p.pen = Pen(Color(r,g,b), 1);
3800 		}
3801 		void setFillColor(int r, int g, int b) {
3802 			p.fillColor = Color(r,g,b);
3803 		}
3804 	}
3805 
3806 	ScriptableVersion toArsdJsvar() {
3807 		auto sv = new ScriptableVersion;
3808 		sv.p = this.screenPainter.impl;
3809 		sv.originX = this.screenPainter.originX;
3810 		sv.originY = this.screenPainter.originY;
3811 		return sv;
3812 	}
3813 
3814 	static WidgetPainter fromJsVar(T)(T t) {
3815 		return WidgetPainter.init;
3816 	}
3817 	// done..........
3818 }
3819 
3820 
3821 struct Style {
3822 	static struct helper(string m, T) {
3823 		enum method = m;
3824 		T v;
3825 
3826 		mixin template MethodOverride(typeof(this) v) {
3827 			mixin("override typeof(v.v) "~v.method~"() { return v.v; }");
3828 		}
3829 	}
3830 
3831 	static auto opDispatch(string method, T)(T value) {
3832 		return helper!(method, T)(value);
3833 	}
3834 }
3835 
3836 /++
3837 	Implementation detail of the [ControlledBy] UDA.
3838 
3839 	History:
3840 		Added Oct 28, 2020
3841 +/
3842 struct ControlledBy_(T, Args...) {
3843 	Args args;
3844 
3845 	static if(Args.length)
3846 	this(Args args) {
3847 		this.args = args;
3848 	}
3849 
3850 	private T construct(Widget parent) {
3851 		return new T(args, parent);
3852 	}
3853 }
3854 
3855 /++
3856 	User-defined attribute you can add to struct members contrlled by [addDataControllerWidget] or [dialog] to tell which widget you want created for them.
3857 
3858 	History:
3859 		Added Oct 28, 2020
3860 +/
3861 auto ControlledBy(T, Args...)(Args args) {
3862 	return ControlledBy_!(T, Args)(args);
3863 }
3864 
3865 struct ContainerMeta {
3866 	string name;
3867 	ContainerMeta[] children;
3868 	Widget function(Widget parent) factory;
3869 
3870 	Widget instantiate(Widget parent) {
3871 		auto n = factory(parent);
3872 		n.name = name;
3873 		foreach(child; children)
3874 			child.instantiate(n);
3875 		return n;
3876 	}
3877 }
3878 
3879 /++
3880 	This is a helper for [addDataControllerWidget]. You can use it as a UDA on the type. See
3881 	http://dpldocs.info/this-week-in-d/Blog.Posted_2020_11_02.html for more information.
3882 
3883 	Please note that as of May 28, 2021, a dmd bug prevents this from compiling on module-level
3884 	structures. It works fine on structs declared inside functions though.
3885 
3886 	See: https://issues.dlang.org/show_bug.cgi?id=21984
3887 +/
3888 template Container(CArgs...) {
3889 	static if(CArgs.length && is(CArgs[0] : Widget)) {
3890 		private alias Super = CArgs[0];
3891 		private alias CArgs2 = CArgs[1 .. $];
3892 	} else {
3893 		private alias Super = Layout;
3894 		private alias CArgs2 = CArgs;
3895 	}
3896 
3897 	class Container : Super {
3898 		this(Widget parent) { super(parent); }
3899 
3900 		// just to partially support old gdc versions
3901 		version(GNU) {
3902 			static if(CArgs2.length >= 1) { enum tmp0 = CArgs2[0]; mixin typeof(tmp0).MethodOverride!(CArgs2[0]); }
3903 			static if(CArgs2.length >= 2) { enum tmp1 = CArgs2[1]; mixin typeof(tmp1).MethodOverride!(CArgs2[1]); }
3904 			static if(CArgs2.length >= 3) { enum tmp2 = CArgs2[2]; mixin typeof(tmp2).MethodOverride!(CArgs2[2]); }
3905 			static if(CArgs2.length > 3) static assert(0, "only a few overrides like this supported on your compiler version at this time");
3906 		} else mixin(q{
3907 			static foreach(Arg; CArgs2) {
3908 				mixin Arg.MethodOverride!(Arg);
3909 			}
3910 		});
3911 
3912 		static ContainerMeta opCall(string name, ContainerMeta[] children...) {
3913 			return ContainerMeta(
3914 				name,
3915 				children.dup,
3916 				function (Widget parent) { return new typeof(this)(parent); }
3917 			);
3918 		}
3919 
3920 		static ContainerMeta opCall(ContainerMeta[] children...) {
3921 			return opCall(null, children);
3922 		}
3923 	}
3924 }
3925 
3926 /++
3927 	The data controller widget is created by reflecting over the given
3928 	data type. You can use [ControlledBy] as a UDA on a struct or
3929 	just let it create things automatically.
3930 
3931 	Unlike [dialog], this uses real-time updating of the data and
3932 	you add it to another window yourself.
3933 
3934 	---
3935 		struct Test {
3936 			int x;
3937 			int y;
3938 		}
3939 
3940 		auto window = new Window();
3941 		auto dcw = new DataControllerWidget!Test(new Test, window);
3942 	---
3943 
3944 	The way it works is any public members are given a widget based
3945 	on their data type, and public methods trigger an action button
3946 	if no relevant parameters or a dialog action if it does have
3947 	parameters, similar to the [menu] facility.
3948 
3949 	If you change data programmatically, without going through the
3950 	DataControllerWidget methods, you will have to tell it something
3951 	has changed and it needs to redraw. This is done with the `invalidate`
3952 	method.
3953 
3954 	History:
3955 		Added Oct 28, 2020
3956 +/
3957 /// Group: generating_from_code
3958 class DataControllerWidget(T) : WidgetContainer {
3959 	static if(is(T == class) || is(T == interface) || is(T : const E[], E))
3960 		private alias Tref = T;
3961 	else
3962 		private alias Tref = T*;
3963 
3964 	Tref datum;
3965 
3966 	/++
3967 		See_also: [addDataControllerWidget]
3968 	+/
3969 	this(Tref datum, Widget parent) {
3970 		this.datum = datum;
3971 
3972 		Widget cp = this;
3973 
3974 		super(parent);
3975 
3976 		foreach(attr; __traits(getAttributes, T))
3977 			static if(is(typeof(attr) == ContainerMeta)) {
3978 				cp = attr.instantiate(this);
3979 			}
3980 
3981 		auto def = this.getByName("default");
3982 		if(def !is null)
3983 			cp = def;
3984 
3985 		Widget helper(string name) {
3986 			auto maybe = this.getByName(name);
3987 			if(maybe is null)
3988 				return cp;
3989 			return maybe;
3990 
3991 		}
3992 
3993 		foreach(member; __traits(allMembers, T))
3994 		static if(member != "this") // wtf https://issues.dlang.org/show_bug.cgi?id=22011
3995 		static if(is(typeof(__traits(getMember, this.datum, member))))
3996 		static if(__traits(getProtection, __traits(getMember, this.datum, member)) == "public") {
3997 			void delegate() update;
3998 
3999 			auto w = widgetFor!(__traits(getMember, T, member))(&__traits(getMember, this.datum, member), helper(member), update);
4000 
4001 			if(update)
4002 				updaters ~= update;
4003 
4004 			static if(is(typeof(__traits(getMember, this.datum, member)) == function)) {
4005 				w.addEventListener("triggered", delegate() {
4006 					makeAutomaticHandler!(__traits(getMember, this.datum, member))(&__traits(getMember, this.datum, member))();
4007 					notifyDataUpdated();
4008 				});
4009 			} else static if(is(typeof(w.isChecked) == bool)) {
4010 				w.addEventListener(EventType.change, (Event ev) {
4011 					__traits(getMember, this.datum, member) = w.isChecked;
4012 				});
4013 			} else static if(is(typeof(w.value) == string) || is(typeof(w.content) == string)) {
4014 				w.addEventListener("change", (Event e) { genericSetValue(&__traits(getMember, this.datum, member), e.stringValue); } );
4015 			} else static if(is(typeof(w.value) == int)) {
4016 				w.addEventListener("change", (Event e) { genericSetValue(&__traits(getMember, this.datum, member), e.intValue); } );
4017 			} else static if(is(typeof(w) == DropDownSelection)) {
4018 				// special case for this to kinda support enums and such. coudl be better though
4019 				w.addEventListener("change", (Event e) { genericSetValue(&__traits(getMember, this.datum, member), e.intValue); } );
4020 			} else {
4021 				//static assert(0, "unsupported type " ~ typeof(__traits(getMember, this.datum, member)).stringof ~ " " ~ typeof(w).stringof);
4022 			}
4023 		}
4024 	}
4025 
4026 	/++
4027 		If you modify the data in the structure directly, you need to call this to update the UI and propagate any change messages.
4028 
4029 		History:
4030 			Added May 28, 2021
4031 	+/
4032 	void notifyDataUpdated() {
4033 		foreach(updater; updaters)
4034 			updater();
4035 
4036 		this.emit!(ChangeEvent!void)(delegate{});
4037 	}
4038 
4039 	private Widget[string] memberWidgets;
4040 	private void delegate()[] updaters;
4041 
4042 	mixin Emits!(ChangeEvent!void);
4043 }
4044 
4045 private int saturatedSum(int[] values...) {
4046 	int sum;
4047 	foreach(value; values) {
4048 		if(value == int.max)
4049 			return int.max;
4050 		sum += value;
4051 	}
4052 	return sum;
4053 }
4054 
4055 void genericSetValue(T, W)(T* where, W what) {
4056 	import std.conv;
4057 	*where = to!T(what);
4058 	//*where = cast(T) stringToLong(what);
4059 }
4060 
4061 /++
4062 	Creates a widget for the value `tt`, which is pointed to at runtime by `valptr`, with the given parent.
4063 
4064 	The `update` delegate can be called if you change `*valptr` to reflect those changes in the widget.
4065 
4066 	Note that this creates the widget but does not attach any event handlers to it.
4067 +/
4068 private static auto widgetFor(alias tt, P)(P valptr, Widget parent, out void delegate() update) {
4069 
4070 	string displayName = __traits(identifier, tt).beautify;
4071 
4072 	static if(controlledByCount!tt == 1) {
4073 		foreach(i, attr; __traits(getAttributes, tt)) {
4074 			static if(is(typeof(attr) == ControlledBy_!(T, Args), T, Args...)) {
4075 				auto w = attr.construct(parent);
4076 				static if(__traits(compiles, w.setPosition(*valptr)))
4077 					update = () { w.setPosition(*valptr); };
4078 				else static if(__traits(compiles, w.setValue(*valptr)))
4079 					update = () { w.setValue(*valptr); };
4080 
4081 				if(update)
4082 					update();
4083 				return w;
4084 			}
4085 		}
4086 	} else static if(controlledByCount!tt == 0) {
4087 		static if(is(typeof(tt) == enum)) {
4088 			// FIXME: update
4089 			auto dds = new DropDownSelection(parent);
4090 			foreach(idx, option; __traits(allMembers, typeof(tt))) {
4091 				dds.addOption(option);
4092 				if(__traits(getMember, typeof(tt), option) == *valptr)
4093 					dds.setSelection(cast(int) idx);
4094 			}
4095 			return dds;
4096 		} else static if(is(typeof(tt) == bool)) {
4097 			auto box = new Checkbox(displayName, parent);
4098 			update = () { box.isChecked = *valptr; };
4099 			update();
4100 			return box;
4101 		} else static if(is(typeof(tt) : const long)) {
4102 			auto le = new LabeledLineEdit(displayName, parent);
4103 			update = () { le.content = toInternal!string(*valptr); };
4104 			update();
4105 			return le;
4106 		} else static if(is(typeof(tt) : const double)) {
4107 			auto le = new LabeledLineEdit(displayName, parent);
4108 			import std.conv;
4109 			update = () { le.content = to!string(*valptr); };
4110 			update();
4111 			return le;
4112 		} else static if(is(typeof(tt) : const string)) {
4113 			auto le = new LabeledLineEdit(displayName, parent);
4114 			update = () { le.content = *valptr; };
4115 			update();
4116 			return le;
4117 		} else static if(is(typeof(tt) == function)) {
4118 			auto w = new Button(displayName, parent);
4119 			return w;
4120 		} else static if(is(typeof(tt) == class) || is(typeof(tt) == interface)) {
4121 			return parent.addDataControllerWidget(tt);
4122 		} else static assert(0, typeof(tt).stringof);
4123 	} else static assert(0, "multiple controllers not yet supported");
4124 }
4125 
4126 private template controlledByCount(alias tt) {
4127 	static int helper() {
4128 		int count;
4129 		foreach(i, attr; __traits(getAttributes, tt))
4130 			static if(is(typeof(attr) == ControlledBy_!(T, Args), T, Args...))
4131 				count++;
4132 		return count;
4133 	}
4134 
4135 	enum controlledByCount = helper;
4136 }
4137 
4138 /++
4139 	Intended for UFCS action like `window.addDataControllerWidget(new MyObject());`
4140 
4141 	If you provide a `redrawOnChange` widget, it will automatically register a change event handler that calls that widget's redraw method.
4142 
4143 	History:
4144 		The `redrawOnChange` parameter was added on May 28, 2021.
4145 +/
4146 DataControllerWidget!T addDataControllerWidget(T)(Widget parent, T t, Widget redrawOnChange = null) if(is(T == class) || is(T == interface)) {
4147 	auto dcw = new DataControllerWidget!T(t, parent);
4148 	initializeDataControllerWidget(dcw, redrawOnChange);
4149 	return dcw;
4150 }
4151 
4152 /// ditto
4153 DataControllerWidget!T addDataControllerWidget(T)(Widget parent, T* t, Widget redrawOnChange = null) if(is(T == struct)) {
4154 	auto dcw = new DataControllerWidget!T(t, parent);
4155 	initializeDataControllerWidget(dcw, redrawOnChange);
4156 	return dcw;
4157 }
4158 
4159 private void initializeDataControllerWidget(Widget w, Widget redrawOnChange) {
4160 	if(redrawOnChange !is null)
4161 		w.addEventListener("change", delegate() { redrawOnChange.redraw(); });
4162 }
4163 
4164 /++
4165 	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.
4166 
4167 	History:
4168 		Finalized on June 3, 2021 for the dub v10.0 release
4169 +/
4170 struct StyleInformation {
4171 	private Widget w;
4172 	private BaseVisualTheme visualTheme;
4173 
4174 	private this(Widget w) {
4175 		this.w = w;
4176 		this.visualTheme = WidgetPainter.visualTheme;
4177 	}
4178 
4179 	/++
4180 		Forwards to [Widget.Style]
4181 
4182 		Bugs:
4183 			It is supposed to fall back to the [VisualTheme] if
4184 			the style doesn't override the default, but that is
4185 			not generally implemented. Many of them may end up
4186 			being explicit overloads instead of the generic
4187 			opDispatch fallback, like [font] is now.
4188 	+/
4189 	public @property opDispatch(string name)() {
4190 		typeof(__traits(getMember, Widget.Style.init, name)()) prop;
4191 		w.useStyleProperties((scope Widget.Style props) {
4192 		//visualTheme.useStyleProperties(w, (props) {
4193 			prop = __traits(getMember, props, name);
4194 		});
4195 		return prop;
4196 	}
4197 
4198 	/++
4199 		Returns the cached font object associated with the widget,
4200 		if overridden by the [Widget.Style|Style], or the [VisualTheme] if not.
4201 
4202 		History:
4203 			Prior to March 21, 2022 (dub v10.7), `font` went through
4204 			[opDispatch], which did not use the cache. You can now call it
4205 			repeatedly without guilt.
4206 	+/
4207 	public @property OperatingSystemFont font() {
4208 		OperatingSystemFont prop;
4209 		w.useStyleProperties((scope Widget.Style props) {
4210 			prop = props.fontCached;
4211 		});
4212 		if(prop is null) {
4213 			prop = visualTheme.defaultFontCached(w.currentDpi);
4214 		}
4215 		return prop;
4216 	}
4217 
4218 	@property {
4219 		// Layout helpers. Currently just forwarding since I haven't made up my mind on a better way.
4220 		/** */ int paddingLeft() { return w.paddingLeft(); }
4221 		/** */ int paddingRight() { return w.paddingRight(); }
4222 		/** */ int paddingTop() { return w.paddingTop(); }
4223 		/** */ int paddingBottom() { return w.paddingBottom(); }
4224 
4225 		/** */ int marginLeft() { return w.marginLeft(); }
4226 		/** */ int marginRight() { return w.marginRight(); }
4227 		/** */ int marginTop() { return w.marginTop(); }
4228 		/** */ int marginBottom() { return w.marginBottom(); }
4229 
4230 		/** */ int maxHeight() { return w.maxHeight(); }
4231 		/** */ int minHeight() { return w.minHeight(); }
4232 
4233 		/** */ int maxWidth() { return w.maxWidth(); }
4234 		/** */ int minWidth() { return w.minWidth(); }
4235 
4236 		/** */ int flexBasisWidth() { return w.flexBasisWidth(); }
4237 		/** */ int flexBasisHeight() { return w.flexBasisHeight(); }
4238 
4239 		/** */ int heightStretchiness() { return w.heightStretchiness(); }
4240 		/** */ int widthStretchiness() { return w.widthStretchiness(); }
4241 
4242 		/** */ int heightShrinkiness() { return w.heightShrinkiness(); }
4243 		/** */ int widthShrinkiness() { return w.widthShrinkiness(); }
4244 
4245 		// Global helpers some of these are unstable.
4246 		static:
4247 		/** */ Color windowBackgroundColor() { return WidgetPainter.visualTheme.windowBackgroundColor(); }
4248 		/** */ Color widgetBackgroundColor() { return WidgetPainter.visualTheme.widgetBackgroundColor(); }
4249 		/** */ Color lightAccentColor() { return WidgetPainter.visualTheme.lightAccentColor(); }
4250 		/** */ Color darkAccentColor() { return WidgetPainter.visualTheme.darkAccentColor(); }
4251 		/** */ Color selectionForegroundColor() { return WidgetPainter.visualTheme.selectionForegroundColor(); }
4252 		/** */ Color selectionBackgroundColor() { return WidgetPainter.visualTheme.selectionBackgroundColor(); }
4253 
4254 		/** */ Color activeTabColor() { return lightAccentColor; }
4255 		/** */ Color buttonColor() { return windowBackgroundColor; }
4256 		/** */ Color depressedButtonColor() { return darkAccentColor; }
4257 		/** */ Color hoveringColor() { return lightAccentColor; }
4258 		deprecated("Use selectionForegroundColor and selectionBackgroundColor instead") Color activeListXorColor() {
4259 			auto c = WidgetPainter.visualTheme.selectionColor();
4260 			return Color(c.r ^ 255, c.g ^ 255, c.b ^ 255, c.a);
4261 		}
4262 		/** */ Color progressBarColor() { return WidgetPainter.visualTheme.selectionBackgroundColor(); }
4263 		/** */ Color activeMenuItemColor() { return WidgetPainter.visualTheme.selectionBackgroundColor(); }
4264 	}
4265 
4266 
4267 
4268 	/+
4269 
4270 	private static auto extractStyleProperty(string name)(Widget w) {
4271 		typeof(__traits(getMember, Widget.Style.init, name)()) prop;
4272 		w.useStyleProperties((props) {
4273 			prop = __traits(getMember, props, name);
4274 		});
4275 		return prop;
4276 	}
4277 
4278 	// FIXME: clear this upon a X server disconnect
4279 	private static OperatingSystemFont[string] fontCache;
4280 
4281 	T getProperty(T)(string name, lazy T default_) {
4282 		if(visualTheme !is null) {
4283 			auto str = visualTheme.getPropertyString(w, name);
4284 			if(str is null)
4285 				return default_;
4286 			static if(is(T == Color))
4287 				return Color.fromString(str);
4288 			else static if(is(T == Measurement))
4289 				return Measurement(cast(int) toInternal!int(str));
4290 			else static if(is(T == WidgetBackground))
4291 				return WidgetBackground.fromString(str);
4292 			else static if(is(T == OperatingSystemFont)) {
4293 				if(auto f = str in fontCache)
4294 					return *f;
4295 				else
4296 					return fontCache[str] = new OperatingSystemFont(str);
4297 			} else static if(is(T == FrameStyle)) {
4298 				switch(str) {
4299 					default:
4300 						return FrameStyle.none;
4301 					foreach(style; __traits(allMembers, FrameStyle))
4302 					case style:
4303 						return __traits(getMember, FrameStyle, style);
4304 				}
4305 			} else static assert(0);
4306 		} else
4307 			return default_;
4308 	}
4309 
4310 	static struct Measurement {
4311 		int value;
4312 		alias value this;
4313 	}
4314 
4315 	@property:
4316 
4317 	int paddingLeft() { return getProperty("padding-left", Measurement(w.paddingLeft())); }
4318 	int paddingRight() { return getProperty("padding-right", Measurement(w.paddingRight())); }
4319 	int paddingTop() { return getProperty("padding-top", Measurement(w.paddingTop())); }
4320 	int paddingBottom() { return getProperty("padding-bottom", Measurement(w.paddingBottom())); }
4321 
4322 	int marginLeft() { return getProperty("margin-left", Measurement(w.marginLeft())); }
4323 	int marginRight() { return getProperty("margin-right", Measurement(w.marginRight())); }
4324 	int marginTop() { return getProperty("margin-top", Measurement(w.marginTop())); }
4325 	int marginBottom() { return getProperty("margin-bottom", Measurement(w.marginBottom())); }
4326 
4327 	int maxHeight() { return getProperty("max-height", Measurement(w.maxHeight())); }
4328 	int minHeight() { return getProperty("min-height", Measurement(w.minHeight())); }
4329 
4330 	int maxWidth() { return getProperty("max-width", Measurement(w.maxWidth())); }
4331 	int minWidth() { return getProperty("min-width", Measurement(w.minWidth())); }
4332 
4333 
4334 	WidgetBackground background() { return getProperty("background", extractStyleProperty!"background"(w)); }
4335 	Color foregroundColor() { return getProperty("foreground-color", extractStyleProperty!"foregroundColor"(w)); }
4336 
4337 	OperatingSystemFont font() { return getProperty("font", extractStyleProperty!"fontCached"(w)); }
4338 
4339 	FrameStyle borderStyle() { return getProperty("border-style", extractStyleProperty!"borderStyle"(w)); }
4340 	Color borderColor() { return getProperty("border-color", extractStyleProperty!"borderColor"(w)); }
4341 
4342 	FrameStyle outlineStyle() { return getProperty("outline-style", extractStyleProperty!"outlineStyle"(w)); }
4343 	Color outlineColor() { return getProperty("outline-color", extractStyleProperty!"outlineColor"(w)); }
4344 
4345 
4346 	Color windowBackgroundColor() { return WidgetPainter.visualTheme.windowBackgroundColor(); }
4347 	Color widgetBackgroundColor() { return WidgetPainter.visualTheme.widgetBackgroundColor(); }
4348 	Color lightAccentColor() { return WidgetPainter.visualTheme.lightAccentColor(); }
4349 	Color darkAccentColor() { return WidgetPainter.visualTheme.darkAccentColor(); }
4350 
4351 	Color activeTabColor() { return lightAccentColor; }
4352 	Color buttonColor() { return windowBackgroundColor; }
4353 	Color depressedButtonColor() { return darkAccentColor; }
4354 	Color hoveringColor() { return Color(228, 228, 228); }
4355 	Color activeListXorColor() {
4356 		auto c = WidgetPainter.visualTheme.selectionColor();
4357 		return Color(c.r ^ 255, c.g ^ 255, c.b ^ 255, c.a);
4358 	}
4359 	Color progressBarColor() { return WidgetPainter.visualTheme.selectionColor(); }
4360 	Color activeMenuItemColor() { return WidgetPainter.visualTheme.selectionColor(); }
4361 	+/
4362 }
4363 
4364 
4365 
4366 // pragma(msg, __traits(classInstanceSize, Widget));
4367 
4368 /*private*/ template EventString(E) {
4369 	static if(is(typeof(E.EventString)))
4370 		enum EventString = E.EventString;
4371 	else
4372 		enum EventString = E.mangleof; // FIXME fqn? or something more user friendly
4373 }
4374 
4375 /*private*/ template EventStringIdentifier(E) {
4376 	string helper() {
4377 		auto es = EventString!E;
4378 		char[] id = new char[](es.length * 2);
4379 		size_t idx;
4380 		foreach(char ch; es) {
4381 			id[idx++] = cast(char)('a' + (ch >> 4));
4382 			id[idx++] = cast(char)('a' + (ch & 0x0f));
4383 		}
4384 		return cast(string) id;
4385 	}
4386 
4387 	enum EventStringIdentifier = helper();
4388 }
4389 
4390 
4391 template classStaticallyEmits(This, EventType) {
4392 	static if(is(This Base == super))
4393 		static if(is(Base : Widget))
4394 			enum baseEmits = classStaticallyEmits!(Base, EventType);
4395 		else
4396 			enum baseEmits = false;
4397 	else
4398 		enum baseEmits = false;
4399 
4400 	enum thisEmits = is(typeof(__traits(getMember, This, "emits_" ~ EventStringIdentifier!EventType)) == EventType[0]);
4401 
4402 	enum classStaticallyEmits = thisEmits || baseEmits;
4403 }
4404 
4405 /++
4406 	A helper to make widgets out of other native windows.
4407 
4408 	History:
4409 		Factored out of OpenGlWidget on November 5, 2021
4410 +/
4411 class NestedChildWindowWidget : Widget {
4412 	SimpleWindow win;
4413 
4414 	/++
4415 		Used on X to send focus to the appropriate child window when requested by the window manager.
4416 
4417 		Normally returns its own nested window. Can also return another child or null to revert to the parent
4418 		if you override it in a child class.
4419 
4420 		History:
4421 			Added April 2, 2022 (dub v10.8)
4422 	+/
4423 	SimpleWindow focusableWindow() {
4424 		return win;
4425 	}
4426 
4427 	///
4428 	// win = new SimpleWindow(640, 480, null, OpenGlOptions.yes, Resizability.automaticallyScaleIfPossible, WindowTypes.nestedChild, WindowFlags.normal, getParentWindow(parent));
4429 	this(SimpleWindow win, Widget parent) {
4430 		this.parentWindow = parent.parentWindow;
4431 		this.win = win;
4432 
4433 		super(parent);
4434 		windowsetup(win);
4435 	}
4436 
4437 	static protected SimpleWindow getParentWindow(Widget parent) {
4438 		assert(parent !is null);
4439 		SimpleWindow pwin = parent.parentWindow.win;
4440 
4441 		version(win32_widgets) {
4442 			HWND phwnd;
4443 			auto wtf = parent;
4444 			while(wtf) {
4445 				if(wtf.hwnd) {
4446 					phwnd = wtf.hwnd;
4447 					break;
4448 				}
4449 				wtf = wtf.parent;
4450 			}
4451 			// kinda a hack here just because the ctor below just needs a SimpleWindow wrapper....
4452 			if(phwnd)
4453 				pwin = new SimpleWindow(phwnd);
4454 		}
4455 
4456 		return pwin;
4457 	}
4458 
4459 	/++
4460 		Called upon the nested window being destroyed.
4461 		Remember the window has already been destroyed at
4462 		this point, so don't use the native handle for anything.
4463 
4464 		History:
4465 			Added April 3, 2022 (dub v10.8)
4466 	+/
4467 	protected void dispose() {
4468 
4469 	}
4470 
4471 	protected void windowsetup(SimpleWindow w) {
4472 		/*
4473 		win.onFocusChange = (bool getting) {
4474 			if(getting)
4475 				this.focus();
4476 		};
4477 		*/
4478 
4479 		/+
4480 		win.onFocusChange = (bool getting) {
4481 			if(getting) {
4482 				this.parentWindow.focusedWidget = this;
4483 				this.emit!FocusEvent();
4484 				this.emit!FocusInEvent();
4485 			} else {
4486 				this.emit!BlurEvent();
4487 				this.emit!FocusOutEvent();
4488 			}
4489 		};
4490 		+/
4491 
4492 		win.onDestroyed = () {
4493 			this.dispose();
4494 		};
4495 
4496 		version(win32_widgets) {
4497 			Widget.nativeMapping[win.hwnd] = this;
4498 			this.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(win.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc);
4499 		} else {
4500 			win.setEventHandlers(
4501 				(MouseEvent e) {
4502 					Widget p = this;
4503 					while(p ! is parentWindow) {
4504 						e.x += p.x;
4505 						e.y += p.y;
4506 						p = p.parent;
4507 					}
4508 					parentWindow.dispatchMouseEvent(e);
4509 				},
4510 				(KeyEvent e) {
4511 					//writefln("%s %x   %s", cast(void*) win, cast(uint) e.key, e.key);
4512 					parentWindow.dispatchKeyEvent(e);
4513 				},
4514 				(dchar e) {
4515 					parentWindow.dispatchCharEvent(e);
4516 				},
4517 			);
4518 		}
4519 
4520 	}
4521 
4522 	override void showing(bool s, bool recalc) {
4523 		auto cur = hidden;
4524 		win.hidden = !s;
4525 		if(cur != s && s)
4526 			redraw();
4527 	}
4528 
4529 	/// OpenGL widgets cannot have child widgets. Do not call this.
4530 	/* @disable */ final override void addChild(Widget, int) {
4531 		throw new Error("cannot add children to OpenGL widgets");
4532 	}
4533 
4534 	/// When an opengl widget is laid out, it will adjust the glViewport for you automatically.
4535 	/// Keep in mind that events like mouse coordinates are still relative to your size.
4536 	override void registerMovement() {
4537 		// writefln("%d %d %d %d", x,y,width,height);
4538 		version(win32_widgets)
4539 			auto pos = getChildPositionRelativeToParentHwnd(this);
4540 		else
4541 			auto pos = getChildPositionRelativeToParentOrigin(this);
4542 		win.moveResize(pos[0], pos[1], width, height);
4543 
4544 		registerMovementAdditionalWork();
4545 		sendResizeEvent();
4546 	}
4547 
4548 	abstract void registerMovementAdditionalWork();
4549 }
4550 
4551 /++
4552 	Nests an opengl capable window inside this window as a widget.
4553 
4554 	You may also just want to create an additional [SimpleWindow] with
4555 	[OpenGlOptions.yes] yourself.
4556 
4557 	An OpenGL widget cannot have child widgets. It will throw if you try.
4558 +/
4559 static if(OpenGlEnabled)
4560 class OpenGlWidget : NestedChildWindowWidget {
4561 
4562 	override void registerMovementAdditionalWork() {
4563 		win.setAsCurrentOpenGlContext();
4564 	}
4565 
4566 	///
4567 	this(Widget parent) {
4568 		auto win = new SimpleWindow(640, 480, null, OpenGlOptions.yes, Resizability.automaticallyScaleIfPossible, WindowTypes.nestedChild, WindowFlags.normal, getParentWindow(parent));
4569 		super(win, parent);
4570 	}
4571 
4572 	override void paint(WidgetPainter painter) {
4573 		win.setAsCurrentOpenGlContext();
4574 		glViewport(0, 0, this.width, this.height);
4575 		win.redrawOpenGlSceneNow();
4576 	}
4577 
4578 	void redrawOpenGlScene(void delegate() dg) {
4579 		win.redrawOpenGlScene = dg;
4580 	}
4581 }
4582 
4583 /++
4584 	This demo shows how to draw text in an opengl scene.
4585 +/
4586 unittest {
4587 	import arsd.minigui;
4588 	import arsd.ttf;
4589 
4590 	void main() {
4591 		auto window = new Window();
4592 
4593 		auto widget = new OpenGlWidget(window);
4594 
4595 		// old means non-shader code so compatible with glBegin etc.
4596 		// tbh I haven't implemented new one in font yet...
4597 		// anyway, declaring here, will construct soon.
4598 		OpenGlLimitedFont!(OpenGlFontGLVersion.old) glfont;
4599 
4600 		// this is a little bit awkward, calling some methods through
4601 		// the underlying SimpleWindow `win` method, and you can't do this
4602 		// on a nanovega widget due to conflicts so I should probably fix
4603 		// the api to be a bit easier. But here it will work.
4604 		//
4605 		// Alternatively, you could load the font on the first draw, inside
4606 		// the redrawOpenGlScene, and keep a flag so you don't do it every
4607 		// time. That'd be a bit easier since the lib sets up the context
4608 		// by then guaranteed.
4609 		//
4610 		// But still, I wanna show this.
4611 		widget.win.visibleForTheFirstTime = delegate {
4612 			// must set the opengl context
4613 			widget.win.setAsCurrentOpenGlContext();
4614 
4615 			// if you were doing a OpenGL 3+ shader, this
4616 			// gets especially important to do in order. With
4617 			// old-style opengl, I think you can even do it
4618 			// in main(), but meh, let's show it more correctly.
4619 
4620 			// Anyway, now it is time to load the font from the
4621 			// OS (you can alternatively load one from a .ttf file
4622 			// you bundle with the application), then load the
4623 			// font into texture for drawing.
4624 
4625 			auto osfont = new OperatingSystemFont("DejaVu Sans", 18);
4626 
4627 			assert(!osfont.isNull()); // make sure it actually loaded
4628 
4629 			// using typeof to avoid repeating the long name lol
4630 			glfont = new typeof(glfont)(
4631 				// get the raw data from the font for loading in here
4632 				// since it doesn't use the OS function to draw the
4633 				// text, we gotta treat it more as a file than as
4634 				// a drawing api.
4635 				osfont.getTtfBytes(),
4636 				18, // need to respecify size since opengl world is different coordinate system
4637 
4638 				// these last two numbers are why it is called
4639 				// "Limited" font. It only loads the characters
4640 				// in the given range, since the texture atlas
4641 				// it references is all a big image generated ahead
4642 				// of time. You could maybe do the whole thing but
4643 				// idk how much memory that is.
4644 				//
4645 				// But here, 0-128 represents the ASCII range, so
4646 				// good enough for most English things, numeric labels,
4647 				// etc.
4648 				0,
4649 				128
4650 			);
4651 		};
4652 
4653 		widget.redrawOpenGlScene = () {
4654 			// now we can use the glfont's drawString function
4655 
4656 			// first some opengl setup. You can do this in one place
4657 			// on window first visible too in many cases, just showing
4658 			// here cuz it is easier for me.
4659 
4660 			// gonna need some alpha blending or it just looks awful
4661 			glEnable(GL_BLEND);
4662 			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
4663 			glClearColor(0,0,0,0);
4664 			glDepthFunc(GL_LEQUAL);
4665 
4666 			// Also need to enable 2d textures, since it draws the
4667 			// font characters as images baked in
4668 			glMatrixMode(GL_MODELVIEW);
4669 			glLoadIdentity();
4670 			glDisable(GL_DEPTH_TEST);
4671 			glEnable(GL_TEXTURE_2D);
4672 
4673 			// the orthographic matrix is best for 2d things like text
4674 			// so let's set that up. This matrix makes the coordinates
4675 			// in the opengl scene be one-to-one with the actual pixels
4676 			// on screen. (Not necessarily best, you may wish to scale
4677 			// things, but it does help keep fonts looking normal.)
4678 			glMatrixMode(GL_PROJECTION);
4679 			glLoadIdentity();
4680 			glOrtho(0, widget.width, widget.height, 0, 0, 1);
4681 
4682 			// you can do other glScale, glRotate, glTranslate, etc
4683 			// to the matrix here of course if you want.
4684 
4685 			// note the x,y coordinates here are for the text baseline
4686 			// NOT the upper-left corner. The baseline is like the line
4687 			// in the notebook you write on. Most the letters are actually
4688 			// above it, but some, like p and q, dip a bit below it.
4689 			//
4690 			// So if you're used to the upper left coordinate like the
4691 			// rest of simpledisplay/minigui usually do, do the
4692 			// y + glfont.ascent to bring it down a little. So this
4693 			// example puts the string in the upper left of the window.
4694 			glfont.drawString(0, 0 + glfont.ascent, "Hello!!", Color.green);
4695 
4696 			// re color btw: the function sets a solid color internally,
4697 			// but you actually COULD do your own thing for rainbow effects
4698 			// and the sort if you wanted too, by pulling its guts out.
4699 			// Just view its source for an idea of how it actually draws:
4700 			// http://arsd-official.dpldocs.info/source/arsd.ttf.d.html#L332
4701 
4702 			// it gets a bit complicated with the character positioning,
4703 			// but the opengl parts are fairly simple: bind a texture,
4704 			// set the color, draw a quad for each letter.
4705 
4706 
4707 			// the last optional argument there btw is a bounding box
4708 			// it will/ use to word wrap and return an object you can
4709 			// use to implement scrolling or pagination; it tells how
4710 			// much of the string didn't fit in the box. But for simple
4711 			// labels we can just ignore that.
4712 
4713 
4714 			// I'd suggest drawing text as the last step, after you
4715 			// do your other drawing. You might use the push/pop matrix
4716 			// stuff to keep your place. You, in theory, should be able
4717 			// to do text in a 3d space but I've never actually tried
4718 			// that....
4719 		};
4720 
4721 		window.loop();
4722 	}
4723 }
4724 
4725 version(custom_widgets)
4726 	private alias ListWidgetBase = ScrollableWidget;
4727 else
4728 	private alias ListWidgetBase = Widget;
4729 
4730 /++
4731 	A list widget contains a list of strings that the user can examine and select.
4732 
4733 
4734 	In the future, items in the list may be possible to be more than just strings.
4735 
4736 	See_Also:
4737 		[TableView]
4738 +/
4739 class ListWidget : ListWidgetBase {
4740 	/// 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.
4741 	mixin Emits!(ChangeEvent!void);
4742 
4743 	static struct Option {
4744 		string label;
4745 		bool selected;
4746 		void* tag;
4747 	}
4748 
4749 	/++
4750 		Sets the current selection to the `y`th item in the list. Will emit [ChangeEvent] when complete.
4751 	+/
4752 	void setSelection(int y) {
4753 		if(!multiSelect)
4754 			foreach(ref opt; options)
4755 				opt.selected = false;
4756 		if(y >= 0 && y < options.length)
4757 			options[y].selected = !options[y].selected;
4758 
4759 		this.emit!(ChangeEvent!void)(delegate {});
4760 
4761 		version(custom_widgets)
4762 			redraw();
4763 	}
4764 
4765 	/++
4766 		Gets the index of the selected item. In case of multi select, the index of the first selected item is returned.
4767 		Returns -1 if nothing is selected.
4768 	+/
4769 	int getSelection()
4770 	{
4771 		foreach(i, opt; options) {
4772 			if (opt.selected)
4773 				return cast(int) i;
4774 		}
4775 		return -1;
4776 	}
4777 
4778 	version(custom_widgets)
4779 	override void defaultEventHandler_click(ClickEvent event) {
4780 		this.focus();
4781 		if(event.button == MouseButton.left) {
4782 			auto y = (event.clientY - 4) / defaultLineHeight;
4783 			if(y >= 0 && y < options.length) {
4784 				setSelection(y);
4785 			}
4786 		}
4787 		super.defaultEventHandler_click(event);
4788 	}
4789 
4790 	this(Widget parent) {
4791 		tabStop = false;
4792 		super(parent);
4793 		version(win32_widgets)
4794 			createWin32Window(this, WC_LISTBOX, "",
4795 				0|WS_CHILD|WS_VISIBLE|LBS_NOTIFY, 0);
4796 	}
4797 
4798 	version(win32_widgets)
4799 	override void handleWmCommand(ushort code, ushort id) {
4800 		switch(code) {
4801 			case LBN_SELCHANGE:
4802 				auto sel = SendMessageW(hwnd, LB_GETCURSEL, 0, 0);
4803 				setSelection(cast(int) sel);
4804 			break;
4805 			default:
4806 		}
4807 	}
4808 
4809 
4810 	version(custom_widgets)
4811 	override void paintFrameAndBackground(WidgetPainter painter) {
4812 		draw3dFrame(this, painter, FrameStyle.sunk, painter.visualTheme.widgetBackgroundColor);
4813 	}
4814 
4815 	version(custom_widgets)
4816 	override void paint(WidgetPainter painter) {
4817 		auto cs = getComputedStyle();
4818 		auto pos = Point(4, 4);
4819 		foreach(idx, option; options) {
4820 			painter.fillColor = painter.visualTheme.widgetBackgroundColor;
4821 			painter.outlineColor = painter.visualTheme.widgetBackgroundColor;
4822 			painter.drawRectangle(pos, width - 8, defaultLineHeight);
4823 			if(option.selected) {
4824 				//painter.rasterOp = RasterOp.xor;
4825 				painter.outlineColor = cs.selectionForegroundColor;
4826 				painter.fillColor = cs.selectionBackgroundColor;
4827 				painter.drawRectangle(pos, width - 8, defaultLineHeight);
4828 				//painter.rasterOp = RasterOp.normal;
4829 			}
4830 			painter.outlineColor = option.selected ? cs.selectionForegroundColor : cs.foregroundColor;
4831 			painter.drawText(pos, option.label);
4832 			pos.y += defaultLineHeight;
4833 		}
4834 	}
4835 
4836 	static class Style : Widget.Style {
4837 		override WidgetBackground background() {
4838 			return WidgetBackground(WidgetPainter.visualTheme.widgetBackgroundColor);
4839 		}
4840 	}
4841 	mixin OverrideStyle!Style;
4842 	//mixin Padding!q{2};
4843 
4844 	void addOption(string text, void* tag = null) {
4845 		options ~= Option(text, false, tag);
4846 		version(win32_widgets) {
4847 			WCharzBuffer buffer = WCharzBuffer(text);
4848 			SendMessageW(hwnd, LB_ADDSTRING, 0, cast(LPARAM) buffer.ptr);
4849 		}
4850 		version(custom_widgets) {
4851 			setContentSize(width, cast(int) (options.length * defaultLineHeight));
4852 			redraw();
4853 		}
4854 	}
4855 
4856 	void clear() {
4857 		options = null;
4858 		version(win32_widgets) {
4859 			while(SendMessageW(hwnd, LB_DELETESTRING, 0, 0) > 0)
4860 				{}
4861 
4862 		} else version(custom_widgets) {
4863 			scrollTo(Point(0, 0));
4864 			redraw();
4865 		}
4866 	}
4867 
4868 	Option[] options;
4869 	version(win32_widgets)
4870 		enum multiSelect = false; /// not implemented yet
4871 	else
4872 		bool multiSelect;
4873 
4874 	override int heightStretchiness() { return 6; }
4875 }
4876 
4877 
4878 
4879 /// For [ScrollableWidget], determines when to show the scroll bar to the user.
4880 enum ScrollBarShowPolicy {
4881 	automatic, /// automatically show the scroll bar if it is necessary
4882 	never, /// never show the scroll bar (scrolling must be done programmatically)
4883 	always /// always show the scroll bar, even if it is disabled
4884 }
4885 
4886 /++
4887 	A widget that tries (with, at best, limited success) to offer scrolling that is transparent to the inner.
4888 
4889 	It isn't very good and will very likely be removed. Try [ScrollMessageWidget] or [ScrollableContainerWidget] instead for new code.
4890 +/
4891 // FIXME ScrollBarShowPolicy
4892 // FIXME: use the ScrollMessageWidget in here now that it exists
4893 class ScrollableWidget : Widget {
4894 	// FIXME: make line size configurable
4895 	// FIXME: add keyboard controls
4896 	version(win32_widgets) {
4897 		override int hookedWndProc(UINT msg, WPARAM wParam, LPARAM lParam) {
4898 			if(msg == WM_VSCROLL || msg == WM_HSCROLL) {
4899 				auto pos = HIWORD(wParam);
4900 				auto m = LOWORD(wParam);
4901 
4902 				// FIXME: I can reintroduce the
4903 				// scroll bars now by using this
4904 				// in the top-level window handler
4905 				// to forward comamnds
4906 				auto scrollbarHwnd = lParam;
4907 				switch(m) {
4908 					case SB_BOTTOM:
4909 						if(msg == WM_HSCROLL)
4910 							horizontalScrollTo(contentWidth_);
4911 						else
4912 							verticalScrollTo(contentHeight_);
4913 					break;
4914 					case SB_TOP:
4915 						if(msg == WM_HSCROLL)
4916 							horizontalScrollTo(0);
4917 						else
4918 							verticalScrollTo(0);
4919 					break;
4920 					case SB_ENDSCROLL:
4921 						// idk
4922 					break;
4923 					case SB_LINEDOWN:
4924 						if(msg == WM_HSCROLL)
4925 							horizontalScroll(scaleWithDpi(16));
4926 						else
4927 							verticalScroll(scaleWithDpi(16));
4928 					break;
4929 					case SB_LINEUP:
4930 						if(msg == WM_HSCROLL)
4931 							horizontalScroll(scaleWithDpi(-16));
4932 						else
4933 							verticalScroll(scaleWithDpi(-16));
4934 					break;
4935 					case SB_PAGEDOWN:
4936 						if(msg == WM_HSCROLL)
4937 							horizontalScroll(scaleWithDpi(100));
4938 						else
4939 							verticalScroll(scaleWithDpi(100));
4940 					break;
4941 					case SB_PAGEUP:
4942 						if(msg == WM_HSCROLL)
4943 							horizontalScroll(scaleWithDpi(-100));
4944 						else
4945 							verticalScroll(scaleWithDpi(-100));
4946 					break;
4947 					case SB_THUMBPOSITION:
4948 					case SB_THUMBTRACK:
4949 						if(msg == WM_HSCROLL)
4950 							horizontalScrollTo(pos);
4951 						else
4952 							verticalScrollTo(pos);
4953 
4954 						if(m == SB_THUMBTRACK) {
4955 							// the event loop doesn't seem to carry on with a requested redraw..
4956 							// so we request it to get our dirty bit set...
4957 							redraw();
4958 
4959 							// then we need to immediately actually redraw it too for instant feedback to user
4960 
4961 							SimpleWindow.processAllCustomEvents();
4962 							//if(parentWindow)
4963 								//parentWindow.actualRedraw();
4964 						}
4965 					break;
4966 					default:
4967 				}
4968 			}
4969 			return super.hookedWndProc(msg, wParam, lParam);
4970 		}
4971 	}
4972 	///
4973 	this(Widget parent) {
4974 		this.parentWindow = parent.parentWindow;
4975 
4976 		version(win32_widgets) {
4977 			createWin32Window(this, Win32Class!"arsd_minigui_ScrollableWidget"w, "",
4978 				0|WS_CHILD|WS_VISIBLE|WS_HSCROLL|WS_VSCROLL, 0);
4979 			super(parent);
4980 		} else version(custom_widgets) {
4981 			outerContainer = new InternalScrollableContainerWidget(this, parent);
4982 			super(outerContainer);
4983 		} else static assert(0);
4984 	}
4985 
4986 	version(custom_widgets)
4987 		InternalScrollableContainerWidget outerContainer;
4988 
4989 	override void defaultEventHandler_click(ClickEvent event) {
4990 		if(event.button == MouseButton.wheelUp)
4991 			verticalScroll(scaleWithDpi(-16));
4992 		if(event.button == MouseButton.wheelDown)
4993 			verticalScroll(scaleWithDpi(16));
4994 		super.defaultEventHandler_click(event);
4995 	}
4996 
4997 	override void defaultEventHandler_keydown(KeyDownEvent event) {
4998 		switch(event.key) {
4999 			case Key.Left:
5000 				horizontalScroll(scaleWithDpi(-16));
5001 			break;
5002 			case Key.Right:
5003 				horizontalScroll(scaleWithDpi(16));
5004 			break;
5005 			case Key.Up:
5006 				verticalScroll(scaleWithDpi(-16));
5007 			break;
5008 			case Key.Down:
5009 				verticalScroll(scaleWithDpi(16));
5010 			break;
5011 			case Key.Home:
5012 				verticalScrollTo(0);
5013 			break;
5014 			case Key.End:
5015 				verticalScrollTo(contentHeight);
5016 			break;
5017 			case Key.PageUp:
5018 				verticalScroll(scaleWithDpi(-160));
5019 			break;
5020 			case Key.PageDown:
5021 				verticalScroll(scaleWithDpi(160));
5022 			break;
5023 			default:
5024 		}
5025 		super.defaultEventHandler_keydown(event);
5026 	}
5027 
5028 
5029 	version(win32_widgets)
5030 	override void recomputeChildLayout() {
5031 		super.recomputeChildLayout();
5032 		SCROLLINFO info;
5033 		info.cbSize = info.sizeof;
5034 		info.nPage = viewportHeight;
5035 		info.fMask = SIF_PAGE | SIF_RANGE;
5036 		info.nMin = 0;
5037 		info.nMax = contentHeight_;
5038 		SetScrollInfo(hwnd, SB_VERT, &info, true);
5039 
5040 		info.cbSize = info.sizeof;
5041 		info.nPage = viewportWidth;
5042 		info.fMask = SIF_PAGE | SIF_RANGE;
5043 		info.nMin = 0;
5044 		info.nMax = contentWidth_;
5045 		SetScrollInfo(hwnd, SB_HORZ, &info, true);
5046 	}
5047 
5048 	/*
5049 		Scrolling
5050 		------------
5051 
5052 		You are assigned a width and a height by the layout engine, which
5053 		is your viewport box. However, you may draw more than that by setting
5054 		a contentWidth and contentHeight.
5055 
5056 		If these can be contained by the viewport, no scrollbar is displayed.
5057 		If they cannot fit though, it will automatically show scroll as necessary.
5058 
5059 		If contentWidth == 0, no horizontal scrolling is performed. If contentHeight
5060 		is zero, no vertical scrolling is performed.
5061 
5062 		If scrolling is necessary, the lib will automatically work with the bars.
5063 		When you redraw, the origin and clipping info in the painter is set so if
5064 		you just draw everything, it will work, but you can be more efficient by checking
5065 		the viewportWidth, viewportHeight, and scrollOrigin members.
5066 	*/
5067 
5068 	///
5069 	final @property int viewportWidth() {
5070 		return width - (showingVerticalScroll ? scaleWithDpi(16) : 0);
5071 	}
5072 	///
5073 	final @property int viewportHeight() {
5074 		return height - (showingHorizontalScroll ? scaleWithDpi(16) : 0);
5075 	}
5076 
5077 	// FIXME property
5078 	Point scrollOrigin_;
5079 
5080 	///
5081 	final const(Point) scrollOrigin() {
5082 		return scrollOrigin_;
5083 	}
5084 
5085 	// the user sets these two
5086 	private int contentWidth_ = 0;
5087 	private int contentHeight_ = 0;
5088 
5089 	///
5090 	int contentWidth() { return contentWidth_; }
5091 	///
5092 	int contentHeight() { return contentHeight_; }
5093 
5094 	///
5095 	void setContentSize(int width, int height) {
5096 		contentWidth_ = width;
5097 		contentHeight_ = height;
5098 
5099 		version(custom_widgets) {
5100 			if(showingVerticalScroll || showingHorizontalScroll) {
5101 				outerContainer.recomputeChildLayout();
5102 			}
5103 
5104 			if(showingVerticalScroll())
5105 				outerContainer.verticalScrollBar.redraw();
5106 			if(showingHorizontalScroll())
5107 				outerContainer.horizontalScrollBar.redraw();
5108 		} else version(win32_widgets) {
5109 			recomputeChildLayout();
5110 		} else static assert(0);
5111 	}
5112 
5113 	///
5114 	void verticalScroll(int delta) {
5115 		verticalScrollTo(scrollOrigin.y + delta);
5116 	}
5117 	///
5118 	void verticalScrollTo(int pos) {
5119 		scrollOrigin_.y = pos;
5120 		if(pos == int.max || (scrollOrigin_.y + viewportHeight > contentHeight))
5121 			scrollOrigin_.y = contentHeight - viewportHeight;
5122 
5123 		if(scrollOrigin_.y < 0)
5124 			scrollOrigin_.y = 0;
5125 
5126 		version(win32_widgets) {
5127 			SCROLLINFO info;
5128 			info.cbSize = info.sizeof;
5129 			info.fMask = SIF_POS;
5130 			info.nPos = scrollOrigin_.y;
5131 			SetScrollInfo(hwnd, SB_VERT, &info, true);
5132 		} else version(custom_widgets) {
5133 			outerContainer.verticalScrollBar.setPosition(scrollOrigin_.y);
5134 		} else static assert(0);
5135 
5136 		redraw();
5137 	}
5138 
5139 	///
5140 	void horizontalScroll(int delta) {
5141 		horizontalScrollTo(scrollOrigin.x + delta);
5142 	}
5143 	///
5144 	void horizontalScrollTo(int pos) {
5145 		scrollOrigin_.x = pos;
5146 		if(pos == int.max || (scrollOrigin_.x + viewportWidth > contentWidth))
5147 			scrollOrigin_.x = contentWidth - viewportWidth;
5148 
5149 		if(scrollOrigin_.x < 0)
5150 			scrollOrigin_.x = 0;
5151 
5152 		version(win32_widgets) {
5153 			SCROLLINFO info;
5154 			info.cbSize = info.sizeof;
5155 			info.fMask = SIF_POS;
5156 			info.nPos = scrollOrigin_.x;
5157 			SetScrollInfo(hwnd, SB_HORZ, &info, true);
5158 		} else version(custom_widgets) {
5159 			outerContainer.horizontalScrollBar.setPosition(scrollOrigin_.x);
5160 		} else static assert(0);
5161 
5162 		redraw();
5163 	}
5164 	///
5165 	void scrollTo(Point p) {
5166 		verticalScrollTo(p.y);
5167 		horizontalScrollTo(p.x);
5168 	}
5169 
5170 	///
5171 	void ensureVisibleInScroll(Point p) {
5172 		auto rect = viewportRectangle();
5173 		if(rect.contains(p))
5174 			return;
5175 		if(p.x < rect.left)
5176 			horizontalScroll(p.x - rect.left);
5177 		else if(p.x > rect.right)
5178 			horizontalScroll(p.x - rect.right);
5179 
5180 		if(p.y < rect.top)
5181 			verticalScroll(p.y - rect.top);
5182 		else if(p.y > rect.bottom)
5183 			verticalScroll(p.y - rect.bottom);
5184 	}
5185 
5186 	///
5187 	void ensureVisibleInScroll(Rectangle rect) {
5188 		ensureVisibleInScroll(rect.upperLeft);
5189 		ensureVisibleInScroll(rect.lowerRight);
5190 	}
5191 
5192 	///
5193 	Rectangle viewportRectangle() {
5194 		return Rectangle(scrollOrigin, Size(viewportWidth, viewportHeight));
5195 	}
5196 
5197 	///
5198 	bool showingHorizontalScroll() {
5199 		return contentWidth > width;
5200 	}
5201 	///
5202 	bool showingVerticalScroll() {
5203 		return contentHeight > height;
5204 	}
5205 
5206 	/// This is called before the ordinary paint delegate,
5207 	/// giving you a chance to draw the window frame, etc,
5208 	/// before the scroll clip takes effect
5209 	void paintFrameAndBackground(WidgetPainter painter) {
5210 		version(win32_widgets) {
5211 			auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
5212 			auto p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
5213 			// since the pen is null, to fill the whole space, we need the +1 on both.
5214 			gdi.Rectangle(painter.impl.hdc, 0, 0, this.width + 1, this.height + 1);
5215 			SelectObject(painter.impl.hdc, p);
5216 			SelectObject(painter.impl.hdc, b);
5217 		}
5218 
5219 	}
5220 
5221 	// make space for the scroll bar, and that's it.
5222 	final override int paddingRight() { return scaleWithDpi(16); }
5223 	final override int paddingBottom() { return scaleWithDpi(16); }
5224 
5225 	/*
5226 		END SCROLLING
5227 	*/
5228 
5229 	override WidgetPainter draw() {
5230 		int x = this.x, y = this.y;
5231 		auto parent = this.parent;
5232 		while(parent) {
5233 			x += parent.x;
5234 			y += parent.y;
5235 			parent = parent.parent;
5236 		}
5237 
5238 		//version(win32_widgets) {
5239 			//auto painter = simpleWindowWrappingHwnd ? simpleWindowWrappingHwnd.draw(true) : parentWindow.win.draw(true);
5240 		//} else {
5241 			auto painter = parentWindow.win.draw(true);
5242 		//}
5243 		painter.originX = x;
5244 		painter.originY = y;
5245 
5246 		painter.originX = painter.originX - scrollOrigin.x;
5247 		painter.originY = painter.originY - scrollOrigin.y;
5248 		painter.setClipRectangle(scrollOrigin, viewportWidth(), viewportHeight());
5249 
5250 		return WidgetPainter(painter, this);
5251 	}
5252 
5253 	mixin ScrollableChildren;
5254 }
5255 
5256 // you need to have a Point scrollOrigin in the class somewhere
5257 // and a paintFrameAndBackground
5258 private mixin template ScrollableChildren() {
5259 	override protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force, bool invalidate) {
5260 		if(hidden)
5261 			return;
5262 
5263 		//version(win32_widgets)
5264 			//painter = simpleWindowWrappingHwnd ? simpleWindowWrappingHwnd.draw(true) : parentWindow.win.draw(true);
5265 
5266 		painter.originX = lox + x;
5267 		painter.originY = loy + y;
5268 
5269 		bool actuallyPainted = false;
5270 
5271 		const clip = containment.intersectionOf(Rectangle(Point(lox + x, loy + y), Size(width, height)));
5272 		if(clip == Rectangle.init)
5273 			return;
5274 
5275 		if(force || redrawRequested) {
5276 			//painter.setClipRectangle(scrollOrigin, width, height);
5277 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
5278 			paintFrameAndBackground(painter);
5279 		}
5280 
5281 		painter.originX = painter.originX - scrollOrigin.x;
5282 		painter.originY = painter.originY - scrollOrigin.y;
5283 		if(force || redrawRequested) {
5284 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY) + Point(2, 2) /* border */, clip.width - 4, clip.height - 4);
5285 			//painter.setClipRectangle(scrollOrigin + Point(2, 2) /* border */, width - 4, height - 4);
5286 
5287 			//erase(painter); // we paintFrameAndBackground above so no need
5288 			if(painter.visualTheme)
5289 				painter.visualTheme.doPaint(this, painter);
5290 			else
5291 				paint(painter);
5292 
5293 			if(invalidate) {
5294 				painter.invalidateRect(Rectangle(Point(clip.upperLeft.x - painter.originX, clip.upperRight.y - painter.originY), Size(clip.width, clip.height)));
5295 				// children are contained inside this, so no need to do extra work
5296 				invalidate = false;
5297 			}
5298 
5299 
5300 			actuallyPainted = true;
5301 			redrawRequested = false;
5302 		}
5303 		foreach(child; children) {
5304 			if(cast(FixedPosition) child)
5305 				child.privatePaint(painter, painter.originX + scrollOrigin.x, painter.originY + scrollOrigin.y, clip, actuallyPainted, invalidate);
5306 			else
5307 				child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted, invalidate);
5308 		}
5309 	}
5310 }
5311 
5312 private class InternalScrollableContainerInsideWidget : ContainerWidget {
5313 	ScrollableContainerWidget scw;
5314 
5315 	this(ScrollableContainerWidget parent) {
5316 		scw = parent;
5317 		super(parent);
5318 	}
5319 
5320 	version(custom_widgets)
5321 	override protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force, bool invalidate) {
5322 		if(hidden)
5323 			return;
5324 
5325 		bool actuallyPainted = false;
5326 
5327 		auto scrollOrigin = Point(scw.scrollX_, scw.scrollY_);
5328 
5329 		const clip = containment.intersectionOf(Rectangle(Point(lox + x, loy + y), Size(width + scw.scrollX_, height + scw.scrollY_)));
5330 		if(clip == Rectangle.init)
5331 			return;
5332 
5333 		painter.originX = lox + x - scrollOrigin.x;
5334 		painter.originY = loy + y - scrollOrigin.y;
5335 		if(force || redrawRequested) {
5336 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
5337 
5338 			erase(painter);
5339 			if(painter.visualTheme)
5340 				painter.visualTheme.doPaint(this, painter);
5341 			else
5342 				paint(painter);
5343 
5344 			if(invalidate) {
5345 				painter.invalidateRect(Rectangle(Point(clip.upperLeft.x - painter.originX, clip.upperRight.y - painter.originY), Size(clip.width, clip.height)));
5346 				// children are contained inside this, so no need to do extra work
5347 				invalidate = false;
5348 			}
5349 
5350 			actuallyPainted = true;
5351 			redrawRequested = false;
5352 		}
5353 		foreach(child; children) {
5354 			if(cast(FixedPosition) child)
5355 				child.privatePaint(painter, painter.originX + scrollOrigin.x, painter.originY + scrollOrigin.y, clip, actuallyPainted, invalidate);
5356 			else
5357 				child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted, invalidate);
5358 		}
5359 	}
5360 
5361 	version(custom_widgets)
5362 	override protected void addScrollPosition(ref int x, ref int y) {
5363 		x += scw.scrollX_;
5364 		y += scw.scrollY_;
5365 	}
5366 }
5367 
5368 /++
5369 	A widget meant to contain other widgets that may need to scroll.
5370 
5371 	Currently buggy.
5372 
5373 	History:
5374 		Added July 1, 2021 (dub v10.2)
5375 
5376 		On January 3, 2022, I tried to use it in a few other cases
5377 		and found it only worked well in the original test case. Since
5378 		it still sucks, I think I'm going to rewrite it again.
5379 +/
5380 class ScrollableContainerWidget : ContainerWidget {
5381 	///
5382 	this(Widget parent) {
5383 		super(parent);
5384 
5385 		container = new InternalScrollableContainerInsideWidget(this);
5386 		hsb = new HorizontalScrollbar(this);
5387 		vsb = new VerticalScrollbar(this);
5388 
5389 		tabStop = false;
5390 		container.tabStop = false;
5391 		magic = true;
5392 
5393 
5394 		vsb.addEventListener("scrolltonextline", () {
5395 			scrollBy(0, scaleWithDpi(16));
5396 		});
5397 		vsb.addEventListener("scrolltopreviousline", () {
5398 			scrollBy(0,scaleWithDpi( -16));
5399 		});
5400 		vsb.addEventListener("scrolltonextpage", () {
5401 			scrollBy(0, container.height);
5402 		});
5403 		vsb.addEventListener("scrolltopreviouspage", () {
5404 			scrollBy(0, -container.height);
5405 		});
5406 		vsb.addEventListener((scope ScrollToPositionEvent spe) {
5407 			scrollTo(scrollX_, spe.value);
5408 		});
5409 
5410 		this.addEventListener(delegate (scope ClickEvent e) {
5411 			if(e.button == MouseButton.wheelUp) {
5412 				if(!e.defaultPrevented)
5413 					scrollBy(0, scaleWithDpi(-16));
5414 				e.stopPropagation();
5415 			} else if(e.button == MouseButton.wheelDown) {
5416 				if(!e.defaultPrevented)
5417 					scrollBy(0, scaleWithDpi(16));
5418 				e.stopPropagation();
5419 			}
5420 		});
5421 	}
5422 
5423 	/+
5424 	override void defaultEventHandler_click(ClickEvent e) {
5425 	}
5426 	+/
5427 
5428 	override void removeAllChildren() {
5429 		container.removeAllChildren();
5430 	}
5431 
5432 	void scrollTo(int x, int y) {
5433 		scrollBy(x - scrollX_, y - scrollY_);
5434 	}
5435 
5436 	void scrollBy(int x, int y) {
5437 		auto ox = scrollX_;
5438 		auto oy = scrollY_;
5439 
5440 		auto nx = ox + x;
5441 		auto ny = oy + y;
5442 
5443 		if(nx < 0)
5444 			nx = 0;
5445 		if(ny < 0)
5446 			ny = 0;
5447 
5448 		auto maxX = hsb.max - container.width;
5449 		if(maxX < 0) maxX = 0;
5450 		auto maxY = vsb.max - container.height;
5451 		if(maxY < 0) maxY = 0;
5452 
5453 		if(nx > maxX)
5454 			nx = maxX;
5455 		if(ny > maxY)
5456 			ny = maxY;
5457 
5458 		auto dx = nx - ox;
5459 		auto dy = ny - oy;
5460 
5461 		if(dx || dy) {
5462 			version(win32_widgets)
5463 				ScrollWindowEx(container.hwnd, -dx, -dy, null, null, null, null, SW_SCROLLCHILDREN | SW_INVALIDATE | SW_ERASE);
5464 			else {
5465 				redraw();
5466 			}
5467 
5468 			hsb.setPosition = nx;
5469 			vsb.setPosition = ny;
5470 
5471 			scrollX_ = nx;
5472 			scrollY_ = ny;
5473 		}
5474 	}
5475 
5476 	private int scrollX_;
5477 	private int scrollY_;
5478 
5479 	void setTotalArea(int width, int height) {
5480 		hsb.setMax(width);
5481 		vsb.setMax(height);
5482 	}
5483 
5484 	///
5485 	void setViewableArea(int width, int height) {
5486 		hsb.setViewableArea(width);
5487 		vsb.setViewableArea(height);
5488 	}
5489 
5490 	private bool magic;
5491 	override void addChild(Widget w, int position = int.max) {
5492 		if(magic)
5493 			container.addChild(w, position);
5494 		else
5495 			super.addChild(w, position);
5496 	}
5497 
5498 	override void recomputeChildLayout() {
5499 		if(hsb is null || vsb is null || container is null) return;
5500 
5501 		/+
5502 		writeln(x, " ", y , " ", width, " ", height);
5503 		writeln(this.ContainerWidget.minWidth(), "x", this.ContainerWidget.minHeight());
5504 		+/
5505 
5506 		registerMovement();
5507 
5508 		hsb.height = scaleWithDpi(16); // FIXME? are tese 16s sane?
5509 		hsb.x = 0;
5510 		hsb.y = this.height - hsb.height;
5511 		hsb.width = this.width - scaleWithDpi(16);
5512 		hsb.recomputeChildLayout();
5513 
5514 		vsb.width = scaleWithDpi(16); // FIXME?
5515 		vsb.x = this.width - vsb.width;
5516 		vsb.y = 0;
5517 		vsb.height = this.height - scaleWithDpi(16);
5518 		vsb.recomputeChildLayout();
5519 
5520 		container.x = 0;
5521 		container.y = 0;
5522 		container.width = this.width - vsb.width;
5523 		container.height = this.height - hsb.height;
5524 		container.recomputeChildLayout();
5525 
5526 		scrollX_ = 0;
5527 		scrollY_ = 0;
5528 
5529 		hsb.setPosition(0);
5530 		vsb.setPosition(0);
5531 
5532 		int mw, mh;
5533 		Widget c = container;
5534 		// FIXME: hack here to handle a layout inside...
5535 		if(c.children.length == 1 && cast(Layout) c.children[0])
5536 			c = c.children[0];
5537 		foreach(child; c.children) {
5538 			auto w = child.x + child.width;
5539 			auto h = child.y + child.height;
5540 
5541 			if(w > mw) mw = w;
5542 			if(h > mh) mh = h;
5543 		}
5544 
5545 		setTotalArea(mw, mh);
5546 		setViewableArea(width, height);
5547 	}
5548 
5549 	override int minHeight() { return scaleWithDpi(64); }
5550 
5551 	HorizontalScrollbar hsb;
5552 	VerticalScrollbar vsb;
5553 	ContainerWidget container;
5554 }
5555 
5556 
5557 version(custom_widgets)
5558 private class InternalScrollableContainerWidget : Widget {
5559 
5560 	ScrollableWidget sw;
5561 
5562 	VerticalScrollbar verticalScrollBar;
5563 	HorizontalScrollbar horizontalScrollBar;
5564 
5565 	this(ScrollableWidget sw, Widget parent) {
5566 		this.sw = sw;
5567 
5568 		this.tabStop = false;
5569 
5570 		super(parent);
5571 
5572 		horizontalScrollBar = new HorizontalScrollbar(this);
5573 		verticalScrollBar = new VerticalScrollbar(this);
5574 
5575 		horizontalScrollBar.showing_ = false;
5576 		verticalScrollBar.showing_ = false;
5577 
5578 		horizontalScrollBar.addEventListener("scrolltonextline", {
5579 			horizontalScrollBar.setPosition(horizontalScrollBar.position + 1);
5580 			sw.horizontalScrollTo(horizontalScrollBar.position);
5581 		});
5582 		horizontalScrollBar.addEventListener("scrolltopreviousline", {
5583 			horizontalScrollBar.setPosition(horizontalScrollBar.position - 1);
5584 			sw.horizontalScrollTo(horizontalScrollBar.position);
5585 		});
5586 		verticalScrollBar.addEventListener("scrolltonextline", {
5587 			verticalScrollBar.setPosition(verticalScrollBar.position + 1);
5588 			sw.verticalScrollTo(verticalScrollBar.position);
5589 		});
5590 		verticalScrollBar.addEventListener("scrolltopreviousline", {
5591 			verticalScrollBar.setPosition(verticalScrollBar.position - 1);
5592 			sw.verticalScrollTo(verticalScrollBar.position);
5593 		});
5594 		horizontalScrollBar.addEventListener("scrolltonextpage", {
5595 			horizontalScrollBar.setPosition(horizontalScrollBar.position + horizontalScrollBar.step_);
5596 			sw.horizontalScrollTo(horizontalScrollBar.position);
5597 		});
5598 		horizontalScrollBar.addEventListener("scrolltopreviouspage", {
5599 			horizontalScrollBar.setPosition(horizontalScrollBar.position - horizontalScrollBar.step_);
5600 			sw.horizontalScrollTo(horizontalScrollBar.position);
5601 		});
5602 		verticalScrollBar.addEventListener("scrolltonextpage", {
5603 			verticalScrollBar.setPosition(verticalScrollBar.position + verticalScrollBar.step_);
5604 			sw.verticalScrollTo(verticalScrollBar.position);
5605 		});
5606 		verticalScrollBar.addEventListener("scrolltopreviouspage", {
5607 			verticalScrollBar.setPosition(verticalScrollBar.position - verticalScrollBar.step_);
5608 			sw.verticalScrollTo(verticalScrollBar.position);
5609 		});
5610 		horizontalScrollBar.addEventListener("scrolltoposition", (Event event) {
5611 			horizontalScrollBar.setPosition(event.intValue);
5612 			sw.horizontalScrollTo(horizontalScrollBar.position);
5613 		});
5614 		verticalScrollBar.addEventListener("scrolltoposition", (Event event) {
5615 			verticalScrollBar.setPosition(event.intValue);
5616 			sw.verticalScrollTo(verticalScrollBar.position);
5617 		});
5618 		horizontalScrollBar.addEventListener("scrolltrack", (Event event) {
5619 			horizontalScrollBar.setPosition(event.intValue);
5620 			sw.horizontalScrollTo(horizontalScrollBar.position);
5621 		});
5622 		verticalScrollBar.addEventListener("scrolltrack", (Event event) {
5623 			verticalScrollBar.setPosition(event.intValue);
5624 		});
5625 	}
5626 
5627 	// this is supposed to be basically invisible...
5628 	override int minWidth() { return sw.minWidth; }
5629 	override int minHeight() { return sw.minHeight; }
5630 	override int maxWidth() { return sw.maxWidth; }
5631 	override int maxHeight() { return sw.maxHeight; }
5632 	override int widthStretchiness() { return sw.widthStretchiness; }
5633 	override int heightStretchiness() { return sw.heightStretchiness; }
5634 	override int marginLeft() { return sw.marginLeft; }
5635 	override int marginRight() { return sw.marginRight; }
5636 	override int marginTop() { return sw.marginTop; }
5637 	override int marginBottom() { return sw.marginBottom; }
5638 	override int paddingLeft() { return sw.paddingLeft; }
5639 	override int paddingRight() { return sw.paddingRight; }
5640 	override int paddingTop() { return sw.paddingTop; }
5641 	override int paddingBottom() { return sw.paddingBottom; }
5642 	override void focus() { sw.focus(); }
5643 
5644 
5645 	override void recomputeChildLayout() {
5646 		// The stupid thing needs to calculate if a scroll bar is needed...
5647 		recomputeChildLayoutHelper();
5648 		// then running it again will position things correctly if the bar is NOT needed
5649 		recomputeChildLayoutHelper();
5650 
5651 		// this sucks but meh it barely works
5652 	}
5653 
5654 	private void recomputeChildLayoutHelper() {
5655 		if(sw is null) return;
5656 
5657 		bool both = sw.showingVerticalScroll && sw.showingHorizontalScroll;
5658 		if(horizontalScrollBar && verticalScrollBar) {
5659 			horizontalScrollBar.width = this.width - (both ? verticalScrollBar.minWidth() : 0);
5660 			horizontalScrollBar.height = horizontalScrollBar.minHeight();
5661 			horizontalScrollBar.x = 0;
5662 			horizontalScrollBar.y = this.height - horizontalScrollBar.minHeight();
5663 
5664 			verticalScrollBar.width = verticalScrollBar.minWidth();
5665 			verticalScrollBar.height = this.height - (both ? horizontalScrollBar.minHeight() : 0) - 2 - 2;
5666 			verticalScrollBar.x = this.width - verticalScrollBar.minWidth();
5667 			verticalScrollBar.y = 0 + 2;
5668 
5669 			sw.x = 0;
5670 			sw.y = 0;
5671 			sw.width = this.width - (verticalScrollBar.showing ? verticalScrollBar.width : 0);
5672 			sw.height = this.height - (horizontalScrollBar.showing ? horizontalScrollBar.height : 0);
5673 
5674 			if(sw.contentWidth_ <= this.width)
5675 				sw.scrollOrigin_.x = 0;
5676 			if(sw.contentHeight_ <= this.height)
5677 				sw.scrollOrigin_.y = 0;
5678 
5679 			horizontalScrollBar.recomputeChildLayout();
5680 			verticalScrollBar.recomputeChildLayout();
5681 			sw.recomputeChildLayout();
5682 		}
5683 
5684 		if(sw.contentWidth_ <= this.width)
5685 			sw.scrollOrigin_.x = 0;
5686 		if(sw.contentHeight_ <= this.height)
5687 			sw.scrollOrigin_.y = 0;
5688 
5689 		if(sw.showingHorizontalScroll())
5690 			horizontalScrollBar.showing(true, false);
5691 		else
5692 			horizontalScrollBar.showing(false, false);
5693 		if(sw.showingVerticalScroll())
5694 			verticalScrollBar.showing(true, false);
5695 		else
5696 			verticalScrollBar.showing(false, false);
5697 
5698 		verticalScrollBar.setViewableArea(sw.viewportHeight());
5699 		verticalScrollBar.setMax(sw.contentHeight);
5700 		verticalScrollBar.setPosition(sw.scrollOrigin.y);
5701 
5702 		horizontalScrollBar.setViewableArea(sw.viewportWidth());
5703 		horizontalScrollBar.setMax(sw.contentWidth);
5704 		horizontalScrollBar.setPosition(sw.scrollOrigin.x);
5705 	}
5706 }
5707 
5708 /*
5709 class ScrollableClientWidget : Widget {
5710 	this(Widget parent) {
5711 		super(parent);
5712 	}
5713 	override void paint(WidgetPainter p) {
5714 		parent.paint(p);
5715 	}
5716 }
5717 */
5718 
5719 /++
5720 	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.
5721 +/
5722 abstract class Slider : Widget {
5723 	this(int min, int max, int step, Widget parent) {
5724 		min_ = min;
5725 		max_ = max;
5726 		step_ = step;
5727 		page_ = step;
5728 		super(parent);
5729 	}
5730 
5731 	private int min_;
5732 	private int max_;
5733 	private int step_;
5734 	private int position_;
5735 	private int page_;
5736 
5737 	// selection start and selection end
5738 	// tics
5739 	// tooltip?
5740 	// some way to see and just type the value
5741 	// win32 buddy controls are labels
5742 
5743 	///
5744 	void setMin(int a) {
5745 		min_ = a;
5746 		version(custom_widgets)
5747 			redraw();
5748 		version(win32_widgets)
5749 			SendMessage(hwnd, TBM_SETRANGEMIN, true, a);
5750 	}
5751 	///
5752 	int min() {
5753 		return min_;
5754 	}
5755 	///
5756 	void setMax(int a) {
5757 		max_ = a;
5758 		version(custom_widgets)
5759 			redraw();
5760 		version(win32_widgets)
5761 			SendMessage(hwnd, TBM_SETRANGEMAX, true, a);
5762 	}
5763 	///
5764 	int max() {
5765 		return max_;
5766 	}
5767 	///
5768 	void setPosition(int a) {
5769 		if(a > max)
5770 			a = max;
5771 		if(a < min)
5772 			a = min;
5773 		position_ = a;
5774 		version(custom_widgets)
5775 			setPositionCustom(a);
5776 
5777 		version(win32_widgets)
5778 			setPositionWindows(a);
5779 	}
5780 	version(win32_widgets) {
5781 		protected abstract void setPositionWindows(int a);
5782 	}
5783 
5784 	protected abstract int win32direction();
5785 
5786 	/++
5787 		Alias for [position] for better compatibility with generic code.
5788 
5789 		History:
5790 			Added October 5, 2021
5791 	+/
5792 	@property int value() {
5793 		return position;
5794 	}
5795 
5796 	///
5797 	int position() {
5798 		return position_;
5799 	}
5800 	///
5801 	void setStep(int a) {
5802 		step_ = a;
5803 		version(win32_widgets)
5804 			SendMessage(hwnd, TBM_SETLINESIZE, 0, a);
5805 	}
5806 	///
5807 	int step() {
5808 		return step_;
5809 	}
5810 	///
5811 	void setPageSize(int a) {
5812 		page_ = a;
5813 		version(win32_widgets)
5814 			SendMessage(hwnd, TBM_SETPAGESIZE, 0, a);
5815 	}
5816 	///
5817 	int pageSize() {
5818 		return page_;
5819 	}
5820 
5821 	private void notify() {
5822 		auto event = new ChangeEvent!int(this, &this.position);
5823 		event.dispatch();
5824 	}
5825 
5826 	version(win32_widgets)
5827 	void win32Setup(int style) {
5828 		createWin32Window(this, TRACKBAR_CLASS, "",
5829 			0|WS_CHILD|WS_VISIBLE|style|TBS_TOOLTIPS, 0);
5830 
5831 		// the trackbar sends the same messages as scroll, which
5832 		// our other layer sends as these... just gonna translate
5833 		// here
5834 		this.addDirectEventListener("scrolltoposition", (Event event) {
5835 			event.stopPropagation();
5836 			this.setPosition(this.win32direction > 0 ? event.intValue : max - event.intValue);
5837 			notify();
5838 		});
5839 		this.addDirectEventListener("scrolltonextline", (Event event) {
5840 			event.stopPropagation();
5841 			this.setPosition(this.position + this.step_ * this.win32direction);
5842 			notify();
5843 		});
5844 		this.addDirectEventListener("scrolltopreviousline", (Event event) {
5845 			event.stopPropagation();
5846 			this.setPosition(this.position - this.step_ * this.win32direction);
5847 			notify();
5848 		});
5849 		this.addDirectEventListener("scrolltonextpage", (Event event) {
5850 			event.stopPropagation();
5851 			this.setPosition(this.position + this.page_ * this.win32direction);
5852 			notify();
5853 		});
5854 		this.addDirectEventListener("scrolltopreviouspage", (Event event) {
5855 			event.stopPropagation();
5856 			this.setPosition(this.position - this.page_ * this.win32direction);
5857 			notify();
5858 		});
5859 
5860 		setMin(min_);
5861 		setMax(max_);
5862 		setStep(step_);
5863 		setPageSize(page_);
5864 	}
5865 
5866 	version(custom_widgets) {
5867 		protected MouseTrackingWidget thumb;
5868 
5869 		protected abstract void setPositionCustom(int a);
5870 
5871 		override void defaultEventHandler_keydown(KeyDownEvent event) {
5872 			switch(event.key) {
5873 				case Key.Up:
5874 				case Key.Right:
5875 					setPosition(position() - step() * win32direction);
5876 					changed();
5877 				break;
5878 				case Key.Down:
5879 				case Key.Left:
5880 					setPosition(position() + step() * win32direction);
5881 					changed();
5882 				break;
5883 				case Key.Home:
5884 					setPosition(win32direction > 0 ? min() : max());
5885 					changed();
5886 				break;
5887 				case Key.End:
5888 					setPosition(win32direction > 0 ? max() : min());
5889 					changed();
5890 				break;
5891 				case Key.PageUp:
5892 					setPosition(position() - pageSize() * win32direction);
5893 					changed();
5894 				break;
5895 				case Key.PageDown:
5896 					setPosition(position() + pageSize() * win32direction);
5897 					changed();
5898 				break;
5899 				default:
5900 			}
5901 			super.defaultEventHandler_keydown(event);
5902 		}
5903 
5904 		protected void changed() {
5905 			auto ev = new ChangeEvent!int(this, &position);
5906 			ev.dispatch();
5907 		}
5908 	}
5909 }
5910 
5911 /++
5912 
5913 +/
5914 class VerticalSlider : Slider {
5915 	this(int min, int max, int step, Widget parent) {
5916 		version(custom_widgets)
5917 			initialize();
5918 
5919 		super(min, max, step, parent);
5920 
5921 		version(win32_widgets)
5922 			win32Setup(TBS_VERT | 0x0200 /* TBS_REVERSED */);
5923 	}
5924 
5925 	protected override int win32direction() {
5926 		return -1;
5927 	}
5928 
5929 	version(win32_widgets)
5930 	protected override void setPositionWindows(int a) {
5931 		// the windows thing makes the top 0 and i don't like that.
5932 		SendMessage(hwnd, TBM_SETPOS, true, max - a);
5933 	}
5934 
5935 	version(custom_widgets)
5936 	private void initialize() {
5937 		thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.vertical, this);
5938 
5939 		thumb.tabStop = false;
5940 
5941 		thumb.thumbWidth = width;
5942 		thumb.thumbHeight = scaleWithDpi(16);
5943 
5944 		thumb.addEventListener(EventType.change, () {
5945 			auto sx = thumb.positionY * max() / (thumb.height - scaleWithDpi(16));
5946 			sx = max - sx;
5947 			//informProgramThatUserChangedPosition(sx);
5948 
5949 			position_ = sx;
5950 
5951 			changed();
5952 		});
5953 	}
5954 
5955 	version(custom_widgets)
5956 	override void recomputeChildLayout() {
5957 		thumb.thumbWidth = this.width;
5958 		super.recomputeChildLayout();
5959 		setPositionCustom(position_);
5960 	}
5961 
5962 	version(custom_widgets)
5963 	protected override void setPositionCustom(int a) {
5964 		if(max())
5965 			thumb.positionY = (max - a) * (thumb.height - scaleWithDpi(16)) / max();
5966 		redraw();
5967 	}
5968 }
5969 
5970 /++
5971 
5972 +/
5973 class HorizontalSlider : Slider {
5974 	this(int min, int max, int step, Widget parent) {
5975 		version(custom_widgets)
5976 			initialize();
5977 
5978 		super(min, max, step, parent);
5979 
5980 		version(win32_widgets)
5981 			win32Setup(TBS_HORZ);
5982 	}
5983 
5984 	version(win32_widgets)
5985 	protected override void setPositionWindows(int a) {
5986 		SendMessage(hwnd, TBM_SETPOS, true, a);
5987 	}
5988 
5989 	protected override int win32direction() {
5990 		return 1;
5991 	}
5992 
5993 	version(custom_widgets)
5994 	private void initialize() {
5995 		thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.horizontal, this);
5996 
5997 		thumb.tabStop = false;
5998 
5999 		thumb.thumbWidth = scaleWithDpi(16);
6000 		thumb.thumbHeight = height;
6001 
6002 		thumb.addEventListener(EventType.change, () {
6003 			auto sx = thumb.positionX * max() / (thumb.width - scaleWithDpi(16));
6004 			//informProgramThatUserChangedPosition(sx);
6005 
6006 			position_ = sx;
6007 
6008 			changed();
6009 		});
6010 	}
6011 
6012 	version(custom_widgets)
6013 	override void recomputeChildLayout() {
6014 		thumb.thumbHeight = this.height;
6015 		super.recomputeChildLayout();
6016 		setPositionCustom(position_);
6017 	}
6018 
6019 	version(custom_widgets)
6020 	protected override void setPositionCustom(int a) {
6021 		if(max())
6022 			thumb.positionX = a * (thumb.width - scaleWithDpi(16)) / max();
6023 		redraw();
6024 	}
6025 }
6026 
6027 
6028 ///
6029 abstract class ScrollbarBase : Widget {
6030 	///
6031 	this(Widget parent) {
6032 		super(parent);
6033 		tabStop = false;
6034 		step_ = scaleWithDpi(16);
6035 	}
6036 
6037 	private int viewableArea_;
6038 	private int max_;
6039 	private int step_;// = 16;
6040 	private int position_;
6041 
6042 	///
6043 	bool atEnd() {
6044 		return position_ + viewableArea_ >= max_;
6045 	}
6046 
6047 	///
6048 	bool atStart() {
6049 		return position_ == 0;
6050 	}
6051 
6052 	///
6053 	void setViewableArea(int a) {
6054 		viewableArea_ = a;
6055 		version(custom_widgets)
6056 			redraw();
6057 	}
6058 	///
6059 	void setMax(int a) {
6060 		max_ = a;
6061 		version(custom_widgets)
6062 			redraw();
6063 	}
6064 	///
6065 	int max() {
6066 		return max_;
6067 	}
6068 	///
6069 	void setPosition(int a) {
6070 		auto logicalMax = max_ - viewableArea_;
6071 		if(a == int.max)
6072 			a = logicalMax;
6073 
6074 		if(a > logicalMax)
6075 			a = logicalMax;
6076 		if(a < 0)
6077 			a = 0;
6078 
6079 		position_ = a;
6080 
6081 		version(custom_widgets)
6082 			redraw();
6083 	}
6084 	///
6085 	int position() {
6086 		return position_;
6087 	}
6088 	///
6089 	void setStep(int a) {
6090 		step_ = a;
6091 	}
6092 	///
6093 	int step() {
6094 		return step_;
6095 	}
6096 
6097 	// FIXME: remove this.... maybe
6098 	/+
6099 	protected void informProgramThatUserChangedPosition(int n) {
6100 		position_ = n;
6101 		auto evt = new Event(EventType.change, this);
6102 		evt.intValue = n;
6103 		evt.dispatch();
6104 	}
6105 	+/
6106 
6107 	version(custom_widgets) {
6108 		enum MIN_THUMB_SIZE = 8;
6109 
6110 		abstract protected int getBarDim();
6111 		int thumbSize() {
6112 			if(viewableArea_ >= max_ || max_ == 0)
6113 				return getBarDim();
6114 
6115 			int res = viewableArea_ * getBarDim() / max_;
6116 
6117 			if(res < scaleWithDpi(MIN_THUMB_SIZE))
6118 				res = scaleWithDpi(MIN_THUMB_SIZE);
6119 
6120 			return res;
6121 		}
6122 
6123 		int thumbPosition() {
6124 			/*
6125 				viewableArea_ is the viewport height/width
6126 				position_ is where we are
6127 			*/
6128 			//if(position_ + viewableArea_ >= max_)
6129 				//return getBarDim - thumbSize;
6130 
6131 			auto maximumPossibleValue = getBarDim() - thumbSize;
6132 			auto maximiumLogicalValue = max_ - viewableArea_;
6133 
6134 			auto p = (maximiumLogicalValue > 0) ? cast(int) (cast(long) position_ * maximumPossibleValue / maximiumLogicalValue) : 0;
6135 
6136 			return p;
6137 		}
6138 	}
6139 }
6140 
6141 //public import mgt;
6142 
6143 /++
6144 	A mouse tracking widget is one that follows the mouse when dragged inside it.
6145 
6146 	Concrete subclasses may include a scrollbar thumb and a volume control.
6147 +/
6148 //version(custom_widgets)
6149 class MouseTrackingWidget : Widget {
6150 
6151 	///
6152 	int positionX() { return positionX_; }
6153 	///
6154 	int positionY() { return positionY_; }
6155 
6156 	///
6157 	void positionX(int p) { positionX_ = p; }
6158 	///
6159 	void positionY(int p) { positionY_ = p; }
6160 
6161 	private int positionX_;
6162 	private int positionY_;
6163 
6164 	///
6165 	enum Orientation {
6166 		horizontal, ///
6167 		vertical, ///
6168 		twoDimensional, ///
6169 	}
6170 
6171 	private int thumbWidth_;
6172 	private int thumbHeight_;
6173 
6174 	///
6175 	int thumbWidth() { return thumbWidth_; }
6176 	///
6177 	int thumbHeight() { return thumbHeight_; }
6178 	///
6179 	int thumbWidth(int a) { return thumbWidth_ = a; }
6180 	///
6181 	int thumbHeight(int a) { return thumbHeight_ = a; }
6182 
6183 	private bool dragging;
6184 	private bool hovering;
6185 	private int startMouseX, startMouseY;
6186 
6187 	///
6188 	this(Orientation orientation, Widget parent) {
6189 		super(parent);
6190 
6191 		//assert(parentWindow !is null);
6192 
6193 		addEventListener((MouseDownEvent event) {
6194 			if(event.clientX >= positionX && event.clientX < positionX + thumbWidth && event.clientY >= positionY && event.clientY < positionY + thumbHeight) {
6195 				dragging = true;
6196 				startMouseX = event.clientX - positionX;
6197 				startMouseY = event.clientY - positionY;
6198 				parentWindow.captureMouse(this);
6199 			} else {
6200 				if(orientation == Orientation.horizontal || orientation == Orientation.twoDimensional)
6201 					positionX = event.clientX - thumbWidth / 2;
6202 				if(orientation == Orientation.vertical || orientation == Orientation.twoDimensional)
6203 					positionY = event.clientY - thumbHeight / 2;
6204 
6205 				if(positionX + thumbWidth > this.width)
6206 					positionX = this.width - thumbWidth;
6207 				if(positionY + thumbHeight > this.height)
6208 					positionY = this.height - thumbHeight;
6209 
6210 				if(positionX < 0)
6211 					positionX = 0;
6212 				if(positionY < 0)
6213 					positionY = 0;
6214 
6215 
6216 				// this.emit!(ChangeEvent!void)();
6217 				auto evt = new Event(EventType.change, this);
6218 				evt.sendDirectly();
6219 
6220 				redraw();
6221 
6222 			}
6223 		});
6224 
6225 		addEventListener(EventType.mouseup, (Event event) {
6226 			dragging = false;
6227 			parentWindow.releaseMouseCapture();
6228 		});
6229 
6230 		addEventListener(EventType.mouseout, (Event event) {
6231 			if(!hovering)
6232 				return;
6233 			hovering = false;
6234 			redraw();
6235 		});
6236 
6237 		int lpx, lpy;
6238 
6239 		addEventListener((MouseMoveEvent event) {
6240 			auto oh = hovering;
6241 			if(event.clientX >= positionX && event.clientX < positionX + thumbWidth && event.clientY >= positionY && event.clientY < positionY + thumbHeight) {
6242 				hovering = true;
6243 			} else {
6244 				hovering = false;
6245 			}
6246 			if(!dragging) {
6247 				if(hovering != oh)
6248 					redraw();
6249 				return;
6250 			}
6251 
6252 			if(orientation == Orientation.horizontal || orientation == Orientation.twoDimensional)
6253 				positionX = event.clientX - startMouseX; // FIXME: click could be in the middle of it
6254 			if(orientation == Orientation.vertical || orientation == Orientation.twoDimensional)
6255 				positionY = event.clientY - startMouseY;
6256 
6257 			if(positionX + thumbWidth > this.width)
6258 				positionX = this.width - thumbWidth;
6259 			if(positionY + thumbHeight > this.height)
6260 				positionY = this.height - thumbHeight;
6261 
6262 			if(positionX < 0)
6263 				positionX = 0;
6264 			if(positionY < 0)
6265 				positionY = 0;
6266 
6267 			if(positionX != lpx || positionY != lpy) {
6268 				lpx = positionX;
6269 				lpy = positionY;
6270 
6271 				auto evt = new Event(EventType.change, this);
6272 				evt.sendDirectly();
6273 			}
6274 
6275 			redraw();
6276 		});
6277 	}
6278 
6279 	version(custom_widgets)
6280 	override void paint(WidgetPainter painter) {
6281 		auto cs = getComputedStyle();
6282 		auto c = darken(cs.windowBackgroundColor, 0.2);
6283 		painter.outlineColor = c;
6284 		painter.fillColor = c;
6285 		painter.drawRectangle(Point(0, 0), this.width, this.height);
6286 
6287 		auto color = hovering ? cs.hoveringColor : cs.windowBackgroundColor;
6288 		draw3dFrame(positionX, positionY, thumbWidth, thumbHeight, painter, FrameStyle.risen, color);
6289 	}
6290 }
6291 
6292 //version(custom_widgets)
6293 //private
6294 class HorizontalScrollbar : ScrollbarBase {
6295 
6296 	version(custom_widgets) {
6297 		private MouseTrackingWidget thumb;
6298 
6299 		override int getBarDim() {
6300 			return thumb.width;
6301 		}
6302 	}
6303 
6304 	override void setViewableArea(int a) {
6305 		super.setViewableArea(a);
6306 
6307 		version(win32_widgets) {
6308 			SCROLLINFO info;
6309 			info.cbSize = info.sizeof;
6310 			info.nPage = a + 1;
6311 			info.fMask = SIF_PAGE;
6312 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6313 		} else version(custom_widgets) {
6314 			thumb.positionX = thumbPosition;
6315 			thumb.thumbWidth = thumbSize;
6316 			thumb.redraw();
6317 		} else static assert(0);
6318 
6319 	}
6320 
6321 	override void setMax(int a) {
6322 		super.setMax(a);
6323 		version(win32_widgets) {
6324 			SCROLLINFO info;
6325 			info.cbSize = info.sizeof;
6326 			info.nMin = 0;
6327 			info.nMax = max;
6328 			info.fMask = SIF_RANGE;
6329 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6330 		} else version(custom_widgets) {
6331 			thumb.positionX = thumbPosition;
6332 			thumb.thumbWidth = thumbSize;
6333 			thumb.redraw();
6334 		}
6335 	}
6336 
6337 	override void setPosition(int a) {
6338 		super.setPosition(a);
6339 		version(win32_widgets) {
6340 			SCROLLINFO info;
6341 			info.cbSize = info.sizeof;
6342 			info.fMask = SIF_POS;
6343 			info.nPos = position;
6344 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6345 		} else version(custom_widgets) {
6346 			thumb.positionX = thumbPosition();
6347 			thumb.thumbWidth = thumbSize;
6348 			thumb.redraw();
6349 		} else static assert(0);
6350 	}
6351 
6352 	this(Widget parent) {
6353 		super(parent);
6354 
6355 		version(win32_widgets) {
6356 			createWin32Window(this, "Scrollbar"w, "",
6357 				0|WS_CHILD|WS_VISIBLE|SBS_HORZ|SBS_BOTTOMALIGN, 0);
6358 		} else version(custom_widgets) {
6359 			auto vl = new HorizontalLayout(this);
6360 			auto leftButton = new ArrowButton(ArrowDirection.left, vl);
6361 			leftButton.setClickRepeat(scrollClickRepeatInterval);
6362 			thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.horizontal, vl);
6363 			auto rightButton = new ArrowButton(ArrowDirection.right, vl);
6364 			rightButton.setClickRepeat(scrollClickRepeatInterval);
6365 
6366 			leftButton.tabStop = false;
6367 			rightButton.tabStop = false;
6368 			thumb.tabStop = false;
6369 
6370 			leftButton.addEventListener(EventType.triggered, () {
6371 				this.emitCommand!"scrolltopreviousline"();
6372 				//informProgramThatUserChangedPosition(position - step());
6373 			});
6374 			rightButton.addEventListener(EventType.triggered, () {
6375 				this.emitCommand!"scrolltonextline"();
6376 				//informProgramThatUserChangedPosition(position + step());
6377 			});
6378 
6379 			thumb.thumbWidth = this.minWidth;
6380 			thumb.thumbHeight = scaleWithDpi(16);
6381 
6382 			thumb.addEventListener(EventType.change, () {
6383 				auto maximumPossibleValue = thumb.width - thumb.thumbWidth;
6384 				auto sx = maximumPossibleValue ? cast(int)(cast(long) thumb.positionX * (max()-viewableArea_) / maximumPossibleValue) : 0;
6385 
6386 				//informProgramThatUserChangedPosition(sx);
6387 
6388 				auto ev = new ScrollToPositionEvent(this, sx);
6389 				ev.dispatch();
6390 			});
6391 		}
6392 	}
6393 
6394 	override int minHeight() { return scaleWithDpi(16); }
6395 	override int maxHeight() { return scaleWithDpi(16); }
6396 	override int minWidth() { return scaleWithDpi(48); }
6397 }
6398 
6399 class ScrollToPositionEvent : Event {
6400 	enum EventString = "scrolltoposition";
6401 
6402 	this(Widget target, int value) {
6403 		this.value = value;
6404 		super(EventString, target);
6405 	}
6406 
6407 	immutable int value;
6408 
6409 	override @property int intValue() {
6410 		return value;
6411 	}
6412 }
6413 
6414 //version(custom_widgets)
6415 //private
6416 class VerticalScrollbar : ScrollbarBase {
6417 
6418 	version(custom_widgets) {
6419 		override int getBarDim() {
6420 			return thumb.height;
6421 		}
6422 
6423 		private MouseTrackingWidget thumb;
6424 	}
6425 
6426 	override void setViewableArea(int a) {
6427 		super.setViewableArea(a);
6428 
6429 		version(win32_widgets) {
6430 			SCROLLINFO info;
6431 			info.cbSize = info.sizeof;
6432 			info.nPage = a + 1;
6433 			info.fMask = SIF_PAGE;
6434 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6435 		} else version(custom_widgets) {
6436 			thumb.positionY = thumbPosition;
6437 			thumb.thumbHeight = thumbSize;
6438 			thumb.redraw();
6439 		} else static assert(0);
6440 
6441 	}
6442 
6443 	override void setMax(int a) {
6444 		super.setMax(a);
6445 		version(win32_widgets) {
6446 			SCROLLINFO info;
6447 			info.cbSize = info.sizeof;
6448 			info.nMin = 0;
6449 			info.nMax = max;
6450 			info.fMask = SIF_RANGE;
6451 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6452 		} else version(custom_widgets) {
6453 			thumb.positionY = thumbPosition;
6454 			thumb.thumbHeight = thumbSize;
6455 			thumb.redraw();
6456 		}
6457 	}
6458 
6459 	override void setPosition(int a) {
6460 		super.setPosition(a);
6461 		version(win32_widgets) {
6462 			SCROLLINFO info;
6463 			info.cbSize = info.sizeof;
6464 			info.fMask = SIF_POS;
6465 			info.nPos = position;
6466 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6467 		} else version(custom_widgets) {
6468 			thumb.positionY = thumbPosition;
6469 			thumb.thumbHeight = thumbSize;
6470 			thumb.redraw();
6471 		} else static assert(0);
6472 	}
6473 
6474 	this(Widget parent) {
6475 		super(parent);
6476 
6477 		version(win32_widgets) {
6478 			createWin32Window(this, "Scrollbar"w, "",
6479 				0|WS_CHILD|WS_VISIBLE|SBS_VERT|SBS_RIGHTALIGN, 0);
6480 		} else version(custom_widgets) {
6481 			auto vl = new VerticalLayout(this);
6482 			auto upButton = new ArrowButton(ArrowDirection.up, vl);
6483 			upButton.setClickRepeat(scrollClickRepeatInterval);
6484 			thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.vertical, vl);
6485 			auto downButton = new ArrowButton(ArrowDirection.down, vl);
6486 			downButton.setClickRepeat(scrollClickRepeatInterval);
6487 
6488 			upButton.addEventListener(EventType.triggered, () {
6489 				this.emitCommand!"scrolltopreviousline"();
6490 				//informProgramThatUserChangedPosition(position - step());
6491 			});
6492 			downButton.addEventListener(EventType.triggered, () {
6493 				this.emitCommand!"scrolltonextline"();
6494 				//informProgramThatUserChangedPosition(position + step());
6495 			});
6496 
6497 			thumb.thumbWidth = this.minWidth;
6498 			thumb.thumbHeight = scaleWithDpi(16);
6499 
6500 			thumb.addEventListener(EventType.change, () {
6501 				auto maximumPossibleValue = thumb.height - thumb.thumbHeight;
6502 				auto sy = maximumPossibleValue ? cast(int) (cast(long) thumb.positionY * (max()-viewableArea_) / maximumPossibleValue) : 0;
6503 
6504 				auto ev = new ScrollToPositionEvent(this, sy);
6505 				ev.dispatch();
6506 
6507 				//informProgramThatUserChangedPosition(sy);
6508 			});
6509 
6510 			upButton.tabStop = false;
6511 			downButton.tabStop = false;
6512 			thumb.tabStop = false;
6513 		}
6514 	}
6515 
6516 	override int minWidth() { return scaleWithDpi(16); }
6517 	override int maxWidth() { return scaleWithDpi(16); }
6518 	override int minHeight() { return scaleWithDpi(48); }
6519 }
6520 
6521 
6522 /++
6523 	EXPERIMENTAL
6524 
6525 	A widget specialized for being a container for other widgets.
6526 
6527 	History:
6528 		Added May 29, 2021. Not stabilized at this time.
6529 +/
6530 class WidgetContainer : Widget {
6531 	this(Widget parent) {
6532 		tabStop = false;
6533 		super(parent);
6534 	}
6535 
6536 	override int maxHeight() {
6537 		if(this.children.length == 1) {
6538 			return saturatedSum(this.children[0].maxHeight, this.children[0].marginTop, this.children[0].marginBottom);
6539 		} else {
6540 			return int.max;
6541 		}
6542 	}
6543 
6544 	override int maxWidth() {
6545 		if(this.children.length == 1) {
6546 			return saturatedSum(this.children[0].maxWidth, this.children[0].marginLeft, this.children[0].marginRight);
6547 		} else {
6548 			return int.max;
6549 		}
6550 	}
6551 
6552 	/+
6553 
6554 	override int minHeight() {
6555 		int largest = 0;
6556 		int margins = 0;
6557 		int lastMargin = 0;
6558 		foreach(child; children) {
6559 			auto mh = child.minHeight();
6560 			if(mh > largest)
6561 				largest = mh;
6562 			margins += mymax(lastMargin, child.marginTop());
6563 			lastMargin = child.marginBottom();
6564 		}
6565 		return largest + margins;
6566 	}
6567 
6568 	override int maxHeight() {
6569 		int largest = 0;
6570 		int margins = 0;
6571 		int lastMargin = 0;
6572 		foreach(child; children) {
6573 			auto mh = child.maxHeight();
6574 			if(mh == int.max)
6575 				return int.max;
6576 			if(mh > largest)
6577 				largest = mh;
6578 			margins += mymax(lastMargin, child.marginTop());
6579 			lastMargin = child.marginBottom();
6580 		}
6581 		return largest + margins;
6582 	}
6583 
6584 	override int minWidth() {
6585 		int min;
6586 		foreach(child; children) {
6587 			auto cm = child.minWidth;
6588 			if(cm > min)
6589 				min = cm;
6590 		}
6591 		return min + paddingLeft + paddingRight;
6592 	}
6593 
6594 	override int minHeight() {
6595 		int min;
6596 		foreach(child; children) {
6597 			auto cm = child.minHeight;
6598 			if(cm > min)
6599 				min = cm;
6600 		}
6601 		return min + paddingTop + paddingBottom;
6602 	}
6603 
6604 	override int maxHeight() {
6605 		int largest = 0;
6606 		int margins = 0;
6607 		int lastMargin = 0;
6608 		foreach(child; children) {
6609 			auto mh = child.maxHeight();
6610 			if(mh == int.max)
6611 				return int.max;
6612 			if(mh > largest)
6613 				largest = mh;
6614 			margins += mymax(lastMargin, child.marginTop());
6615 			lastMargin = child.marginBottom();
6616 		}
6617 		return largest + margins;
6618 	}
6619 
6620 	override int heightStretchiness() {
6621 		int max;
6622 		foreach(child; children) {
6623 			auto c = child.heightStretchiness;
6624 			if(c > max)
6625 				max = c;
6626 		}
6627 		return max;
6628 	}
6629 
6630 	override int marginTop() {
6631 		if(this.children.length)
6632 			return this.children[0].marginTop;
6633 		return 0;
6634 	}
6635 	+/
6636 }
6637 
6638 ///
6639 abstract class Layout : Widget {
6640 	this(Widget parent) {
6641 		tabStop = false;
6642 		super(parent);
6643 	}
6644 }
6645 
6646 /++
6647 	Makes all children minimum width and height, placing them down
6648 	left to right, top to bottom.
6649 
6650 	Useful if you want to make a list of buttons that automatically
6651 	wrap to a new line when necessary.
6652 +/
6653 class InlineBlockLayout : Layout {
6654 	///
6655 	this(Widget parent) { super(parent); }
6656 
6657 	override void recomputeChildLayout() {
6658 		registerMovement();
6659 
6660 		int x = this.paddingLeft, y = this.paddingTop;
6661 
6662 		int lineHeight;
6663 		int previousMargin = 0;
6664 		int previousMarginBottom = 0;
6665 
6666 		foreach(child; children) {
6667 			if(child.hidden)
6668 				continue;
6669 			if(cast(FixedPosition) child) {
6670 				child.recomputeChildLayout();
6671 				continue;
6672 			}
6673 			child.width = child.flexBasisWidth();
6674 			if(child.width == 0)
6675 				child.width = child.minWidth();
6676 			if(child.width == 0)
6677 				child.width = 32;
6678 
6679 			child.height = child.flexBasisHeight();
6680 			if(child.height == 0)
6681 				child.height = child.minHeight();
6682 			if(child.height == 0)
6683 				child.height = 32;
6684 
6685 			if(x + child.width + paddingRight > this.width) {
6686 				x = this.paddingLeft;
6687 				y += lineHeight;
6688 				lineHeight = 0;
6689 				previousMargin = 0;
6690 				previousMarginBottom = 0;
6691 			}
6692 
6693 			auto margin = child.marginLeft;
6694 			if(previousMargin > margin)
6695 				margin = previousMargin;
6696 
6697 			x += margin;
6698 
6699 			child.x = x;
6700 			child.y = y;
6701 
6702 			int marginTopApplied;
6703 			if(child.marginTop > previousMarginBottom) {
6704 				child.y += child.marginTop;
6705 				marginTopApplied = child.marginTop;
6706 			}
6707 
6708 			x += child.width;
6709 			previousMargin = child.marginRight;
6710 
6711 			if(child.marginBottom > previousMarginBottom)
6712 				previousMarginBottom = child.marginBottom;
6713 
6714 			auto h = child.height + previousMarginBottom + marginTopApplied;
6715 			if(h > lineHeight)
6716 				lineHeight = h;
6717 
6718 			child.recomputeChildLayout();
6719 		}
6720 
6721 	}
6722 
6723 	override int minWidth() {
6724 		int min;
6725 		foreach(child; children) {
6726 			auto cm = child.minWidth;
6727 			if(cm > min)
6728 				min = cm;
6729 		}
6730 		return min + paddingLeft + paddingRight;
6731 	}
6732 
6733 	override int minHeight() {
6734 		int min;
6735 		foreach(child; children) {
6736 			auto cm = child.minHeight;
6737 			if(cm > min)
6738 				min = cm;
6739 		}
6740 		return min + paddingTop + paddingBottom;
6741 	}
6742 }
6743 
6744 /++
6745 	A TabMessageWidget is a clickable row of tabs followed by a content area, very similar
6746 	to the [TabWidget]. The difference is the TabMessageWidget only sends messages, whereas
6747 	the [TabWidget] will automatically change pages of child widgets.
6748 
6749 	This allows you to react to it however you see fit rather than having to
6750 	be tied to just the new sets of child widgets.
6751 
6752 	It sends the message in the form of `this.emitCommand!"changetab"();`.
6753 
6754 	History:
6755 		Added December 24, 2021 (dub v10.5)
6756 +/
6757 class TabMessageWidget : Widget {
6758 
6759 	protected void tabIndexClicked(int item) {
6760 		this.emitCommand!"changetab"();
6761 	}
6762 
6763 	/++
6764 		Adds the a new tab to the control with the given title.
6765 
6766 		Returns:
6767 			The index of the newly added tab. You will need to know
6768 			this index to refer to it later and to know which tab to
6769 			change to when you get a changetab message.
6770 	+/
6771 	int addTab(string title, int pos = int.max) {
6772 		version(win32_widgets) {
6773 			TCITEM item;
6774 			item.mask = TCIF_TEXT;
6775 			WCharzBuffer buf = WCharzBuffer(title);
6776 			item.pszText = buf.ptr;
6777 			return cast(int) SendMessage(hwnd, TCM_INSERTITEM, pos, cast(LPARAM) &item);
6778 		} else version(custom_widgets) {
6779 			if(pos >= tabs.length) {
6780 				tabs ~= title;
6781 				redraw();
6782 				return cast(int) tabs.length - 1;
6783 			} else if(pos <= 0) {
6784 				tabs = title ~ tabs;
6785 				redraw();
6786 				return 0;
6787 			} else {
6788 				tabs = tabs[0 .. pos] ~ title ~ title[pos .. $];
6789 				redraw();
6790 				return pos;
6791 			}
6792 		}
6793 	}
6794 
6795 	override void addChild(Widget child, int pos = int.max) {
6796 		if(container)
6797 			container.addChild(child, pos);
6798 		else
6799 			super.addChild(child, pos);
6800 	}
6801 
6802 	protected Widget makeContainer() {
6803 		return new Widget(this);
6804 	}
6805 
6806 	private Widget container;
6807 
6808 	override void recomputeChildLayout() {
6809 		version(win32_widgets) {
6810 			this.registerMovement();
6811 
6812 			RECT rect;
6813 			GetWindowRect(hwnd, &rect);
6814 
6815 			auto left = rect.left;
6816 			auto top = rect.top;
6817 
6818 			TabCtrl_AdjustRect(hwnd, false, &rect);
6819 			foreach(child; children) {
6820 				if(!child.showing) continue;
6821 				child.x = rect.left - left;
6822 				child.y = rect.top - top;
6823 				child.width = rect.right - rect.left;
6824 				child.height = rect.bottom - rect.top;
6825 				child.recomputeChildLayout();
6826 			}
6827 		} else version(custom_widgets) {
6828 			this.registerMovement();
6829 			foreach(child; children) {
6830 				if(!child.showing) continue;
6831 				child.x = 2;
6832 				child.y = tabBarHeight + 2; // for the border
6833 				child.width = width - 4; // for the border
6834 				child.height = height - tabBarHeight - 2 - 2; // for the border
6835 				child.recomputeChildLayout();
6836 			}
6837 		} else static assert(0);
6838 	}
6839 
6840 	version(custom_widgets)
6841 		string[] tabs;
6842 
6843 	this(Widget parent) {
6844 		super(parent);
6845 
6846 		tabStop = false;
6847 
6848 		version(win32_widgets) {
6849 			createWin32Window(this, WC_TABCONTROL, "", 0);
6850 		} else version(custom_widgets) {
6851 			addEventListener((ClickEvent event) {
6852 				if(event.target !is this && this.container !is null && event.target !is this.container) return;
6853 				if(event.clientY < tabBarHeight) {
6854 					auto t = (event.clientX / tabWidth);
6855 					if(t >= 0 && t < tabs.length) {
6856 						currentTab_ = t;
6857 						tabIndexClicked(t);
6858 						redraw();
6859 					}
6860 				}
6861 			});
6862 		} else static assert(0);
6863 
6864 		this.container = makeContainer();
6865 	}
6866 
6867 	override int marginTop() { return 4; }
6868 	override int paddingBottom() { return 4; }
6869 
6870 	override int minHeight() {
6871 		int max = 0;
6872 		foreach(child; children)
6873 			max = mymax(child.minHeight, max);
6874 
6875 
6876 		version(win32_widgets) {
6877 			RECT rect;
6878 			rect.right = this.width;
6879 			rect.bottom = max;
6880 			TabCtrl_AdjustRect(hwnd, true, &rect);
6881 
6882 			max = rect.bottom;
6883 		} else {
6884 			max += defaultLineHeight + 4;
6885 		}
6886 
6887 
6888 		return max;
6889 	}
6890 
6891 	version(win32_widgets)
6892 	override int handleWmNotify(NMHDR* hdr, int code, out int mustReturn) {
6893 		switch(code) {
6894 			case TCN_SELCHANGE:
6895 				auto sel = TabCtrl_GetCurSel(hwnd);
6896 				tabIndexClicked(sel);
6897 			break;
6898 			default:
6899 		}
6900 		return 0;
6901 	}
6902 
6903 	version(custom_widgets) {
6904 		private int currentTab_;
6905 		private int tabBarHeight() { return defaultLineHeight; }
6906 		int tabWidth() { return scaleWithDpi(80); }
6907 	}
6908 
6909 	version(win32_widgets)
6910 	override void paint(WidgetPainter painter) {}
6911 
6912 	version(custom_widgets)
6913 	override void paint(WidgetPainter painter) {
6914 		auto cs = getComputedStyle();
6915 
6916 		draw3dFrame(0, tabBarHeight - 2, width, height - tabBarHeight + 2, painter, FrameStyle.risen, cs.background.color);
6917 
6918 		int posX = 0;
6919 		foreach(idx, title; tabs) {
6920 			auto isCurrent = idx == getCurrentTab();
6921 
6922 			painter.setClipRectangle(Point(posX, 0), tabWidth, tabBarHeight);
6923 
6924 			draw3dFrame(posX, 0, tabWidth, tabBarHeight, painter, isCurrent ? FrameStyle.risen : FrameStyle.sunk, isCurrent ? cs.windowBackgroundColor : darken(cs.windowBackgroundColor, 0.1));
6925 			painter.outlineColor = cs.foregroundColor;
6926 			painter.drawText(Point(posX + 4, 2), title, Point(posX + tabWidth, tabBarHeight - 2), TextAlignment.VerticalCenter);
6927 
6928 			if(isCurrent) {
6929 				painter.outlineColor = cs.windowBackgroundColor;
6930 				painter.fillColor = Color.transparent;
6931 				painter.drawLine(Point(posX + 2, tabBarHeight - 1), Point(posX + tabWidth, tabBarHeight - 1));
6932 				painter.drawLine(Point(posX + 2, tabBarHeight - 2), Point(posX + tabWidth, tabBarHeight - 2));
6933 
6934 				painter.outlineColor = Color.white;
6935 				painter.drawPixel(Point(posX + 1, tabBarHeight - 1));
6936 				painter.drawPixel(Point(posX + 1, tabBarHeight - 2));
6937 				painter.outlineColor = cs.activeTabColor;
6938 				painter.drawPixel(Point(posX, tabBarHeight - 1));
6939 			}
6940 
6941 			posX += tabWidth - 2;
6942 		}
6943 	}
6944 
6945 	///
6946 	@scriptable
6947 	void setCurrentTab(int item) {
6948 		version(win32_widgets)
6949 			TabCtrl_SetCurSel(hwnd, item);
6950 		else version(custom_widgets)
6951 			currentTab_ = item;
6952 		else static assert(0);
6953 
6954 		tabIndexClicked(item);
6955 	}
6956 
6957 	///
6958 	@scriptable
6959 	int getCurrentTab() {
6960 		version(win32_widgets)
6961 			return TabCtrl_GetCurSel(hwnd);
6962 		else version(custom_widgets)
6963 			return currentTab_; // FIXME
6964 		else static assert(0);
6965 	}
6966 
6967 	///
6968 	@scriptable
6969 	void removeTab(int item) {
6970 		if(item && item == getCurrentTab())
6971 			setCurrentTab(item - 1);
6972 
6973 		version(win32_widgets) {
6974 			TabCtrl_DeleteItem(hwnd, item);
6975 		}
6976 
6977 		for(int a = item; a < children.length - 1; a++)
6978 			this._children[a] = this._children[a + 1];
6979 		this._children = this._children[0 .. $-1];
6980 	}
6981 
6982 }
6983 
6984 
6985 /++
6986 	A tab widget is a set of clickable tab buttons followed by a content area.
6987 
6988 
6989 	Tabs can change existing content or can be new pages.
6990 
6991 	When the user picks a different tab, a `change` message is generated.
6992 +/
6993 class TabWidget : TabMessageWidget {
6994 	this(Widget parent) {
6995 		super(parent);
6996 	}
6997 
6998 	override protected Widget makeContainer() {
6999 		return null;
7000 	}
7001 
7002 	override void addChild(Widget child, int pos = int.max) {
7003 		if(auto twp = cast(TabWidgetPage) child) {
7004 			Widget.addChild(child, pos);
7005 			if(pos == int.max)
7006 				pos = cast(int) this.children.length - 1;
7007 
7008 			super.addTab(twp.title, pos); // need to bypass the override here which would get into a loop...
7009 
7010 			if(pos != getCurrentTab) {
7011 				child.showing = false;
7012 			}
7013 		} else {
7014 			assert(0, "Don't add children directly to a tab widget, instead add them to a page (see addPage)");
7015 		}
7016 	}
7017 
7018 	// FIXME: add tab icons at some point, Windows supports them
7019 	/++
7020 		Adds a page and its associated tab with the given label to the widget.
7021 
7022 		Returns:
7023 			The added page object, to which you can add other widgets.
7024 	+/
7025 	@scriptable
7026 	TabWidgetPage addPage(string title) {
7027 		return new TabWidgetPage(title, this);
7028 	}
7029 
7030 	/++
7031 		Gets the page at the given tab index, or `null` if the index is bad.
7032 
7033 		History:
7034 			Added December 24, 2021.
7035 	+/
7036 	TabWidgetPage getPage(int index) {
7037 		if(index < this.children.length)
7038 			return null;
7039 		return cast(TabWidgetPage) this.children[index];
7040 	}
7041 
7042 	/++
7043 		While you can still use the addTab from the parent class,
7044 		*strongly* recommend you use [addPage] insteaad.
7045 
7046 		History:
7047 			Added December 24, 2021 to fulful the interface
7048 			requirement that came from adding [TabMessageWidget].
7049 
7050 			You should not use it though since the [addPage] function
7051 			is much easier to use here.
7052 	+/
7053 	override int addTab(string title, int pos = int.max) {
7054 		auto p = addPage(title);
7055 		foreach(idx, child; this.children)
7056 			if(child is p)
7057 				return cast(int) idx;
7058 		return -1;
7059 	}
7060 
7061 	protected override void tabIndexClicked(int item) {
7062 		foreach(idx, child; children) {
7063 			child.showing(false, false); // batch the recalculates for the end
7064 		}
7065 
7066 		foreach(idx, child; children) {
7067 			if(idx == item) {
7068 				child.showing(true, false);
7069 				if(parentWindow) {
7070 					auto f = parentWindow.getFirstFocusable(child);
7071 					if(f)
7072 						f.focus();
7073 				}
7074 				recomputeChildLayout();
7075 			}
7076 		}
7077 
7078 		version(win32_widgets) {
7079 			InvalidateRect(hwnd, null, true);
7080 		} else version(custom_widgets) {
7081 			this.redraw();
7082 		}
7083 	}
7084 
7085 }
7086 
7087 /++
7088 	A page widget is basically a tab widget with hidden tabs. It is also sometimes called a "StackWidget".
7089 
7090 	You add [TabWidgetPage]s to it.
7091 +/
7092 class PageWidget : Widget {
7093 	this(Widget parent) {
7094 		super(parent);
7095 	}
7096 
7097 	override int minHeight() {
7098 		int max = 0;
7099 		foreach(child; children)
7100 			max = mymax(child.minHeight, max);
7101 
7102 		return max;
7103 	}
7104 
7105 
7106 	override void addChild(Widget child, int pos = int.max) {
7107 		if(auto twp = cast(TabWidgetPage) child) {
7108 			super.addChild(child, pos);
7109 			if(pos == int.max)
7110 				pos = cast(int) this.children.length - 1;
7111 
7112 			if(pos != getCurrentTab) {
7113 				child.showing = false;
7114 			}
7115 		} else {
7116 			assert(0, "Don't add children directly to a page widget, instead add them to a page (see addPage)");
7117 		}
7118 	}
7119 
7120 	override void recomputeChildLayout() {
7121 		this.registerMovement();
7122 		foreach(child; children) {
7123 			child.x = 0;
7124 			child.y = 0;
7125 			child.width = width;
7126 			child.height = height;
7127 			child.recomputeChildLayout();
7128 		}
7129 	}
7130 
7131 	private int currentTab_;
7132 
7133 	///
7134 	@scriptable
7135 	void setCurrentTab(int item) {
7136 		currentTab_ = item;
7137 
7138 		showOnly(item);
7139 	}
7140 
7141 	///
7142 	@scriptable
7143 	int getCurrentTab() {
7144 		return currentTab_;
7145 	}
7146 
7147 	///
7148 	@scriptable
7149 	void removeTab(int item) {
7150 		if(item && item == getCurrentTab())
7151 			setCurrentTab(item - 1);
7152 
7153 		for(int a = item; a < children.length - 1; a++)
7154 			this._children[a] = this._children[a + 1];
7155 		this._children = this._children[0 .. $-1];
7156 	}
7157 
7158 	///
7159 	@scriptable
7160 	TabWidgetPage addPage(string title) {
7161 		return new TabWidgetPage(title, this);
7162 	}
7163 
7164 	private void showOnly(int item) {
7165 		foreach(idx, child; children)
7166 			if(idx == item) {
7167 				child.show();
7168 				child.recomputeChildLayout();
7169 			} else {
7170 				child.hide();
7171 			}
7172 	}
7173 
7174 }
7175 
7176 /++
7177 
7178 +/
7179 class TabWidgetPage : Widget {
7180 	string title;
7181 	this(string title, Widget parent) {
7182 		this.title = title;
7183 		this.tabStop = false;
7184 		super(parent);
7185 
7186 		///*
7187 		version(win32_widgets) {
7188 			createWin32Window(this, Win32Class!"arsd_minigui_TabWidgetPage"w, "", 0);
7189 		}
7190 		//*/
7191 	}
7192 
7193 	override int minHeight() {
7194 		int sum = 0;
7195 		foreach(child; children)
7196 			sum += child.minHeight();
7197 		return sum;
7198 	}
7199 }
7200 
7201 version(none)
7202 /++
7203 	A collapsable sidebar is a container that shows if its assigned width is greater than its minimum and otherwise shows as a button.
7204 
7205 	I think I need to modify the layout algorithms to support this.
7206 +/
7207 class CollapsableSidebar : Widget {
7208 
7209 }
7210 
7211 /// Stacks the widgets vertically, taking all the available width for each child.
7212 class VerticalLayout : Layout {
7213 	// most of this is intentionally blank - widget's default is vertical layout right now
7214 	///
7215 	this(Widget parent) { super(parent); }
7216 
7217 	/++
7218 		Sets a max width for the layout so you don't have to subclass. The max width
7219 		is in device-independent pixels, meaning pixels at 96 dpi that are auto-scaled.
7220 
7221 		History:
7222 			Added November 29, 2021 (dub v10.5)
7223 	+/
7224 	this(int maxWidth, Widget parent) {
7225 		this.mw = maxWidth;
7226 		super(parent);
7227 	}
7228 
7229 	private int mw = int.max;
7230 
7231 	override int maxWidth() { return scaleWithDpi(mw); }
7232 }
7233 
7234 /// Stacks the widgets horizontally, taking all the available height for each child.
7235 class HorizontalLayout : Layout {
7236 	///
7237 	this(Widget parent) { super(parent); }
7238 
7239 	/++
7240 		Sets a max height for the layout so you don't have to subclass. The max height
7241 		is in device-independent pixels, meaning pixels at 96 dpi that are auto-scaled.
7242 
7243 		History:
7244 			Added November 29, 2021 (dub v10.5)
7245 	+/
7246 	this(int maxHeight, Widget parent) {
7247 		this.mh = maxHeight;
7248 		super(parent);
7249 	}
7250 
7251 	private int mh = 0;
7252 
7253 
7254 
7255 	override void recomputeChildLayout() {
7256 		.recomputeChildLayout!"width"(this);
7257 	}
7258 
7259 	override int minHeight() {
7260 		int largest = 0;
7261 		int margins = 0;
7262 		int lastMargin = 0;
7263 		foreach(child; children) {
7264 			auto mh = child.minHeight();
7265 			if(mh > largest)
7266 				largest = mh;
7267 			margins += mymax(lastMargin, child.marginTop());
7268 			lastMargin = child.marginBottom();
7269 		}
7270 		return largest + margins;
7271 	}
7272 
7273 	override int maxHeight() {
7274 		if(mh != 0)
7275 			return mymax(minHeight, scaleWithDpi(mh));
7276 
7277 		int largest = 0;
7278 		int margins = 0;
7279 		int lastMargin = 0;
7280 		foreach(child; children) {
7281 			auto mh = child.maxHeight();
7282 			if(mh == int.max)
7283 				return int.max;
7284 			if(mh > largest)
7285 				largest = mh;
7286 			margins += mymax(lastMargin, child.marginTop());
7287 			lastMargin = child.marginBottom();
7288 		}
7289 		return largest + margins;
7290 	}
7291 
7292 	override int heightStretchiness() {
7293 		int max;
7294 		foreach(child; children) {
7295 			auto c = child.heightStretchiness;
7296 			if(c > max)
7297 				max = c;
7298 		}
7299 		return max;
7300 	}
7301 
7302 }
7303 
7304 version(win32_widgets)
7305 private
7306 extern(Windows)
7307 LRESULT DoubleBufferWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) nothrow {
7308 	Widget* pwin = hwnd in Widget.nativeMapping;
7309 	if(pwin is null)
7310 		return DefWindowProc(hwnd, message, wparam, lparam);
7311 	SimpleWindow win = pwin.simpleWindowWrappingHwnd;
7312 	if(win is null)
7313 		return DefWindowProc(hwnd, message, wparam, lparam);
7314 
7315 	switch(message) {
7316 		case WM_SIZE:
7317 			auto width = LOWORD(lparam);
7318 			auto height = HIWORD(lparam);
7319 
7320 			auto hdc = GetDC(hwnd);
7321 			auto hdcBmp = CreateCompatibleDC(hdc);
7322 
7323 			// FIXME: could this be more efficient? it never relinquishes a large bitmap
7324 			if(width > win.bmpWidth || height > win.bmpHeight) {
7325 				auto oldBuffer = win.buffer;
7326 				win.buffer = CreateCompatibleBitmap(hdc, width, height);
7327 
7328 				if(oldBuffer)
7329 					DeleteObject(oldBuffer);
7330 
7331 				win.bmpWidth = width;
7332 				win.bmpHeight = height;
7333 			}
7334 
7335 			// just always erase it upon resizing so minigui can draw over with a clean slate
7336 			auto oldBmp = SelectObject(hdcBmp, win.buffer);
7337 
7338 			auto brush = GetSysColorBrush(COLOR_3DFACE);
7339 			RECT r;
7340 			r.left = 0;
7341 			r.top = 0;
7342 			r.right = width;
7343 			r.bottom = height;
7344 			FillRect(hdcBmp, &r, brush);
7345 
7346 			SelectObject(hdcBmp, oldBmp);
7347 			DeleteDC(hdcBmp);
7348 			ReleaseDC(hwnd, hdc);
7349 		break;
7350 		case WM_PAINT:
7351 			if(win.buffer is null)
7352 				goto default;
7353 
7354 			BITMAP bm;
7355 			PAINTSTRUCT ps;
7356 
7357 			HDC hdc = BeginPaint(hwnd, &ps);
7358 
7359 			HDC hdcMem = CreateCompatibleDC(hdc);
7360 			HBITMAP hbmOld = SelectObject(hdcMem, win.buffer);
7361 
7362 			GetObject(win.buffer, bm.sizeof, &bm);
7363 
7364 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
7365 
7366 			SelectObject(hdcMem, hbmOld);
7367 			DeleteDC(hdcMem);
7368 			EndPaint(hwnd, &ps);
7369 		break;
7370 		default:
7371 			return DefWindowProc(hwnd, message, wparam, lparam);
7372 	}
7373 
7374 	return 0;
7375 }
7376 
7377 private wstring Win32Class(wstring name)() {
7378 	static bool classRegistered;
7379 	if(!classRegistered) {
7380 		HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
7381 		WNDCLASSEX wc;
7382 		wc.cbSize = wc.sizeof;
7383 		wc.hInstance = hInstance;
7384 		wc.hbrBackground = cast(HBRUSH) (COLOR_3DFACE+1); // GetStockObject(WHITE_BRUSH);
7385 		wc.lpfnWndProc = &DoubleBufferWndProc;
7386 		wc.lpszClassName = name.ptr;
7387 		if(!RegisterClassExW(&wc))
7388 			throw new Exception("RegisterClass ");// ~ to!string(GetLastError()));
7389 		classRegistered = true;
7390 	}
7391 
7392 		return name;
7393 }
7394 
7395 /+
7396 version(win32_widgets)
7397 extern(Windows)
7398 private
7399 LRESULT CustomDrawWindowProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
7400 	switch(iMessage) {
7401 		case WM_PAINT:
7402 			if(auto te = hWnd in Widget.nativeMapping) {
7403 				try {
7404 					//te.redraw();
7405 					writeln(te, " drawing");
7406 				} catch(Exception) {}
7407 			}
7408 			return DefWindowProc(hWnd, iMessage, wParam, lParam);
7409 		default:
7410 			return DefWindowProc(hWnd, iMessage, wParam, lParam);
7411 	}
7412 }
7413 +/
7414 
7415 
7416 /++
7417 	A widget specifically designed to hold other widgets.
7418 
7419 	History:
7420 		Added July 1, 2021
7421 +/
7422 class ContainerWidget : Widget {
7423 	this(Widget parent) {
7424 		super(parent);
7425 		this.tabStop = false;
7426 
7427 		version(win32_widgets) {
7428 			createWin32Window(this, Win32Class!"arsd_minigui_ContainerWidget"w, "", 0);
7429 		}
7430 	}
7431 }
7432 
7433 /++
7434 	A widget that takes your widget, puts scroll bars around it, and sends
7435 	messages to it when the user scrolls. Unlike [ScrollableWidget], it makes
7436 	no effort to automatically scroll or clip its child widgets - it just sends
7437 	the messages.
7438 
7439 
7440 	A ScrollMessageWidget notifies you with a [ScrollEvent] that it has changed.
7441 	The scroll coordinates are all given in a unit you interpret as you wish. One
7442 	of these units is moved on each press of the arrow buttons and represents the
7443 	smallest amount the user can scroll. The intention is for this to be one line,
7444 	one item in a list, one row in a table, etc. Whatever makes sense for your widget
7445 	in each direction that the user might be interested in.
7446 
7447 	You can set a "page size" with the [step] property. (Yes, I regret the name...)
7448 	This is the amount it jumps when the user pressed page up and page down, or clicks
7449 	in the exposed part of the scroll bar.
7450 
7451 	You should add child content to the ScrollMessageWidget. However, it is important to
7452 	note that the coordinates are always independent of the scroll position! It is YOUR
7453 	responsibility to do any necessary transforms, clipping, etc., while drawing the
7454 	content and interpreting mouse events if they are supposed to change with the scroll.
7455 	This is in contrast to the (likely to be deprecated) [ScrollableWidget], which tries
7456 	to maintain the illusion that there's an infinite space. The [ScrollMessageWidget] gives
7457 	you more control (which can be considerably more efficient and adapted to your actual data)
7458 	at the expense of you also needing to be aware of its reality.
7459 
7460 	Please note that it does NOT react to mouse wheel events or various keyboard events as of
7461 	version 10.3. Maybe this will change in the future.... but for now you must call
7462 	[addDefaultKeyboardListeners] and/or [addDefaultWheelListeners] or set something up yourself.
7463 +/
7464 class ScrollMessageWidget : Widget {
7465 	this(Widget parent) {
7466 		super(parent);
7467 
7468 		container = new Widget(this);
7469 		hsb = new HorizontalScrollbar(this);
7470 		vsb = new VerticalScrollbar(this);
7471 
7472 		hsb.addEventListener("scrolltonextline", {
7473 			hsb.setPosition(hsb.position + movementPerButtonClickH_);
7474 			notify();
7475 		});
7476 		hsb.addEventListener("scrolltopreviousline", {
7477 			hsb.setPosition(hsb.position - movementPerButtonClickH_);
7478 			notify();
7479 		});
7480 		vsb.addEventListener("scrolltonextline", {
7481 			vsb.setPosition(vsb.position + movementPerButtonClickV_);
7482 			notify();
7483 		});
7484 		vsb.addEventListener("scrolltopreviousline", {
7485 			vsb.setPosition(vsb.position - movementPerButtonClickV_);
7486 			notify();
7487 		});
7488 		hsb.addEventListener("scrolltonextpage", {
7489 			hsb.setPosition(hsb.position + hsb.step_);
7490 			notify();
7491 		});
7492 		hsb.addEventListener("scrolltopreviouspage", {
7493 			hsb.setPosition(hsb.position - hsb.step_);
7494 			notify();
7495 		});
7496 		vsb.addEventListener("scrolltonextpage", {
7497 			vsb.setPosition(vsb.position + vsb.step_);
7498 			notify();
7499 		});
7500 		vsb.addEventListener("scrolltopreviouspage", {
7501 			vsb.setPosition(vsb.position - vsb.step_);
7502 			notify();
7503 		});
7504 		hsb.addEventListener("scrolltoposition", (Event event) {
7505 			hsb.setPosition(event.intValue);
7506 			notify();
7507 		});
7508 		vsb.addEventListener("scrolltoposition", (Event event) {
7509 			vsb.setPosition(event.intValue);
7510 			notify();
7511 		});
7512 
7513 
7514 		tabStop = false;
7515 		container.tabStop = false;
7516 		magic = true;
7517 	}
7518 
7519 	private int movementPerButtonClickH_ = 1;
7520 	private int movementPerButtonClickV_ = 1;
7521 	public void movementPerButtonClick(int h, int v) {
7522 		movementPerButtonClickH_ = h;
7523 		movementPerButtonClickV_ = v;
7524 	}
7525 
7526 	/++
7527 		Add default event listeners for keyboard and mouse wheel scrolling shortcuts.
7528 
7529 
7530 		The defaults for [addDefaultWheelListeners] are:
7531 
7532 			$(LIST
7533 				* Mouse wheel scrolls vertically
7534 				* Alt key + mouse wheel scrolls horiontally
7535 				* Shift + mouse wheel scrolls faster.
7536 				* Any mouse click or wheel event will focus the inner widget if it has `tabStop = true`
7537 			)
7538 
7539 		The defaults for [addDefaultKeyboardListeners] are:
7540 
7541 			$(LIST
7542 				* Arrow keys scroll by the given amounts
7543 				* Shift+arrow keys scroll by the given amounts times the given shiftMultiplier
7544 				* Page up and down scroll by the vertical viewable area
7545 				* Home and end scroll to the start and end of the verticle viewable area.
7546 				* Alt + page up / page down / home / end will horizonally scroll instead of vertical.
7547 			)
7548 
7549 		My recommendation is to change the scroll amounts if you are scrolling by pixels, but otherwise keep them at one line.
7550 
7551 		Params:
7552 			horizontalArrowScrollAmount =
7553 			verticalArrowScrollAmount =
7554 			verticalWheelScrollAmount = how much should be scrolled vertically on each tick of the mouse wheel
7555 			horizontalWheelScrollAmount = how much should be scrolled horizontally when alt is held on each tick of the mouse wheel
7556 			shiftMultiplier = multiplies the scroll amount by this when shift is held
7557 	+/
7558 	void addDefaultKeyboardListeners(int verticalArrowScrollAmount = 1, int horizontalArrowScrollAmount = 1, int shiftMultiplier = 3) {
7559 		auto _this = this;
7560 
7561 		container.addEventListener((scope KeyDownEvent ke) {
7562 			switch(ke.key) {
7563 				case Key.Left:
7564 					_this.scrollLeft(horizontalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7565 				break;
7566 				case Key.Right:
7567 					_this.scrollRight(horizontalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7568 				break;
7569 				case Key.Up:
7570 					_this.scrollUp(verticalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7571 				break;
7572 				case Key.Down:
7573 					_this.scrollDown(verticalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7574 				break;
7575 				case Key.PageUp:
7576 					if(ke.altKey)
7577 						_this.scrollLeft(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7578 					else
7579 						_this.scrollUp(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7580 				break;
7581 				case Key.PageDown:
7582 					if(ke.altKey)
7583 						_this.scrollRight(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7584 					else
7585 						_this.scrollDown(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7586 				break;
7587 				case Key.Home:
7588 					if(ke.altKey)
7589 						_this.scrollLeft(short.max * 16);
7590 					else
7591 						_this.scrollUp(short.max * 16);
7592 				break;
7593 				case Key.End:
7594 					if(ke.altKey)
7595 						_this.scrollRight(short.max * 16);
7596 					else
7597 						_this.scrollDown(short.max * 16);
7598 				break;
7599 
7600 				default:
7601 					// ignore, not for us.
7602 			}
7603 
7604 		});
7605 	}
7606 
7607 	/// ditto
7608 	void addDefaultWheelListeners(int verticalWheelScrollAmount = 1, int horizontalWheelScrollAmount = 1, int shiftMultiplier = 3) {
7609 		auto _this = this;
7610 		container.addEventListener((scope ClickEvent ce) {
7611 
7612 			if(ce.target && ce.target.tabStop)
7613 				ce.target.focus();
7614 
7615 			// ctrl is reserved for the application
7616 			if(ce.ctrlKey)
7617 				return;
7618 
7619 			if(horizontalWheelScrollAmount == 0 && ce.altKey)
7620 				return;
7621 
7622 			if(shiftMultiplier == 0 && ce.shiftKey)
7623 				return;
7624 
7625 			if(ce.button == MouseButton.wheelDown) {
7626 				if(ce.altKey)
7627 					_this.scrollRight(horizontalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7628 				else
7629 					_this.scrollDown(verticalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7630 			} else if(ce.button == MouseButton.wheelUp) {
7631 				if(ce.altKey)
7632 					_this.scrollLeft(horizontalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7633 				else
7634 					_this.scrollUp(verticalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7635 			}
7636 		});
7637 	}
7638 
7639 	/++
7640 		Scrolls the given amount.
7641 
7642 		History:
7643 			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.
7644 	+/
7645 	void scrollUp(int amount = 1) {
7646 		vsb.setPosition(vsb.position - amount);
7647 		notify();
7648 	}
7649 	/// ditto
7650 	void scrollDown(int amount = 1) {
7651 		vsb.setPosition(vsb.position + amount);
7652 		notify();
7653 	}
7654 	/// ditto
7655 	void scrollLeft(int amount = 1) {
7656 		hsb.setPosition(hsb.position - amount);
7657 		notify();
7658 	}
7659 	/// ditto
7660 	void scrollRight(int amount = 1) {
7661 		hsb.setPosition(hsb.position + amount);
7662 		notify();
7663 	}
7664 
7665 	///
7666 	VerticalScrollbar verticalScrollBar() { return vsb; }
7667 	///
7668 	HorizontalScrollbar horizontalScrollBar() { return hsb; }
7669 
7670 	void notify() {
7671 		static bool insideNotify;
7672 
7673 		if(insideNotify)
7674 			return; // avoid the recursive call, even if it isn't strictly correct
7675 
7676 		insideNotify = true;
7677 		scope(exit) insideNotify = false;
7678 
7679 		this.emit!ScrollEvent();
7680 	}
7681 
7682 	mixin Emits!ScrollEvent;
7683 
7684 	///
7685 	Point position() {
7686 		return Point(hsb.position, vsb.position);
7687 	}
7688 
7689 	///
7690 	void setPosition(int x, int y) {
7691 		hsb.setPosition(x);
7692 		vsb.setPosition(y);
7693 	}
7694 
7695 	///
7696 	void setPageSize(int unitsX, int unitsY) {
7697 		hsb.setStep(unitsX);
7698 		vsb.setStep(unitsY);
7699 	}
7700 
7701 	/// Always call this BEFORE setViewableArea
7702 	void setTotalArea(int width, int height) {
7703 		hsb.setMax(width);
7704 		vsb.setMax(height);
7705 	}
7706 
7707 	/++
7708 		Always set the viewable area AFTER setitng the total area if you are going to change both.
7709 		NEVER call this from inside a scroll event. This includes through recomputeChildLayout.
7710 		If you need to do that, use [queueRecomputeChildLayout].
7711 	+/
7712 	void setViewableArea(int width, int height) {
7713 
7714 		// actually there IS A need to dothis cuz the max might have changed since then
7715 		//if(width == hsb.viewableArea_ && height == vsb.viewableArea_)
7716 			//return; // no need to do what is already done
7717 		hsb.setViewableArea(width);
7718 		vsb.setViewableArea(height);
7719 
7720 		bool needsNotify = false;
7721 
7722 		// FIXME: if at any point the rhs is outside the scrollbar, we need
7723 		// to reset to 0. but it should remember the old position in case the
7724 		// window resizes again, so it can kinda return ot where it was.
7725 		//
7726 		// so there's an inner position and a exposed position. the exposed one is always in bounds and thus may be (0,0)
7727 		if(width >= hsb.max) {
7728 			// there's plenty of room to display it all so we need to reset to zero
7729 			// FIXME: adjust so it matches the note above
7730 			hsb.setPosition(0);
7731 			needsNotify = true;
7732 		}
7733 		if(height >= vsb.max) {
7734 			// there's plenty of room to display it all so we need to reset to zero
7735 			// FIXME: adjust so it matches the note above
7736 			vsb.setPosition(0);
7737 			needsNotify = true;
7738 		}
7739 		if(needsNotify)
7740 			notify();
7741 	}
7742 
7743 	private bool magic;
7744 	override void addChild(Widget w, int position = int.max) {
7745 		if(magic)
7746 			container.addChild(w, position);
7747 		else
7748 			super.addChild(w, position);
7749 	}
7750 
7751 	override void recomputeChildLayout() {
7752 		if(hsb is null || vsb is null || container is null) return;
7753 
7754 		registerMovement();
7755 
7756 		enum BUTTON_SIZE = 16;
7757 
7758 		hsb.height = scaleWithDpi(BUTTON_SIZE); // FIXME? are tese 16s sane?
7759 		hsb.x = 0;
7760 		hsb.y = this.height - hsb.height;
7761 
7762 		vsb.width = scaleWithDpi(BUTTON_SIZE); // FIXME?
7763 		vsb.x = this.width - vsb.width;
7764 		vsb.y = 0;
7765 
7766 		auto vsb_width = vsb.showing ? vsb.width : 0;
7767 		auto hsb_height = hsb.showing ? hsb.height : 0;
7768 
7769 		hsb.width = this.width - vsb_width;
7770 		vsb.height = this.height - hsb_height;
7771 
7772 		hsb.recomputeChildLayout();
7773 		vsb.recomputeChildLayout();
7774 
7775 		if(this.header is null) {
7776 			container.x = 0;
7777 			container.y = 0;
7778 			container.width = this.width - vsb_width;
7779 			container.height = this.height - hsb_height;
7780 			container.recomputeChildLayout();
7781 		} else {
7782 			header.x = 0;
7783 			header.y = 0;
7784 			header.width = this.width - vsb_width;
7785 			header.height = scaleWithDpi(BUTTON_SIZE); // size of the button
7786 			header.recomputeChildLayout();
7787 
7788 			container.x = 0;
7789 			container.y = scaleWithDpi(BUTTON_SIZE);
7790 			container.width = this.width - vsb_width;
7791 			container.height = this.height - hsb_height - scaleWithDpi(BUTTON_SIZE);
7792 			container.recomputeChildLayout();
7793 		}
7794 	}
7795 
7796 	private HorizontalScrollbar hsb;
7797 	private VerticalScrollbar vsb;
7798 	Widget container;
7799 	private Widget header;
7800 
7801 	/++
7802 		Adds a fixed-size "header" widget. This will be positioned to align with the scroll up button.
7803 
7804 		History:
7805 			Added September 27, 2021 (dub v10.3)
7806 	+/
7807 	Widget getHeader() {
7808 		if(this.header is null) {
7809 			magic = false;
7810 			scope(exit) magic = true;
7811 			this.header = new Widget(this);
7812 			recomputeChildLayout();
7813 		}
7814 		return this.header;
7815 	}
7816 
7817 	/++
7818 		Makes an effort to ensure as much of `rect` is visible as possible, scrolling if necessary.
7819 
7820 		History:
7821 			Added January 3, 2023 (dub v11.0)
7822 	+/
7823 	void scrollIntoView(Rectangle rect) {
7824 		Rectangle viewRectangle = Rectangle(position, Size(hsb.viewableArea_, vsb.viewableArea_));
7825 
7826 		// import std.stdio;writeln(viewRectangle, "\n", rect, " ", viewRectangle.contains(rect.lowerRight - Point(1, 1)));
7827 
7828 		// the lower right is exclusive normally
7829 		auto test = rect.lowerRight;
7830 		if(test.x > 0) test.x--;
7831 		if(test.y > 0) test.y--;
7832 
7833 		if(!viewRectangle.contains(test) || !viewRectangle.contains(rect.upperLeft)) {
7834 			// try to scroll only one dimension at a time if we can
7835 			if(!viewRectangle.contains(Point(test.x, position.y)) || !viewRectangle.contains(Point(rect.upperLeft.x, position.y)))
7836 				setPosition(rect.upperLeft.x, position.y);
7837 			if(!viewRectangle.contains(Point(position.x, test.y)) || !viewRectangle.contains(Point(position.x, rect.upperLeft.y)))
7838 				setPosition(position.x, rect.upperLeft.y);
7839 		}
7840 
7841 	}
7842 
7843 	override int minHeight() {
7844 		int min = mymax(container ? container.minHeight : 0, (verticalScrollBar.showing ? verticalScrollBar.minHeight : 0));
7845 		if(header !is null)
7846 			min += header.minHeight;
7847 		if(horizontalScrollBar.showing)
7848 			min += horizontalScrollBar.minHeight;
7849 		return min;
7850 	}
7851 
7852 	override int maxHeight() {
7853 		int max = container ? container.maxHeight : int.max;
7854 		if(max == int.max)
7855 			return max;
7856 		if(horizontalScrollBar.showing)
7857 			max += horizontalScrollBar.minHeight;
7858 		return max;
7859 	}
7860 }
7861 
7862 /++
7863 	$(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")
7864 	$(IMG //arsdnet.net/minigui-screenshots/linux/ScrollMessageWidget.png, Same thing, but in the default Linux theme.)
7865 +/
7866 version(minigui_screenshots)
7867 @Screenshot("ScrollMessageWidget")
7868 unittest {
7869 	auto window = new Window("ScrollMessageWidget");
7870 
7871 	auto smw = new ScrollMessageWidget(window);
7872 	smw.addDefaultKeyboardListeners();
7873 	smw.addDefaultWheelListeners();
7874 
7875 	window.loop();
7876 }
7877 
7878 /++
7879 	Bypasses automatic layout for its children, using manual positioning and sizing only.
7880 	While you need to manually position them, you must ensure they are inside the StaticLayout's
7881 	bounding box to avoid undefined behavior.
7882 
7883 	You should almost never use this.
7884 +/
7885 class StaticLayout : Layout {
7886 	///
7887 	this(Widget parent) { super(parent); }
7888 	override void recomputeChildLayout() {
7889 		registerMovement();
7890 		foreach(child; children)
7891 			child.recomputeChildLayout();
7892 	}
7893 }
7894 
7895 /++
7896 	Bypasses automatic positioning when being laid out. It is your responsibility to make
7897 	room for this widget in the parent layout.
7898 
7899 	Its children are laid out normally, unless there is exactly one, in which case it takes
7900 	on the full size of the `StaticPosition` object (if you plan to put stuff on the edge, you
7901 	can do that with `padding`).
7902 +/
7903 class StaticPosition : Layout {
7904 	///
7905 	this(Widget parent) { super(parent); }
7906 
7907 	override void recomputeChildLayout() {
7908 		registerMovement();
7909 		if(this.children.length == 1) {
7910 			auto child = children[0];
7911 			child.x = 0;
7912 			child.y = 0;
7913 			child.width = this.width;
7914 			child.height = this.height;
7915 			child.recomputeChildLayout();
7916 		} else
7917 		foreach(child; children)
7918 			child.recomputeChildLayout();
7919 	}
7920 
7921 	alias width = typeof(super).width;
7922 	alias height = typeof(super).height;
7923 
7924 	@property int width(int w) @nogc pure @safe nothrow {
7925 		return this._width = w;
7926 	}
7927 
7928 	@property int height(int w) @nogc pure @safe nothrow {
7929 		return this._height = w;
7930 	}
7931 
7932 }
7933 
7934 /++
7935 	FixedPosition is like [StaticPosition], but its coordinates
7936 	are always relative to the viewport, meaning they do not scroll with
7937 	the parent content.
7938 +/
7939 class FixedPosition : StaticPosition {
7940 	///
7941 	this(Widget parent) { super(parent); }
7942 }
7943 
7944 version(win32_widgets)
7945 int processWmCommand(HWND parentWindow, HWND handle, ushort cmd, ushort idm) {
7946 	if(true) {
7947 		// cmd == 0 = menu, cmd == 1 = accelerator
7948 		if(auto item = idm in Action.mapping) {
7949 			foreach(handler; (*item).triggered)
7950 				handler();
7951 		/*
7952 			auto event = new Event("triggered", *item);
7953 			event.button = idm;
7954 			event.dispatch();
7955 		*/
7956 			return 0;
7957 		}
7958 	}
7959 	if(handle)
7960 	if(auto widgetp = handle in Widget.nativeMapping) {
7961 		(*widgetp).handleWmCommand(cmd, idm);
7962 		return 0;
7963 	}
7964 	return 1;
7965 }
7966 
7967 
7968 ///
7969 class Window : Widget {
7970 	int mouseCaptureCount = 0;
7971 	Widget mouseCapturedBy;
7972 	void captureMouse(Widget byWhom) {
7973 		assert(mouseCapturedBy is null || byWhom is mouseCapturedBy);
7974 		mouseCaptureCount++;
7975 		mouseCapturedBy = byWhom;
7976 		win.grabInput();
7977 	}
7978 	void releaseMouseCapture() {
7979 		mouseCaptureCount--;
7980 		mouseCapturedBy = null;
7981 		win.releaseInputGrab();
7982 	}
7983 
7984 	/++
7985 		Sets the window icon which is often seen in title bars and taskbars.
7986 
7987 		History:
7988 			Added April 5, 2022 (dub v10.8)
7989 	+/
7990 	@property void icon(MemoryImage icon) {
7991 		if(win && icon)
7992 			win.icon = icon;
7993 	}
7994 
7995 	///
7996 	@scriptable
7997 	@property bool focused() {
7998 		return win.focused;
7999 	}
8000 
8001 	static class Style : Widget.Style {
8002 		override WidgetBackground background() {
8003 			version(custom_widgets)
8004 				return WidgetBackground(WidgetPainter.visualTheme.windowBackgroundColor);
8005 			else version(win32_widgets)
8006 				return WidgetBackground(Color.transparent);
8007 			else static assert(0);
8008 		}
8009 	}
8010 	mixin OverrideStyle!Style;
8011 
8012 	/++
8013 		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.
8014 	+/
8015 	deprecated("Use the non-static Widget.defaultLineHeight() instead") static int lineHeight() {
8016 		return lineHeightNotDeprecatedButShouldBeSinceItIsJustAFallback();
8017 	}
8018 
8019 	private static int lineHeightNotDeprecatedButShouldBeSinceItIsJustAFallback() {
8020 		OperatingSystemFont font;
8021 		if(auto vt = WidgetPainter.visualTheme) {
8022 			font = vt.defaultFontCached(96); // FIXME
8023 		}
8024 
8025 		if(font is null) {
8026 			static int defaultHeightCache;
8027 			if(defaultHeightCache == 0) {
8028 				font = new OperatingSystemFont;
8029 				font.loadDefault;
8030 				defaultHeightCache = font.height();// * 5 / 4;
8031 			}
8032 			return defaultHeightCache;
8033 		}
8034 
8035 		return font.height();// * 5 / 4;
8036 	}
8037 
8038 	Widget focusedWidget;
8039 
8040 	private SimpleWindow win_;
8041 
8042 	@property {
8043 		/++
8044 			Provides access to the underlying [SimpleWindow]. Note that changing properties on this window may disconnect minigui's event dispatchers.
8045 
8046 			History:
8047 				Prior to June 21, 2021, it was a public (but undocumented) member. Now it a semi-protected property.
8048 		+/
8049 		public SimpleWindow win() {
8050 			return win_;
8051 		}
8052 		///
8053 		protected void win(SimpleWindow w) {
8054 			win_ = w;
8055 		}
8056 	}
8057 
8058 	/// YOU ALMOST CERTAINLY SHOULD NOT USE THIS. This is really only for special purposes like pseudowindows or popup windows doing their own thing.
8059 	this(Widget p) {
8060 		tabStop = false;
8061 		super(p);
8062 	}
8063 
8064 	private void actualRedraw() {
8065 		if(recomputeChildLayoutRequired)
8066 			recomputeChildLayoutEntry();
8067 		if(!showing) return;
8068 
8069 		assert(parentWindow !is null);
8070 
8071 		auto w = drawableWindow;
8072 		if(w is null)
8073 			w = parentWindow.win;
8074 
8075 		if(w.closed())
8076 			return;
8077 
8078 		auto ugh = this.parent;
8079 		int lox, loy;
8080 		while(ugh) {
8081 			lox += ugh.x;
8082 			loy += ugh.y;
8083 			ugh = ugh.parent;
8084 		}
8085 		auto painter = w.draw(true);
8086 		privatePaint(WidgetPainter(painter, this), lox, loy, Rectangle(0, 0, int.max, int.max), false, willDraw());
8087 		// RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
8088 	}
8089 
8090 
8091 	private bool skipNextChar = false;
8092 
8093 	/++
8094 		Creates a window from an existing [SimpleWindow]. This constructor attaches various event handlers to the SimpleWindow object which may overwrite your existing handlers.
8095 
8096 		This constructor is intended primarily for internal use and may be changed to `protected` later.
8097 	+/
8098 	this(SimpleWindow win) {
8099 
8100 		static if(UsingSimpledisplayX11) {
8101 			win.discardAdditionalConnectionState = &discardXConnectionState;
8102 			win.recreateAdditionalConnectionState = &recreateXConnectionState;
8103 		}
8104 
8105 		tabStop = false;
8106 		super(null);
8107 		this.win = win;
8108 
8109 		win.addEventListener((Widget.RedrawEvent) {
8110 			if(win.eventQueued!RecomputeEvent) {
8111 				// writeln("skipping");
8112 				return; // let the recompute event do the actual redraw
8113 			}
8114 			this.actualRedraw();
8115 		});
8116 
8117 		win.addEventListener((Widget.RecomputeEvent) {
8118 			recomputeChildLayoutEntry();
8119 			if(win.eventQueued!RedrawEvent)
8120 				return; // let the queued one do it
8121 			else {
8122 				// writeln("drawing");
8123 				this.actualRedraw(); // if not queued, it needs to be done now anyway
8124 			}
8125 		});
8126 
8127 		this.width = win.width;
8128 		this.height = win.height;
8129 		this.parentWindow = this;
8130 
8131 		win.closeQuery = () {
8132 			if(this.emit!ClosingEvent())
8133 				win.close();
8134 		};
8135 		win.onClosing = () {
8136 			this.emit!ClosedEvent();
8137 		};
8138 
8139 		win.windowResized = (int w, int h) {
8140 			this.width = w;
8141 			this.height = h;
8142 			recomputeChildLayout();
8143 			// this causes a HUGE performance problem for no apparent benefit, hence the commenting
8144 			//version(win32_widgets)
8145 				//InvalidateRect(hwnd, null, true);
8146 			redraw();
8147 		};
8148 
8149 		win.onFocusChange = (bool getting) {
8150 			if(this.focusedWidget) {
8151 				if(getting) {
8152 					this.focusedWidget.emit!FocusEvent();
8153 					this.focusedWidget.emit!FocusInEvent();
8154 				} else {
8155 					this.focusedWidget.emit!BlurEvent();
8156 					this.focusedWidget.emit!FocusOutEvent();
8157 				}
8158 			}
8159 
8160 			if(getting) {
8161 				this.emit!FocusEvent();
8162 				this.emit!FocusInEvent();
8163 			} else {
8164 				this.emit!BlurEvent();
8165 				this.emit!FocusOutEvent();
8166 			}
8167 		};
8168 
8169 		win.onDpiChanged = {
8170 			this.queueRecomputeChildLayout();
8171 			auto event = new DpiChangedEvent(this);
8172 			event.sendDirectly();
8173 
8174 			privateDpiChanged();
8175 		};
8176 
8177 		win.setEventHandlers(
8178 			(MouseEvent e) {
8179 				dispatchMouseEvent(e);
8180 			},
8181 			(KeyEvent e) {
8182 				//writefln("%x   %s", cast(uint) e.key, e.key);
8183 				dispatchKeyEvent(e);
8184 			},
8185 			(dchar e) {
8186 				if(e == 13) e = 10; // hack?
8187 				if(e == 127) return; // linux sends this, windows doesn't. we don't want it.
8188 				dispatchCharEvent(e);
8189 			},
8190 		);
8191 
8192 		addEventListener("char", (Widget, Event ev) {
8193 			if(skipNextChar) {
8194 				ev.preventDefault();
8195 				skipNextChar = false;
8196 			}
8197 		});
8198 
8199 		version(win32_widgets)
8200 		win.handleNativeEvent = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
8201 			if(hwnd !is this.win.impl.hwnd)
8202 				return 1; // we don't care... pass it on
8203 			auto ret = WindowProcedureHelper(this, hwnd, msg, wParam, lParam, mustReturn);
8204 			if(mustReturn)
8205 				return ret;
8206 			return 1; // pass it on
8207 		};
8208 
8209 		if(Window.newWindowCreated)
8210 			Window.newWindowCreated(this);
8211 	}
8212 
8213 	version(custom_widgets)
8214 	override void defaultEventHandler_click(ClickEvent event) {
8215 		if(event.target && event.target.tabStop)
8216 			event.target.focus();
8217 	}
8218 
8219 	private static void delegate(Window) newWindowCreated;
8220 
8221 	version(win32_widgets)
8222 	override void paint(WidgetPainter painter) {
8223 		/*
8224 		RECT rect;
8225 		rect.right = this.width;
8226 		rect.bottom = this.height;
8227 		DrawThemeBackground(theme, painter.impl.hdc, 4, 1, &rect, null);
8228 		*/
8229 		// 3dface is used as window backgrounds by Windows too, so that's why I'm using it here
8230 		auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
8231 		auto p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
8232 		// since the pen is null, to fill the whole space, we need the +1 on both.
8233 		gdi.Rectangle(painter.impl.hdc, 0, 0, this.width + 1, this.height + 1);
8234 		SelectObject(painter.impl.hdc, p);
8235 		SelectObject(painter.impl.hdc, b);
8236 	}
8237 	version(custom_widgets)
8238 	override void paint(WidgetPainter painter) {
8239 		auto cs = getComputedStyle();
8240 		painter.fillColor = cs.windowBackgroundColor;
8241 		painter.outlineColor = cs.windowBackgroundColor;
8242 		painter.drawRectangle(Point(0, 0), this.width, this.height);
8243 	}
8244 
8245 
8246 	override void defaultEventHandler_keydown(KeyDownEvent event) {
8247 		Widget _this = event.target;
8248 
8249 		if(event.key == Key.Tab) {
8250 			/* Window tab ordering is a recursive thingy with each group */
8251 
8252 			// FIXME inefficient
8253 			Widget[] helper(Widget p) {
8254 				if(p.hidden)
8255 					return null;
8256 				Widget[] childOrdering;
8257 
8258 				auto children = p.children.dup;
8259 
8260 				while(true) {
8261 					// UIs should be generally small, so gonna brute force it a little
8262 					// note that it must be a stable sort here; if all are index 0, it should be in order of declaration
8263 
8264 					Widget smallestTab;
8265 					foreach(ref c; children) {
8266 						if(c is null) continue;
8267 						if(smallestTab is null || c.tabOrder < smallestTab.tabOrder) {
8268 							smallestTab = c;
8269 							c = null;
8270 						}
8271 					}
8272 					if(smallestTab !is null) {
8273 						if(smallestTab.tabStop && !smallestTab.hidden)
8274 							childOrdering ~= smallestTab;
8275 						if(!smallestTab.hidden)
8276 							childOrdering ~= helper(smallestTab);
8277 					} else
8278 						break;
8279 
8280 				}
8281 
8282 				return childOrdering;
8283 			}
8284 
8285 			Widget[] tabOrdering = helper(this);
8286 
8287 			Widget recipient;
8288 
8289 			if(tabOrdering.length) {
8290 				bool seenThis = false;
8291 				Widget previous;
8292 				foreach(idx, child; tabOrdering) {
8293 					if(child is focusedWidget) {
8294 
8295 						if(event.shiftKey) {
8296 							if(idx == 0)
8297 								recipient = tabOrdering[$-1];
8298 							else
8299 								recipient = tabOrdering[idx - 1];
8300 							break;
8301 						}
8302 
8303 						seenThis = true;
8304 						if(idx + 1 == tabOrdering.length) {
8305 							// we're at the end, either move to the next group
8306 							// or start back over
8307 							recipient = tabOrdering[0];
8308 						}
8309 						continue;
8310 					}
8311 					if(seenThis) {
8312 						recipient = child;
8313 						break;
8314 					}
8315 					previous = child;
8316 				}
8317 			}
8318 
8319 			if(recipient !is null) {
8320 				//  writeln(typeid(recipient));
8321 				recipient.focus();
8322 
8323 				skipNextChar = true;
8324 			}
8325 		}
8326 
8327 		debug if(event.key == Key.F12) {
8328 			if(devTools) {
8329 				devTools.close();
8330 				devTools = null;
8331 			} else {
8332 				devTools = new DevToolWindow(this);
8333 				devTools.show();
8334 			}
8335 		}
8336 	}
8337 
8338 	debug DevToolWindow devTools;
8339 
8340 
8341 	/++
8342 		Creates a window. Please note windows are created in a hidden state, so you must call [show] or [loop] to get it to display.
8343 
8344 		History:
8345 			Prior to May 12, 2021, the default title was "D Application" (simpledisplay.d's default). After that, the default is `Runtime.args[0]` instead.
8346 
8347 			The width and height arguments were added to the overload that takes `string` first on June 21, 2021.
8348 	+/
8349 	this(int width = 500, int height = 500, string title = null) {
8350 		if(title is null) {
8351 			import core.runtime;
8352 			if(Runtime.args.length)
8353 				title = Runtime.args[0];
8354 		}
8355 		win = new SimpleWindow(width, height, title, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.normal, WindowFlags.dontAutoShow | WindowFlags.managesChildWindowFocus);
8356 
8357 		static if(UsingSimpledisplayX11) {
8358 		///+
8359 		// for input proxy
8360 		auto display = XDisplayConnection.get;
8361 		auto inputProxy = XCreateSimpleWindow(display, win.window, -1, -1, 1, 1, 0, 0, 0);
8362 		XSelectInput(display, inputProxy, EventMask.KeyPressMask | EventMask.KeyReleaseMask | EventMask.FocusChangeMask);
8363 		XMapWindow(display, inputProxy);
8364 		// writefln("input proxy: 0x%0x", inputProxy);
8365 		this.inputProxy = new SimpleWindow(inputProxy);
8366 
8367 		XEvent lastEvent;
8368 		this.inputProxy.handleNativeEvent = (XEvent ev) {
8369 			lastEvent = ev;
8370 			return 1;
8371 		};
8372 		this.inputProxy.setEventHandlers(
8373 			(MouseEvent e) {
8374 				dispatchMouseEvent(e);
8375 			},
8376 			(KeyEvent e) {
8377 				//writefln("%x   %s", cast(uint) e.key, e.key);
8378 				if(dispatchKeyEvent(e)) {
8379 					// FIXME: i should trap error
8380 					if(auto nw = cast(NestedChildWindowWidget) focusedWidget) {
8381 						auto thing = nw.focusableWindow();
8382 						if(thing && thing.window) {
8383 							lastEvent.xkey.window = thing.window;
8384 							// writeln("sending event ", lastEvent.xkey);
8385 							trapXErrors( {
8386 								XSendEvent(XDisplayConnection.get, thing.window, false, 0, &lastEvent);
8387 							});
8388 						}
8389 					}
8390 				}
8391 			},
8392 			(dchar e) {
8393 				if(e == 13) e = 10; // hack?
8394 				if(e == 127) return; // linux sends this, windows doesn't. we don't want it.
8395 				dispatchCharEvent(e);
8396 			},
8397 		);
8398 
8399 		this.inputProxy.populateXic();
8400 		// done
8401 		//+/
8402 		}
8403 
8404 
8405 
8406 		win.setRequestedInputFocus = &this.setRequestedInputFocus;
8407 
8408 		this(win);
8409 	}
8410 
8411 	SimpleWindow inputProxy;
8412 
8413 	private SimpleWindow setRequestedInputFocus() {
8414 		return inputProxy;
8415 	}
8416 
8417 	/// ditto
8418 	this(string title, int width = 500, int height = 500) {
8419 		this(width, height, title);
8420 	}
8421 
8422 	///
8423 	@property string title() { return parentWindow.win.title; }
8424 	///
8425 	@property void title(string title) { parentWindow.win.title = title; }
8426 
8427 	///
8428 	@scriptable
8429 	void close() {
8430 		win.close();
8431 		// I synchronize here upon window closing to ensure all child windows
8432 		// get updated too before the event loop. This avoids some random X errors.
8433 		static if(UsingSimpledisplayX11) {
8434 			runInGuiThread( {
8435 				XSync(XDisplayConnection.get, false);
8436 			});
8437 		}
8438 	}
8439 
8440 	bool dispatchKeyEvent(KeyEvent ev) {
8441 		auto wid = focusedWidget;
8442 		if(wid is null)
8443 			wid = this;
8444 		KeyEventBase event = ev.pressed ? new KeyDownEvent(wid) : new KeyUpEvent(wid);
8445 		event.originalKeyEvent = ev;
8446 		event.key = ev.key;
8447 		event.state = ev.modifierState;
8448 		event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false;
8449 		event.altKey = (ev.modifierState & ModifierState.alt) ? true : false;
8450 		event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false;
8451 		event.dispatch();
8452 
8453 		return !event.propagationStopped;
8454 	}
8455 
8456 	// returns true if propagation should continue into nested things.... prolly not a great thing to do.
8457 	bool dispatchCharEvent(dchar ch) {
8458 		if(focusedWidget) {
8459 			auto event = new CharEvent(focusedWidget, ch);
8460 			event.dispatch();
8461 			return !event.propagationStopped;
8462 		}
8463 		return true;
8464 	}
8465 
8466 	Widget mouseLastOver;
8467 	Widget mouseLastDownOn;
8468 	bool lastWasDoubleClick;
8469 	bool dispatchMouseEvent(MouseEvent ev) {
8470 		auto eleR = widgetAtPoint(this, ev.x, ev.y);
8471 		auto ele = eleR.widget;
8472 
8473 		auto captureEle = ele;
8474 
8475 		if(mouseCapturedBy !is null) {
8476 			if(ele !is mouseCapturedBy && !mouseCapturedBy.isAParentOf(ele))
8477 				captureEle = mouseCapturedBy;
8478 		}
8479 
8480 		// a hack to get it relative to the widget.
8481 		eleR.x = ev.x;
8482 		eleR.y = ev.y;
8483 		auto pain = captureEle;
8484 		while(pain) {
8485 			eleR.x -= pain.x;
8486 			eleR.y -= pain.y;
8487 			pain.addScrollPosition(eleR.x, eleR.y);
8488 			pain = pain.parent;
8489 		}
8490 
8491 		void populateMouseEventBase(MouseEventBase event) {
8492 			event.button = ev.button;
8493 			event.buttonLinear = ev.buttonLinear;
8494 			event.state = ev.modifierState;
8495 			event.clientX = eleR.x;
8496 			event.clientY = eleR.y;
8497 
8498 			event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false;
8499 			event.altKey = (ev.modifierState & ModifierState.alt) ? true : false;
8500 			event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false;
8501 		}
8502 
8503 		if(ev.type == MouseEventType.buttonPressed) {
8504 			{
8505 				auto event = new MouseDownEvent(captureEle);
8506 				populateMouseEventBase(event);
8507 				event.dispatch();
8508 			}
8509 
8510 			if(ev.button != MouseButton.wheelDown && ev.button != MouseButton.wheelUp && mouseLastDownOn is ele && ev.doubleClick) {
8511 				auto event = new DoubleClickEvent(captureEle);
8512 				populateMouseEventBase(event);
8513 				event.dispatch();
8514 				lastWasDoubleClick = ev.doubleClick;
8515 			} else {
8516 				lastWasDoubleClick = false;
8517 			}
8518 
8519 			mouseLastDownOn = ele;
8520 		} else if(ev.type == MouseEventType.buttonReleased) {
8521 			{
8522 				auto event = new MouseUpEvent(captureEle);
8523 				populateMouseEventBase(event);
8524 				event.dispatch();
8525 			}
8526 			if(!lastWasDoubleClick && mouseLastDownOn is ele) {
8527 				auto event = new ClickEvent(captureEle);
8528 				populateMouseEventBase(event);
8529 				event.dispatch();
8530 			}
8531 		} else if(ev.type == MouseEventType.motion) {
8532 			// motion
8533 			{
8534 				auto event = new MouseMoveEvent(captureEle);
8535 				populateMouseEventBase(event); // fills in button which is meaningless but meh
8536 				event.dispatch();
8537 			}
8538 
8539 			if(mouseLastOver !is ele) {
8540 				if(ele !is null) {
8541 					if(!isAParentOf(ele, mouseLastOver)) {
8542 						ele.setDynamicState(DynamicState.hover, true);
8543 						auto event = new MouseEnterEvent(ele);
8544 						event.relatedTarget = mouseLastOver;
8545 						event.sendDirectly();
8546 
8547 						ele.useStyleProperties((scope Widget.Style s) {
8548 							ele.parentWindow.win.cursor = s.cursor;
8549 						});
8550 					}
8551 				}
8552 
8553 				if(mouseLastOver !is null) {
8554 					if(!isAParentOf(mouseLastOver, ele)) {
8555 						mouseLastOver.setDynamicState(DynamicState.hover, false);
8556 						auto event = new MouseLeaveEvent(mouseLastOver);
8557 						event.relatedTarget = ele;
8558 						event.sendDirectly();
8559 					}
8560 				}
8561 
8562 				if(ele !is null) {
8563 					auto event = new MouseOverEvent(ele);
8564 					event.relatedTarget = mouseLastOver;
8565 					event.dispatch();
8566 				}
8567 
8568 				if(mouseLastOver !is null) {
8569 					auto event = new MouseOutEvent(mouseLastOver);
8570 					event.relatedTarget = ele;
8571 					event.dispatch();
8572 				}
8573 
8574 				mouseLastOver = ele;
8575 			}
8576 		}
8577 
8578 		return true; // FIXME: the event default prevented?
8579 	}
8580 
8581 	/++
8582 		Shows the window and runs the application event loop.
8583 
8584 		Blocks until this window is closed.
8585 
8586 		Bugs:
8587 
8588 		$(PITFALL
8589 			You should always have one event loop live for your application.
8590 			If you make two windows in sequence, the second call to loop (or
8591 			simpledisplay's [SimpleWindow.eventLoop], upon which this is built)
8592 			might fail:
8593 
8594 			---
8595 			// don't do this!
8596 			auto window = new Window();
8597 			window.loop();
8598 
8599 			// or new Window or new MainWindow, all the same
8600 			auto window2 = new SimpleWindow();
8601 			window2.eventLoop(0); // problematic! might crash
8602 			---
8603 
8604 			simpledisplay's current implementation assumes that final cleanup is
8605 			done when the event loop refcount reaches zero. So after the first
8606 			eventLoop returns, when there isn't already another one active, it assumes
8607 			the program will exit soon and cleans up.
8608 
8609 			This is arguably a bug that it doesn't reinitialize, and I'll probably change
8610 			it eventually, but in the mean time, there's an easy solution:
8611 
8612 			---
8613 			// do this
8614 			EventLoop mainEventLoop = EventLoop.get; // just add this line
8615 
8616 			auto window = new Window();
8617 			window.loop();
8618 
8619 			// or any other type of Window etc.
8620 			auto window2 = new Window();
8621 			window2.loop(); // perfectly fine since mainEventLoop still alive
8622 			---
8623 
8624 			By adding a top-level reference to the event loop, it ensures the final cleanup
8625 			is not performed until it goes out of scope too, letting the individual window loops
8626 			work without trouble despite the bug.
8627 		)
8628 
8629 		History:
8630 			The [BlockingMode] parameter was added on December 8, 2021.
8631 			The default behavior is to block until the application quits
8632 			(so all windows have been closed), unless another minigui or
8633 			simpledisplay event loop is already running, in which case it
8634 			will block until this window closes specifically.
8635 	+/
8636 	@scriptable
8637 	void loop(BlockingMode bm = BlockingMode.automatic) {
8638 		if(win.closed)
8639 			return; // otherwise show will throw
8640 		show();
8641 		win.eventLoopWithBlockingMode(bm, 0);
8642 	}
8643 
8644 	private bool firstShow = true;
8645 
8646 	@scriptable
8647 	override void show() {
8648 		bool rd = false;
8649 		if(firstShow) {
8650 			firstShow = false;
8651 			recomputeChildLayout();
8652 			auto f = getFirstFocusable(this); // FIXME: autofocus?
8653 			if(f)
8654 				f.focus();
8655 			redraw();
8656 		}
8657 		win.show();
8658 		super.show();
8659 	}
8660 	@scriptable
8661 	override void hide() {
8662 		win.hide();
8663 		super.hide();
8664 	}
8665 
8666 	static Widget getFirstFocusable(Widget start) {
8667 		if(start is null)
8668 			return null;
8669 
8670 		foreach(widget; &start.focusableWidgets) {
8671 			return widget;
8672 		}
8673 
8674 		return null;
8675 	}
8676 
8677 	static Widget getLastFocusable(Widget start) {
8678 		if(start is null)
8679 			return null;
8680 
8681 		Widget last;
8682 		foreach(widget; &start.focusableWidgets) {
8683 			last = widget;
8684 		}
8685 
8686 		return last;
8687 	}
8688 
8689 
8690 	mixin Emits!ClosingEvent;
8691 	mixin Emits!ClosedEvent;
8692 }
8693 
8694 /++
8695 	History:
8696 		Added January 12, 2022
8697 +/
8698 class DpiChangedEvent : Event {
8699 	enum EventString = "dpichanged";
8700 
8701 	this(Widget target) {
8702 		super(EventString, target);
8703 	}
8704 }
8705 
8706 debug private class DevToolWindow : Window {
8707 	Window p;
8708 
8709 	TextEdit parentList;
8710 	TextEdit logWindow;
8711 	TextLabel clickX, clickY;
8712 
8713 	this(Window p) {
8714 		this.p = p;
8715 		super(400, 300, "Developer Toolbox");
8716 
8717 		logWindow = new TextEdit(this);
8718 		parentList = new TextEdit(this);
8719 
8720 		auto hl = new HorizontalLayout(this);
8721 		clickX = new TextLabel("", TextAlignment.Right, hl);
8722 		clickY = new TextLabel("", TextAlignment.Right, hl);
8723 
8724 		parentListeners ~= p.addEventListener("*", (Event ev) {
8725 			log(typeid(ev.source).name, " emitted ", typeid(ev).name);
8726 		});
8727 
8728 		parentListeners ~= p.addEventListener((ClickEvent ev) {
8729 			auto s = ev.srcElement;
8730 
8731 			string list;
8732 
8733 			void addInfo(Widget s) {
8734 				list ~= s.toString();
8735 				list ~= "\n\tminHeight: " ~ toInternal!string(s.minHeight);
8736 				list ~= "\n\tmaxHeight: " ~ toInternal!string(s.maxHeight);
8737 				list ~= "\n\theightStretchiness: " ~ toInternal!string(s.heightStretchiness);
8738 				list ~= "\n\theight: " ~ toInternal!string(s.height);
8739 				list ~= "\n\tminWidth: " ~ toInternal!string(s.minWidth);
8740 				list ~= "\n\tmaxWidth: " ~ toInternal!string(s.maxWidth);
8741 				list ~= "\n\twidthStretchiness: " ~ toInternal!string(s.widthStretchiness);
8742 				list ~= "\n\twidth: " ~ toInternal!string(s.width);
8743 				list ~= "\n\tmarginTop: " ~ toInternal!string(s.marginTop);
8744 				list ~= "\n\tmarginBottom: " ~ toInternal!string(s.marginBottom);
8745 			}
8746 
8747 			addInfo(s);
8748 
8749 			s = s.parent;
8750 			while(s) {
8751 				list ~= "\n";
8752 				addInfo(s);
8753 				s = s.parent;
8754 			}
8755 			parentList.content = list;
8756 
8757 			clickX.label = toInternal!string(ev.clientX);
8758 			clickY.label = toInternal!string(ev.clientY);
8759 		});
8760 	}
8761 
8762 	EventListener[] parentListeners;
8763 
8764 	override void close() {
8765 		assert(p !is null);
8766 		foreach(p; parentListeners)
8767 			p.disconnect();
8768 		parentListeners = null;
8769 		p.devTools = null;
8770 		p = null;
8771 		super.close();
8772 	}
8773 
8774 	override void defaultEventHandler_keydown(KeyDownEvent ev) {
8775 		if(ev.key == Key.F12) {
8776 			this.close();
8777 			if(p)
8778 				p.devTools = null;
8779 		} else {
8780 			super.defaultEventHandler_keydown(ev);
8781 		}
8782 	}
8783 
8784 	void log(T...)(T t) {
8785 		string str;
8786 		import std.conv;
8787 		foreach(i; t)
8788 			str ~= to!string(i);
8789 		str ~= "\n";
8790 		logWindow.addText(str);
8791 
8792 		//version(custom_widgets)
8793 		//logWindow.ensureVisibleInScroll(logWindow.textLayout.caretBoundingBox());
8794 	}
8795 }
8796 
8797 /++
8798 	A dialog is a transient window that intends to get information from
8799 	the user before being dismissed.
8800 +/
8801 abstract class Dialog : Window {
8802 	///
8803 	this(int width, int height, string title = null) {
8804 		super(width, height, title);
8805 	}
8806 
8807 	///
8808 	abstract void OK();
8809 
8810 	///
8811 	void Cancel() {
8812 		this.close();
8813 	}
8814 }
8815 
8816 /++
8817 	A custom widget similar to the HTML5 <details> tag.
8818 +/
8819 version(none)
8820 class DetailsView : Widget {
8821 
8822 }
8823 
8824 // FIXME: maybe i should expose the other list views Windows offers too
8825 
8826 /++
8827 	A TableView is a widget made to display a table of data strings.
8828 
8829 
8830 	Future_Directions:
8831 		Each item should be able to take an icon too and maybe I'll allow more of the view modes Windows offers.
8832 
8833 		I will add a selection changed event at some point, as well as item clicked events.
8834 	History:
8835 		Added September 24, 2021. Initial api stabilized in dub v10.4, but it isn't completely feature complete yet.
8836 	See_Also:
8837 		[ListWidget] which displays a list of strings without additional columns.
8838 +/
8839 class TableView : Widget {
8840 	/++
8841 
8842 	+/
8843 	this(Widget parent) {
8844 		super(parent);
8845 
8846 		version(win32_widgets) {
8847 			createWin32Window(this, WC_LISTVIEW, "", LVS_REPORT | LVS_OWNERDATA);//| LVS_OWNERDRAWFIXED);
8848 		} else version(custom_widgets) {
8849 			auto smw = new ScrollMessageWidget(this);
8850 			smw.addDefaultKeyboardListeners();
8851 			smw.addDefaultWheelListeners(1, scaleWithDpi(16));
8852 			tvwi = new TableViewWidgetInner(this, smw);
8853 		}
8854 	}
8855 
8856 	// FIXME: auto-size columns on double click of header thing like in Windows
8857 	// it need only make the currently displayed things fit well.
8858 
8859 
8860 	private ColumnInfo[] columns;
8861 	private int itemCount;
8862 
8863 	version(custom_widgets) private {
8864 		TableViewWidgetInner tvwi;
8865 	}
8866 
8867 	/// Passed to [setColumnInfo]
8868 	static struct ColumnInfo {
8869 		const(char)[] name; /// the name displayed in the header
8870 		/++
8871 			The default width, in pixels. As a special case, you can set this to -1
8872 			if you want the system to try to automatically size the width to fit visible
8873 			content. If it can't, it will try to pick a sensible default size.
8874 
8875 			Any other negative value is not allowed and may lead to unpredictable results.
8876 
8877 			History:
8878 				The -1 behavior was specified on December 3, 2021. It actually worked before
8879 				anyway on Win32 but now it is a formal feature with partial Linux support.
8880 
8881 			Bugs:
8882 				It doesn't actually attempt to calculate a best-fit width on Linux as of
8883 				December 3, 2021. I do plan to fix this in the future, but Windows is the
8884 				priority right now. At least it doesn't break things when you use it now.
8885 		+/
8886 		int width;
8887 
8888 		/++
8889 			Alignment of the text in the cell. Applies to the header as well as all data in this
8890 			column.
8891 
8892 			Bugs:
8893 				On Windows, the first column ignores this member and is always left aligned.
8894 				You can work around this by inserting a dummy first column with width = 0
8895 				then putting your actual data in the second column, which does respect the
8896 				alignment.
8897 
8898 				This is a quirk of the operating system's implementation going back a very
8899 				long time and is unlikely to ever be fixed.
8900 		+/
8901 		TextAlignment alignment;
8902 
8903 		/++
8904 			After all the pixel widths have been assigned, any left over
8905 			space is divided up among all columns and distributed to according
8906 			to the widthPercent field.
8907 
8908 
8909 			For example, if you have two fields, both with width 50 and one with
8910 			widthPercent of 25 and the other with widthPercent of 75, and the
8911 			container is 200 pixels wide, first both get their width of 50.
8912 			then the 100 remaining pixels are split up, so the one gets a total
8913 			of 75 pixels and the other gets a total of 125.
8914 
8915 			This is automatically applied as the window is resized.
8916 
8917 			If there is not enough space - that is, when a horizontal scrollbar
8918 			needs to appear - there are 0 pixels divided up, and thus everyone
8919 			gets 0. This can cause a column to shrink out of proportion when
8920 			passing the scroll threshold.
8921 
8922 			It is important to still set a fixed width (that is, to populate the
8923 			`width` field) even if you use the percents because that will be the
8924 			default minimum in the event of a scroll bar appearing.
8925 
8926 			The percents total in the column can never exceed 100 or be less than 0.
8927 			Doing this will trigger an assert error.
8928 
8929 			Implementation note:
8930 
8931 			Please note that percentages are only recalculated 1) upon original
8932 			construction and 2) upon resizing the control. If the user adjusts the
8933 			width of a column, the percentage items will not be updated.
8934 
8935 			On the other hand, if the user adjusts the width of a percentage column
8936 			then resizes the window, it is recalculated, meaning their hand adjustment
8937 			is discarded. This specific behavior may change in the future as it is
8938 			arguably a bug, but I'm not certain yet.
8939 
8940 			History:
8941 				Added November 10, 2021 (dub v10.4)
8942 		+/
8943 		int widthPercent;
8944 
8945 
8946 		private int calculatedWidth;
8947 	}
8948 	/++
8949 		Sets the number of columns along with information about the headers.
8950 
8951 		Please note: on Windows, the first column ignores your alignment preference
8952 		and is always left aligned.
8953 	+/
8954 	void setColumnInfo(ColumnInfo[] columns...) {
8955 
8956 		foreach(ref c; columns) {
8957 			c.name = c.name.idup;
8958 		}
8959 		this.columns = columns.dup;
8960 
8961 		updateCalculatedWidth(false);
8962 
8963 		version(custom_widgets) {
8964 			tvwi.header.updateHeaders();
8965 			tvwi.updateScrolls();
8966 		} else version(win32_widgets)
8967 		foreach(i, column; this.columns) {
8968 			LVCOLUMN lvColumn;
8969 			lvColumn.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
8970 			lvColumn.cx = column.width == -1 ? -1 : column.calculatedWidth;
8971 
8972 			auto bfr = WCharzBuffer(column.name);
8973 			lvColumn.pszText = bfr.ptr;
8974 
8975 			if(column.alignment & TextAlignment.Center)
8976 				lvColumn.fmt = LVCFMT_CENTER;
8977 			else if(column.alignment & TextAlignment.Right)
8978 				lvColumn.fmt = LVCFMT_RIGHT;
8979 			else
8980 				lvColumn.fmt = LVCFMT_LEFT;
8981 
8982 			if(SendMessage(hwnd, LVM_INSERTCOLUMN, cast(WPARAM) i, cast(LPARAM) &lvColumn) == -1)
8983 				throw new WindowsApiException("Insert Column Fail", GetLastError());
8984 		}
8985 	}
8986 
8987 	private int getActualSetSize(size_t i, bool askWindows) {
8988 		version(win32_widgets)
8989 			if(askWindows)
8990 				return cast(int) SendMessage(hwnd, LVM_GETCOLUMNWIDTH, cast(WPARAM) i, 0);
8991 		auto w = columns[i].width;
8992 		if(w == -1)
8993 			return 50; // idk, just give it some space so the percents aren't COMPLETELY off FIXME
8994 		return w;
8995 	}
8996 
8997 	private void updateCalculatedWidth(bool informWindows) {
8998 		int padding;
8999 		version(win32_widgets)
9000 			padding = 4;
9001 		int remaining = this.width;
9002 		foreach(i, column; columns)
9003 			remaining -= this.getActualSetSize(i, informWindows && column.widthPercent == 0) + padding;
9004 		remaining -= padding;
9005 		if(remaining < 0)
9006 			remaining = 0;
9007 
9008 		int percentTotal;
9009 		foreach(i, ref column; columns) {
9010 			percentTotal += column.widthPercent;
9011 
9012 			auto c = this.getActualSetSize(i, informWindows && column.widthPercent == 0) + (remaining * column.widthPercent) / 100;
9013 
9014 			column.calculatedWidth = c;
9015 
9016 			version(win32_widgets)
9017 			if(informWindows)
9018 				SendMessage(hwnd, LVM_SETCOLUMNWIDTH, i, c); // LVSCW_AUTOSIZE or LVSCW_AUTOSIZE_USEHEADER are amazing omg
9019 		}
9020 
9021 		assert(percentTotal >= 0, "The total percents in your column definitions were negative. They must add up to something between 0 and 100.");
9022 		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).");
9023 
9024 
9025 	}
9026 
9027 	override void registerMovement() {
9028 		super.registerMovement();
9029 
9030 		updateCalculatedWidth(true);
9031 	}
9032 
9033 	/++
9034 		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.
9035 	+/
9036 	void setItemCount(int count) {
9037 		this.itemCount = count;
9038 		version(custom_widgets) {
9039 			tvwi.updateScrolls();
9040 			redraw();
9041 		} else version(win32_widgets) {
9042 			SendMessage(hwnd, LVM_SETITEMCOUNT, count, 0);
9043 		}
9044 	}
9045 
9046 	/++
9047 		Clears all items;
9048 	+/
9049 	void clear() {
9050 		this.itemCount = 0;
9051 		this.columns = null;
9052 		version(custom_widgets) {
9053 			tvwi.header.updateHeaders();
9054 			tvwi.updateScrolls();
9055 			redraw();
9056 		} else version(win32_widgets) {
9057 			SendMessage(hwnd, LVM_DELETEALLITEMS, 0, 0);
9058 		}
9059 	}
9060 
9061 	/+
9062 	version(win32_widgets)
9063 	override int handleWmDrawItem(DRAWITEMSTRUCT* dis)
9064 		auto itemId = dis.itemID;
9065 		auto hdc = dis.hDC;
9066 		auto rect = dis.rcItem;
9067 		switch(dis.itemAction) {
9068 			case ODA_DRAWENTIRE:
9069 
9070 				// FIXME: do other items
9071 				// FIXME: do the focus rectangle i guess
9072 				// FIXME: alignment
9073 				// FIXME: column width
9074 				// FIXME: padding left
9075 				// FIXME: check dpi scaling
9076 				// FIXME: don't owner draw unless it is necessary.
9077 
9078 				auto padding = GetSystemMetrics(SM_CXEDGE); // FIXME: for dpi
9079 				RECT itemRect;
9080 				itemRect.top = 1; // subitem idx, 1-based
9081 				itemRect.left = LVIR_BOUNDS;
9082 
9083 				SendMessage(hwnd, LVM_GETSUBITEMRECT, itemId, cast(LPARAM) &itemRect);
9084 				itemRect.left += padding;
9085 
9086 				getData(itemId, 0, (in char[] data) {
9087 					auto wdata = WCharzBuffer(data);
9088 					DrawTextW(hdc, wdata.ptr, wdata.length, &itemRect, DT_RIGHT| DT_END_ELLIPSIS);
9089 
9090 				});
9091 			goto case;
9092 			case ODA_FOCUS:
9093 				if(dis.itemState & ODS_FOCUS)
9094 					DrawFocusRect(hdc, &rect);
9095 			break;
9096 			case ODA_SELECT:
9097 				// itemState & ODS_SELECTED
9098 			break;
9099 			default:
9100 		}
9101 		return 1;
9102 	}
9103 	+/
9104 
9105 	version(win32_widgets) {
9106 		CellStyle last;
9107 		COLORREF defaultColor;
9108 		COLORREF defaultBackground;
9109 	}
9110 
9111 	version(win32_widgets)
9112 	override int handleWmNotify(NMHDR* hdr, int code, out int mustReturn) {
9113 		switch(code) {
9114 			case NM_CUSTOMDRAW:
9115 				auto s = cast(NMLVCUSTOMDRAW*) hdr;
9116 				switch(s.nmcd.dwDrawStage) {
9117 					case CDDS_PREPAINT:
9118 						if(getCellStyle is null)
9119 							return 0;
9120 
9121 						mustReturn = true;
9122 						return CDRF_NOTIFYITEMDRAW;
9123 					case CDDS_ITEMPREPAINT:
9124 						mustReturn = true;
9125 						return CDRF_NOTIFYSUBITEMDRAW;
9126 					case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
9127 						mustReturn = true;
9128 
9129 						if(getCellStyle is null) // this SHOULD never happen...
9130 							return 0;
9131 
9132 						if(s.iSubItem == 0) {
9133 							// Windows resets it per row so we'll use item 0 as a chance
9134 							// to capture these for later
9135 							defaultColor = s.clrText;
9136 							defaultBackground = s.clrTextBk;
9137 						}
9138 
9139 						auto style = getCellStyle(cast(int) s.nmcd.dwItemSpec, cast(int) s.iSubItem);
9140 						// if no special style and no reset needed...
9141 						if(style == CellStyle.init && (s.iSubItem == 0 || last == CellStyle.init))
9142 							return 0; // allow default processing to continue
9143 
9144 						last = style;
9145 
9146 						// might still need to reset or use the preference.
9147 
9148 						if(style.flags & CellStyle.Flags.textColorSet)
9149 							s.clrText = style.textColor.asWindowsColorRef;
9150 						else
9151 							s.clrText = defaultColor; // reset in case it was set from last iteration not a fan
9152 						if(style.flags & CellStyle.Flags.backgroundColorSet)
9153 							s.clrTextBk = style.backgroundColor.asWindowsColorRef;
9154 						else
9155 							s.clrTextBk = defaultBackground; // need to reset it... not a fan of this
9156 
9157 						return CDRF_NEWFONT;
9158 					default:
9159 						return 0;
9160 
9161 				}
9162 			case NM_RETURN: // no need since i subclass keydown
9163 			break;
9164 			case LVN_COLUMNCLICK:
9165 				auto info = cast(LPNMLISTVIEW) hdr;
9166 				this.emit!HeaderClickedEvent(info.iSubItem);
9167 			break;
9168 			case NM_CLICK:
9169 			case NM_DBLCLK:
9170 			case NM_RCLICK:
9171 			case NM_RDBLCLK:
9172 				// the item/subitem is set here and that can be a useful notification
9173 				// even beyond the normal click notification
9174 			break;
9175 			case LVN_GETDISPINFO:
9176 				LV_DISPINFO* info = cast(LV_DISPINFO*) hdr;
9177 				if(info.item.mask & LVIF_TEXT) {
9178 					if(getData) {
9179 						getData(info.item.iItem, info.item.iSubItem, (in char[] dataReceived) {
9180 							auto bfr = WCharzBuffer(dataReceived);
9181 							auto len = info.item.cchTextMax;
9182 							if(bfr.length < len)
9183 								len = cast(typeof(len)) bfr.length;
9184 							info.item.pszText[0 .. len] = bfr.ptr[0 .. len];
9185 							info.item.pszText[len] = 0;
9186 						});
9187 					} else {
9188 						info.item.pszText[0] = 0;
9189 					}
9190 					//info.item.iItem
9191 					//if(info.item.iSubItem)
9192 				}
9193 			break;
9194 			default:
9195 		}
9196 		return 0;
9197 	}
9198 
9199 	override bool encapsulatedChildren() {
9200 		return true;
9201 	}
9202 
9203 	/++
9204 		Informs the control that content has changed.
9205 
9206 		History:
9207 			Added November 10, 2021 (dub v10.4)
9208 	+/
9209 	void update() {
9210 		version(custom_widgets)
9211 			redraw();
9212 		else {
9213 			SendMessage(hwnd, LVM_REDRAWITEMS, 0, SendMessage(hwnd, LVM_GETITEMCOUNT, 0, 0));
9214 			UpdateWindow(hwnd);
9215 		}
9216 
9217 
9218 	}
9219 
9220 	/++
9221 		Called by the system to request the text content of an individual cell. You
9222 		should pass the text into the provided `sink` delegate. This function will be
9223 		called for each visible cell as-needed when drawing.
9224 	+/
9225 	void delegate(int row, int column, scope void delegate(in char[]) sink) getData;
9226 
9227 	/++
9228 		Available per-cell style customization options. Use one of the constructors
9229 		provided to set the values conveniently, or default construct it and set individual
9230 		values yourself. Just remember to set the `flags` so your values are actually used.
9231 		If the flag isn't set, the field is ignored and the system default is used instead.
9232 
9233 		This is returned by the [getCellStyle] delegate.
9234 
9235 		Examples:
9236 			---
9237 			// assumes you have a variables called `my_data` which is an array of arrays of numbers
9238 			auto table = new TableView(window);
9239 			// snip: you would set up columns here
9240 
9241 			// this is how you provide data to the table view class
9242 			table.getData = delegate(int row, int column, scope void delegate(in char[]) sink) {
9243 				import std.conv;
9244 				sink(to!string(my_data[row][column]));
9245 			};
9246 
9247 			// and this is how you customize the colors
9248 			table.getCellStyle = delegate(int row, int column) {
9249 				return (my_data[row][column] < 0) ?
9250 					TableView.CellStyle(Color.red); // make negative numbers red
9251 					: TableView.CellStyle.init; // leave the rest alone
9252 			};
9253 			// snip: you would call table.setItemCount here then continue with the rest of your window setup work
9254 			---
9255 
9256 		History:
9257 			Added November 27, 2021 (dub v10.4)
9258 	+/
9259 	struct CellStyle {
9260 		/// 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.
9261 		this(Color textColor) {
9262 			this.textColor = textColor;
9263 			this.flags |= Flags.textColorSet;
9264 		}
9265 		/// Sets a custom text and background color.
9266 		this(Color textColor, Color backgroundColor) {
9267 			this.textColor = textColor;
9268 			this.backgroundColor = backgroundColor;
9269 			this.flags |= Flags.textColorSet | Flags.backgroundColorSet;
9270 		}
9271 
9272 		Color textColor;
9273 		Color backgroundColor;
9274 		int flags; /// bitmask of [Flags]
9275 		/// available options to combine into [flags]
9276 		enum Flags {
9277 			textColorSet = 1 << 0,
9278 			backgroundColorSet = 1 << 1,
9279 		}
9280 	}
9281 	/++
9282 		Companion delegate to [getData] that allows you to custom style each
9283 		cell of the table.
9284 
9285 		Returns:
9286 			A [CellStyle] structure that describes the desired style for the
9287 			given cell. `return CellStyle.init` if you want the default style.
9288 
9289 		History:
9290 			Added November 27, 2021 (dub v10.4)
9291 	+/
9292 	CellStyle delegate(int row, int column) getCellStyle;
9293 
9294 	// i want to be able to do things like draw little colored things to show red for negative numbers
9295 	// or background color indicators or even in-cell charts
9296 	// void delegate(int row, int column, WidgetPainter painter, int width, int height, in char[] text) drawCell;
9297 
9298 	/++
9299 		When the user clicks on a header, this event is emitted. It has a meber to identify which header (by index) was clicked.
9300 	+/
9301 	mixin Emits!HeaderClickedEvent;
9302 }
9303 
9304 /++
9305 	This is emitted by the [TableView] when a user clicks on a column header.
9306 
9307 	Its member `columnIndex` has the zero-based index of the column that was clicked.
9308 
9309 	The default behavior of this event is to do nothing, so `preventDefault` has no effect.
9310 
9311 	History:
9312 		Added November 27, 2021 (dub v10.4)
9313 +/
9314 class HeaderClickedEvent : Event {
9315 	enum EventString = "HeaderClicked";
9316 	this(Widget target, int columnIndex) {
9317 		this.columnIndex = columnIndex;
9318 		super(EventString, target);
9319 	}
9320 
9321 	/// The index of the column
9322 	int columnIndex;
9323 
9324 	///
9325 	override @property int intValue() {
9326 		return columnIndex;
9327 	}
9328 }
9329 
9330 version(custom_widgets)
9331 private class TableViewWidgetInner : Widget {
9332 
9333 // wrap this thing in a ScrollMessageWidget
9334 
9335 	TableView tvw;
9336 	ScrollMessageWidget smw;
9337 	HeaderWidget header;
9338 
9339 	this(TableView tvw, ScrollMessageWidget smw) {
9340 		this.tvw = tvw;
9341 		this.smw = smw;
9342 		super(smw);
9343 
9344 		this.tabStop = true;
9345 
9346 		header = new HeaderWidget(this, smw.getHeader());
9347 
9348 		smw.addEventListener("scroll", () {
9349 			this.redraw();
9350 			header.redraw();
9351 		});
9352 
9353 
9354 		// I need headers outside the scroll area but rendered on the same line as the up arrow
9355 		// FIXME: add a fixed header to the SMW
9356 	}
9357 
9358 	enum padding = 3;
9359 
9360 	void updateScrolls() {
9361 		int w;
9362 		foreach(idx, column; tvw.columns) {
9363 			if(column.width == 0) continue;
9364 			w += tvw.getActualSetSize(idx, false);// + padding;
9365 		}
9366 		smw.setTotalArea(w, tvw.itemCount);
9367 		columnsWidth = w;
9368 	}
9369 
9370 	private int columnsWidth;
9371 
9372 	private int lh() { return scaleWithDpi(16); } // FIXME lineHeight
9373 
9374 	override void registerMovement() {
9375 		super.registerMovement();
9376 		// FIXME: actual column width. it might need to be done per-pixel instead of per-colum
9377 		smw.setViewableArea(this.width, this.height / lh);
9378 	}
9379 
9380 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
9381 		int x;
9382 		int y;
9383 
9384 		int row = smw.position.y;
9385 
9386 		foreach(lol; 0 .. this.height / lh) {
9387 			if(row >= tvw.itemCount)
9388 				break;
9389 			x = 0;
9390 			foreach(columnNumber, column; tvw.columns) {
9391 				auto x2 = x + column.calculatedWidth;
9392 				auto smwx = smw.position.x;
9393 
9394 				if(x2 > smwx /* if right side of it is visible at all */ || (x >= smwx && x < smwx + this.width) /* left side is visible at all*/) {
9395 					auto startX = x;
9396 					auto endX = x + column.calculatedWidth;
9397 					switch (column.alignment & (TextAlignment.Left | TextAlignment.Center | TextAlignment.Right)) {
9398 						case TextAlignment.Left: startX += padding; break;
9399 						case TextAlignment.Center: startX += padding; endX -= padding; break;
9400 						case TextAlignment.Right: endX -= padding; break;
9401 						default: /* broken */ break;
9402 					}
9403 					if(column.width != 0) // no point drawing an invisible column
9404 					tvw.getData(row, cast(int) columnNumber, (in char[] info) {
9405 						// auto clip = painter.setClipRectangle(
9406 
9407 						void dotext(WidgetPainter painter) {
9408 							painter.drawText(Point(startX - smw.position.x, y), info, Point(endX - smw.position.x, y + lh), column.alignment);
9409 						}
9410 
9411 						if(tvw.getCellStyle !is null) {
9412 							auto style = tvw.getCellStyle(row, cast(int) columnNumber);
9413 
9414 							if(style.flags & TableView.CellStyle.Flags.backgroundColorSet) {
9415 								auto tempPainter = painter;
9416 								tempPainter.fillColor = style.backgroundColor;
9417 								tempPainter.outlineColor = style.backgroundColor;
9418 
9419 								tempPainter.drawRectangle(Point(startX - smw.position.x, y),
9420 									Point(endX - smw.position.x, y + lh));
9421 							}
9422 							auto tempPainter = painter;
9423 							if(style.flags & TableView.CellStyle.Flags.textColorSet)
9424 								tempPainter.outlineColor = style.textColor;
9425 
9426 							dotext(tempPainter);
9427 						} else {
9428 							dotext(painter);
9429 						}
9430 					});
9431 				}
9432 
9433 				x += column.calculatedWidth;
9434 			}
9435 			row++;
9436 			y += lh;
9437 		}
9438 		return bounds;
9439 	}
9440 
9441 	static class Style : Widget.Style {
9442 		override WidgetBackground background() {
9443 			return WidgetBackground(WidgetPainter.visualTheme.widgetBackgroundColor);
9444 		}
9445 	}
9446 	mixin OverrideStyle!Style;
9447 
9448 	private static class HeaderWidget : Widget {
9449 		this(TableViewWidgetInner tvw, Widget parent) {
9450 			super(parent);
9451 			this.tvw = tvw;
9452 
9453 			this.remainder = new Button("", this);
9454 
9455 			this.addEventListener((scope ClickEvent ev) {
9456 				int header = -1;
9457 				foreach(idx, child; this.children[1 .. $]) {
9458 					if(child is ev.target) {
9459 						header = cast(int) idx;
9460 						break;
9461 					}
9462 				}
9463 
9464 				if(header != -1) {
9465 					auto hce = new HeaderClickedEvent(tvw.tvw, header);
9466 					hce.dispatch();
9467 				}
9468 
9469 			});
9470 		}
9471 
9472 		void updateHeaders() {
9473 			foreach(child; children[1 .. $])
9474 				child.removeWidget();
9475 
9476 			foreach(column; tvw.tvw.columns) {
9477 				// the cast is ok because I dup it above, just the type is never changed.
9478 				// all this is private so it should never get messed up.
9479 				new Button(ImageLabel(cast(string) column.name, column.alignment), this);
9480 			}
9481 		}
9482 
9483 		Button remainder;
9484 		TableViewWidgetInner tvw;
9485 
9486 		override void recomputeChildLayout() {
9487 			registerMovement();
9488 			int pos;
9489 			foreach(idx, child; children[1 .. $]) {
9490 				if(idx >= tvw.tvw.columns.length)
9491 					continue;
9492 				child.x = pos;
9493 				child.y = 0;
9494 				child.width = tvw.tvw.columns[idx].calculatedWidth;
9495 				child.height = scaleWithDpi(16);// this.height;
9496 				pos += child.width;
9497 
9498 				child.recomputeChildLayout();
9499 			}
9500 
9501 			if(remainder is null)
9502 				return;
9503 
9504 			remainder.x = pos;
9505 			remainder.y = 0;
9506 			if(pos < this.width)
9507 				remainder.width = this.width - pos;// + 4;
9508 			else
9509 				remainder.width = 0;
9510 			remainder.height = scaleWithDpi(16);
9511 
9512 			remainder.recomputeChildLayout();
9513 		}
9514 
9515 		// for the scrollable children mixin
9516 		Point scrollOrigin() {
9517 			return Point(tvw.smw.position.x, 0);
9518 		}
9519 		void paintFrameAndBackground(WidgetPainter painter) { }
9520 
9521 		mixin ScrollableChildren;
9522 	}
9523 }
9524 
9525 /+
9526 
9527 // given struct / array / number / string / etc, make it viewable and editable
9528 class DataViewerWidget : Widget {
9529 
9530 }
9531 +/
9532 
9533 /++
9534 	A line edit box with an associated label.
9535 
9536 	History:
9537 		On May 17, 2021, the default internal layout was changed from horizontal to vertical.
9538 
9539 		```
9540 		Old: ________
9541 
9542 		New:
9543 		____________
9544 		```
9545 
9546 		To restore the old behavior, use `new LabeledLineEdit("label", TextAlignment.Right, parent);`
9547 
9548 		You can also use `new LabeledLineEdit("label", TextAlignment.Left, parent);` if you want a
9549 		horizontal label but left aligned. You may also consider a [GridLayout].
9550 +/
9551 alias LabeledLineEdit = Labeled!LineEdit;
9552 
9553 /++
9554 	History:
9555 		Added May 19, 2021
9556 +/
9557 class Labeled(T) : Widget {
9558 	///
9559 	this(string label, Widget parent) {
9560 		super(parent);
9561 		initialize!VerticalLayout(label, TextAlignment.Left, parent);
9562 	}
9563 
9564 	/++
9565 		History:
9566 			The alignment parameter was added May 17, 2021
9567 	+/
9568 	this(string label, TextAlignment alignment, Widget parent) {
9569 		super(parent);
9570 		initialize!HorizontalLayout(label, alignment, parent);
9571 	}
9572 
9573 	private void initialize(L)(string label, TextAlignment alignment, Widget parent) {
9574 		tabStop = false;
9575 		horizontal = is(L == HorizontalLayout);
9576 		auto hl = new L(this);
9577 		if(horizontal) {
9578 			static class SpecialTextLabel : TextLabel {
9579 				this(string label, TextAlignment alignment, Widget parent) {
9580 					super(label, alignment, parent);
9581 				}
9582 
9583 				override int paddingTop() { return 6; }
9584 			}
9585 			this.label = new SpecialTextLabel(label, alignment, hl);
9586 		} else
9587 			this.label = new TextLabel(label, alignment, hl);
9588 		this.lineEdit = new T(hl);
9589 
9590 		this.label.labelFor = this.lineEdit;
9591 	}
9592 
9593 	private bool horizontal;
9594 
9595 	TextLabel label; ///
9596 	T lineEdit; ///
9597 
9598 	override int flexBasisWidth() { return 250; }
9599 
9600 	override int minHeight() {
9601 		return this.children[0].minHeight;
9602 	}
9603 	override int maxHeight() { return minHeight(); }
9604 	override int marginTop() { return 4; }
9605 	override int marginBottom() { return 4; }
9606 
9607 	// FIXME: i should prolly call it value as well as content tbh
9608 
9609 	///
9610 	@property string content() {
9611 		return lineEdit.content;
9612 	}
9613 	///
9614 	@property void content(string c) {
9615 		return lineEdit.content(c);
9616 	}
9617 
9618 	///
9619 	void selectAll() {
9620 		lineEdit.selectAll();
9621 	}
9622 
9623 	override void focus() {
9624 		lineEdit.focus();
9625 	}
9626 }
9627 
9628 /++
9629 	A labeled password edit.
9630 
9631 	History:
9632 		Added as a class on January 25, 2021, changed into an alias of the new [Labeled] template on May 19, 2021
9633 
9634 		The default parameters for the constructors were also removed on May 19, 2021
9635 +/
9636 alias LabeledPasswordEdit = Labeled!PasswordEdit;
9637 
9638 private string toMenuLabel(string s) {
9639 	string n;
9640 	n.reserve(s.length);
9641 	foreach(c; s)
9642 		if(c == '_')
9643 			n ~= ' ';
9644 		else
9645 			n ~= c;
9646 	return n;
9647 }
9648 
9649 private void autoExceptionHandler(Exception e) {
9650 	messageBox(e.msg);
9651 }
9652 
9653 private void delegate() makeAutomaticHandler(alias fn, T)(T t) {
9654 	static if(is(T : void delegate())) {
9655 		return () {
9656 			try
9657 				t();
9658 			catch(Exception e)
9659 				autoExceptionHandler(e);
9660 		};
9661 	} else static if(is(typeof(fn) Params == __parameters)) {
9662 		static if(Params.length == 1 && is(Params[0] == FileName!(member, filters, type), alias member, string[] filters, FileDialogType type)) {
9663 			return () {
9664 				void onOK(string s) {
9665 					member = s;
9666 					try
9667 						t(Params[0](s));
9668 					catch(Exception e)
9669 						autoExceptionHandler(e);
9670 				}
9671 
9672 				if(
9673 					(type == FileDialogType.Automatic && (__traits(identifier, fn).startsWith("Save") || __traits(identifier, fn).startsWith("Export")))
9674 					|| type == FileDialogType.Save)
9675 				{
9676 					getSaveFileName(&onOK, member, filters, null);
9677 				} else
9678 					getOpenFileName(&onOK, member, filters, null);
9679 			};
9680 		} else {
9681 			struct S {
9682 				static if(!__traits(compiles, mixin(`{ static foreach(i; 1..4) {} }`))) {
9683 					pragma(msg, "warning: automatic handler of params not yet implemented on your compiler");
9684 				} else mixin(q{
9685 				static foreach(idx, ignore; Params) {
9686 					mixin("Params[idx] " ~ __traits(identifier, Params[idx .. idx + 1]) ~ ";");
9687 				}
9688 				});
9689 			}
9690 			return () {
9691 				dialog((S s) {
9692 					try {
9693 						static if(is(typeof(t) Ret == return)) {
9694 							static if(is(Ret == void)) {
9695 								t(s.tupleof);
9696 							} else {
9697 								auto ret = t(s.tupleof);
9698 								import std.conv;
9699 								messageBox(to!string(ret), "Returned Value");
9700 							}
9701 						}
9702 					} catch(Exception e)
9703 						autoExceptionHandler(e);
9704 				}, null, __traits(identifier, fn));
9705 			};
9706 		}
9707 	}
9708 }
9709 
9710 private template hasAnyRelevantAnnotations(a...) {
9711 	bool helper() {
9712 		bool any;
9713 		foreach(attr; a) {
9714 			static if(is(typeof(attr) == .menu))
9715 				any = true;
9716 			else static if(is(typeof(attr) == .toolbar))
9717 				any = true;
9718 			else static if(is(attr == .separator))
9719 				any = true;
9720 			else static if(is(typeof(attr) == .accelerator))
9721 				any = true;
9722 			else static if(is(typeof(attr) == .hotkey))
9723 				any = true;
9724 			else static if(is(typeof(attr) == .icon))
9725 				any = true;
9726 			else static if(is(typeof(attr) == .label))
9727 				any = true;
9728 			else static if(is(typeof(attr) == .tip))
9729 				any = true;
9730 		}
9731 		return any;
9732 	}
9733 
9734 	enum bool hasAnyRelevantAnnotations = helper();
9735 }
9736 
9737 /++
9738 	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.
9739 +/
9740 class MainWindow : Window {
9741 	///
9742 	this(string title = null, int initialWidth = 500, int initialHeight = 500) {
9743 		super(initialWidth, initialHeight, title);
9744 
9745 		_clientArea = new ClientAreaWidget();
9746 		_clientArea.x = 0;
9747 		_clientArea.y = 0;
9748 		_clientArea.width = this.width;
9749 		_clientArea.height = this.height;
9750 		_clientArea.tabStop = false;
9751 
9752 		super.addChild(_clientArea);
9753 
9754 		statusBar = new StatusBar(this);
9755 	}
9756 
9757 	/++
9758 		Adds a menu and toolbar from annotated functions.
9759 
9760 	---
9761         struct Commands {
9762                 @menu("File") {
9763                         void New() {}
9764                         void Open() {}
9765                         void Save() {}
9766                         @separator
9767                         void Exit() @accelerator("Alt+F4") @hotkey('x') {
9768                                 window.close();
9769                         }
9770                 }
9771 
9772                 @menu("Edit") {
9773                         void Undo() {
9774                                 undo();
9775                         }
9776                         @separator
9777                         void Cut() {}
9778                         void Copy() {}
9779                         void Paste() {}
9780                 }
9781 
9782                 @menu("Help") {
9783                         void About() {}
9784                 }
9785         }
9786 
9787         Commands commands;
9788 
9789         window.setMenuAndToolbarFromAnnotatedCode(commands);
9790 	---
9791 
9792 	Note that you can call this function multiple times and it will add the items in order to the given items.
9793 
9794 	+/
9795 	void setMenuAndToolbarFromAnnotatedCode(T)(ref T t) if(!is(T == class) && !is(T == interface)) {
9796 		setMenuAndToolbarFromAnnotatedCode_internal(t);
9797 	}
9798 	void setMenuAndToolbarFromAnnotatedCode(T)(T t) if(is(T == class) || is(T == interface)) {
9799 		setMenuAndToolbarFromAnnotatedCode_internal(t);
9800 	}
9801 	void setMenuAndToolbarFromAnnotatedCode_internal(T)(ref T t) {
9802 		Action[] toolbarActions;
9803 		auto menuBar = this.menuBar is null ? new MenuBar() : this.menuBar;
9804 		Menu[string] mcs;
9805 
9806 		foreach(menu; menuBar.subMenus) {
9807 			mcs[menu.label] = menu;
9808 		}
9809 
9810 		foreach(memberName; __traits(derivedMembers, T)) {
9811 			static if(memberName != "this")
9812 			static if(hasAnyRelevantAnnotations!(__traits(getAttributes, __traits(getMember, T, memberName)))) {
9813 				.menu menu;
9814 				.toolbar toolbar;
9815 				bool separator;
9816 				.accelerator accelerator;
9817 				.hotkey hotkey;
9818 				.icon icon;
9819 				string label;
9820 				string tip;
9821 				foreach(attr; __traits(getAttributes, __traits(getMember, T, memberName))) {
9822 					static if(is(typeof(attr) == .menu))
9823 						menu = attr;
9824 					else static if(is(typeof(attr) == .toolbar))
9825 						toolbar = attr;
9826 					else static if(is(attr == .separator))
9827 						separator = true;
9828 					else static if(is(typeof(attr) == .accelerator))
9829 						accelerator = attr;
9830 					else static if(is(typeof(attr) == .hotkey))
9831 						hotkey = attr;
9832 					else static if(is(typeof(attr) == .icon))
9833 						icon = attr;
9834 					else static if(is(typeof(attr) == .label))
9835 						label = attr.label;
9836 					else static if(is(typeof(attr) == .tip))
9837 						tip = attr.tip;
9838 				}
9839 
9840 				if(menu !is .menu.init || toolbar !is .toolbar.init) {
9841 					ushort correctIcon = icon.id; // FIXME
9842 					if(label.length == 0)
9843 						label = memberName.toMenuLabel;
9844 
9845 					auto handler = makeAutomaticHandler!(__traits(getMember, T, memberName))(&__traits(getMember, t, memberName));
9846 
9847 					auto action = new Action(label, correctIcon, handler);
9848 
9849 					if(accelerator.keyString.length) {
9850 						auto ke = KeyEvent.parse(accelerator.keyString);
9851 						action.accelerator = ke;
9852 						accelerators[ke.toStr] = handler;
9853 					}
9854 
9855 					if(toolbar !is .toolbar.init)
9856 						toolbarActions ~= action;
9857 					if(menu !is .menu.init) {
9858 						Menu mc;
9859 						if(menu.name in mcs) {
9860 							mc = mcs[menu.name];
9861 						} else {
9862 							mc = new Menu(menu.name, this);
9863 							menuBar.addItem(mc);
9864 							mcs[menu.name] = mc;
9865 						}
9866 
9867 						if(separator)
9868 							mc.addSeparator();
9869 						mc.addItem(new MenuItem(action));
9870 					}
9871 				}
9872 			}
9873 		}
9874 
9875 		this.menuBar = menuBar;
9876 
9877 		if(toolbarActions.length) {
9878 			auto tb = new ToolBar(toolbarActions, this);
9879 		}
9880 	}
9881 
9882 	void delegate()[string] accelerators;
9883 
9884 	override void defaultEventHandler_keydown(KeyDownEvent event) {
9885 		auto str = event.originalKeyEvent.toStr;
9886 		if(auto acl = str in accelerators)
9887 			(*acl)();
9888 		super.defaultEventHandler_keydown(event);
9889 	}
9890 
9891 	override void defaultEventHandler_mouseover(MouseOverEvent event) {
9892 		super.defaultEventHandler_mouseover(event);
9893 		if(this.statusBar !is null && event.target.statusTip.length)
9894 			this.statusBar.parts[0].content = event.target.statusTip;
9895 		else if(this.statusBar !is null && this.statusTip.length)
9896 			this.statusBar.parts[0].content = this.statusTip; // ~ " " ~ event.target.toString();
9897 	}
9898 
9899 	override void addChild(Widget c, int position = int.max) {
9900 		if(auto tb = cast(ToolBar) c)
9901 			version(win32_widgets)
9902 				super.addChild(c, 0);
9903 			else version(custom_widgets)
9904 				super.addChild(c, menuBar ? 1 : 0);
9905 			else static assert(0);
9906 		else
9907 			clientArea.addChild(c, position);
9908 	}
9909 
9910 	ToolBar _toolBar;
9911 	///
9912 	ToolBar toolBar() { return _toolBar; }
9913 	///
9914 	ToolBar toolBar(ToolBar t) {
9915 		_toolBar = t;
9916 		foreach(child; this.children)
9917 			if(child is t)
9918 				return t;
9919 		version(win32_widgets)
9920 			super.addChild(t, 0);
9921 		else version(custom_widgets)
9922 			super.addChild(t, menuBar ? 1 : 0);
9923 		else static assert(0);
9924 		return t;
9925 	}
9926 
9927 	MenuBar _menu;
9928 	///
9929 	MenuBar menuBar() { return _menu; }
9930 	///
9931 	MenuBar menuBar(MenuBar m) {
9932 		if(m is _menu) {
9933 			version(custom_widgets)
9934 				recomputeChildLayout();
9935 			return m;
9936 		}
9937 
9938 		if(_menu !is null) {
9939 			// make sure it is sanely removed
9940 			// FIXME
9941 		}
9942 
9943 		_menu = m;
9944 
9945 		version(win32_widgets) {
9946 			SetMenu(parentWindow.win.impl.hwnd, m.handle);
9947 		} else version(custom_widgets) {
9948 			super.addChild(m, 0);
9949 
9950 		//	clientArea.y = menu.height;
9951 		//	clientArea.height = this.height - menu.height;
9952 
9953 			recomputeChildLayout();
9954 		} else static assert(false);
9955 
9956 		return _menu;
9957 	}
9958 	private Widget _clientArea;
9959 	///
9960 	@property Widget clientArea() { return _clientArea; }
9961 	protected @property void clientArea(Widget wid) {
9962 		_clientArea = wid;
9963 	}
9964 
9965 	private StatusBar _statusBar;
9966 	/++
9967 		Returns the window's [StatusBar]. Be warned it may be `null`.
9968 	+/
9969 	@property StatusBar statusBar() { return _statusBar; }
9970 	/// ditto
9971 	@property void statusBar(StatusBar bar) {
9972 		if(_statusBar !is null)
9973 			_statusBar.removeWidget();
9974 		_statusBar = bar;
9975 		if(bar !is null)
9976 			super.addChild(_statusBar);
9977 	}
9978 }
9979 
9980 /+
9981 	This is really an implementation detail of [MainWindow]
9982 +/
9983 private class ClientAreaWidget : Widget {
9984 	this() {
9985 		this.tabStop = false;
9986 		super(null);
9987 		//sa = new ScrollableWidget(this);
9988 	}
9989 	/*
9990 	ScrollableWidget sa;
9991 	override void addChild(Widget w, int position) {
9992 		if(sa is null)
9993 			super.addChild(w, position);
9994 		else {
9995 			sa.addChild(w, position);
9996 			sa.setContentSize(this.minWidth + 1, this.minHeight);
9997 			writeln(sa.contentWidth, "x", sa.contentHeight);
9998 		}
9999 	}
10000 	*/
10001 }
10002 
10003 /**
10004 	Toolbars are lists of buttons (typically icons) that appear under the menu.
10005 	Each button ought to correspond to a menu item, represented by [Action] objects.
10006 */
10007 class ToolBar : Widget {
10008 	version(win32_widgets) {
10009 		private int idealHeight;
10010 		override int minHeight() { return idealHeight; }
10011 		override int maxHeight() { return idealHeight; }
10012 	} else version(custom_widgets) {
10013 		override int minHeight() { return toolbarIconSize; }// defaultLineHeight * 3/2; }
10014 		override int maxHeight() { return toolbarIconSize; } //defaultLineHeight * 3/2; }
10015 	} else static assert(false);
10016 	override int heightStretchiness() { return 0; }
10017 
10018 	version(win32_widgets) {
10019 		HIMAGELIST imageListSmall;
10020 		HIMAGELIST imageListLarge;
10021 	}
10022 
10023 	this(Widget parent) {
10024 		this(null, parent);
10025 	}
10026 
10027 	version(win32_widgets)
10028 	void changeIconSize(bool useLarge) {
10029 		SendMessageW(hwnd, TB_SETIMAGELIST, cast(WPARAM) 0, cast(LPARAM) (useLarge ? imageListLarge : imageListSmall));
10030 
10031 		/+
10032 		SIZE size;
10033 		import core.sys.windows.commctrl;
10034 		SendMessageW(hwnd, TB_GETMAXSIZE, 0, cast(LPARAM) &size);
10035 		idealHeight = size.cy + 4; // the plus 4 is a hack
10036 		+/
10037 
10038 		idealHeight = useLarge ? 34 : 26;
10039 
10040 		if(parent) {
10041 			parent.recomputeChildLayout();
10042 			parent.redraw();
10043 		}
10044 
10045 		SendMessageW(hwnd, TB_SETBUTTONSIZE, 0, (idealHeight-4) << 16 | (idealHeight-4));
10046 		SendMessageW(hwnd, TB_AUTOSIZE, 0, 0);
10047 	}
10048 
10049 	///
10050 	this(Action[] actions, Widget parent) {
10051 		super(parent);
10052 
10053 		tabStop = false;
10054 
10055 		version(win32_widgets) {
10056 			// so i like how the flat thing looks on windows, but not on wine
10057 			// and eh, with windows visual styles enabled it looks cool anyway soooo gonna
10058 			// leave it commented
10059 			createWin32Window(this, "ToolbarWindow32"w, "", TBSTYLE_LIST|/*TBSTYLE_FLAT|*/TBSTYLE_TOOLTIPS);
10060 
10061 			SendMessageW(hwnd, TB_SETEXTENDEDSTYLE, 0, 8/*TBSTYLE_EX_MIXEDBUTTONS*/);
10062 
10063 			imageListSmall = ImageList_Create(
10064 				// width, height
10065 				16, 16,
10066 				ILC_COLOR16 | ILC_MASK,
10067 				16 /*numberOfButtons*/, 0);
10068 
10069 			imageListLarge = ImageList_Create(
10070 				// width, height
10071 				24, 24,
10072 				ILC_COLOR16 | ILC_MASK,
10073 				16 /*numberOfButtons*/, 0);
10074 
10075 			SendMessageW(hwnd, TB_SETIMAGELIST, cast(WPARAM) 0, cast(LPARAM) imageListSmall);
10076 			SendMessageW(hwnd, TB_LOADIMAGES, cast(WPARAM) IDB_STD_SMALL_COLOR, cast(LPARAM) HINST_COMMCTRL);
10077 
10078 			SendMessageW(hwnd, TB_SETIMAGELIST, cast(WPARAM) 0, cast(LPARAM) imageListLarge);
10079 			SendMessageW(hwnd, TB_LOADIMAGES, cast(WPARAM) IDB_STD_LARGE_COLOR, cast(LPARAM) HINST_COMMCTRL);
10080 
10081 			SendMessageW(hwnd, TB_SETMAXTEXTROWS, 0, 0);
10082 
10083 			TBBUTTON[] buttons;
10084 
10085 			// FIXME: I_IMAGENONE is if here is no icon
10086 			foreach(action; actions)
10087 				buttons ~= TBBUTTON(
10088 					MAKELONG(cast(ushort)(action.iconId ? (action.iconId - 1) : -2 /* I_IMAGENONE */), 0),
10089 					action.id,
10090 					TBSTATE_ENABLED, // state
10091 					0, // style
10092 					0, // reserved array, just zero it out
10093 					0, // dwData
10094 					cast(size_t) toWstringzInternal(action.label) // INT_PTR
10095 				);
10096 
10097 			SendMessageW(hwnd, TB_BUTTONSTRUCTSIZE, cast(WPARAM)TBBUTTON.sizeof, 0);
10098 			SendMessageW(hwnd, TB_ADDBUTTONSW, cast(WPARAM) buttons.length, cast(LPARAM)buttons.ptr);
10099 
10100 			/*
10101 			RECT rect;
10102 			GetWindowRect(hwnd, &rect);
10103 			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
10104 			*/
10105 
10106 			dpiChanged(); // to load the things calling changeIconSize the first time
10107 
10108 			assert(idealHeight);
10109 		} else version(custom_widgets) {
10110 			foreach(action; actions)
10111 				new ToolButton(action, this);
10112 		} else static assert(false);
10113 	}
10114 
10115 	override void recomputeChildLayout() {
10116 		.recomputeChildLayout!"width"(this);
10117 	}
10118 
10119 
10120 	version(win32_widgets)
10121 	override protected void dpiChanged() {
10122 		auto sz = scaleWithDpi(16);
10123 		if(sz >= 20)
10124 			changeIconSize(true);
10125 		else
10126 			changeIconSize(false);
10127 	}
10128 }
10129 
10130 enum toolbarIconSize = 24;
10131 
10132 /// 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.
10133 class ToolButton : Button {
10134 	///
10135 	this(string label, Widget parent) {
10136 		super(label, parent);
10137 		tabStop = false;
10138 	}
10139 	///
10140 	this(Action action, Widget parent) {
10141 		super(action.label, parent);
10142 		tabStop = false;
10143 		this.action = action;
10144 	}
10145 
10146 	version(custom_widgets)
10147 	override void defaultEventHandler_click(ClickEvent event) {
10148 		foreach(handler; action.triggered)
10149 			handler();
10150 	}
10151 
10152 	Action action;
10153 
10154 	override int maxWidth() { return toolbarIconSize; }
10155 	override int minWidth() { return toolbarIconSize; }
10156 	override int maxHeight() { return toolbarIconSize; }
10157 	override int minHeight() { return toolbarIconSize; }
10158 
10159 	version(custom_widgets)
10160 	override void paint(WidgetPainter painter) {
10161 	painter.drawThemed(delegate Rectangle (const Rectangle bounds) {
10162 		painter.outlineColor = Color.black;
10163 
10164 		// I want to get from 16 to 24. that's * 3 / 2
10165 		static assert(toolbarIconSize >= 16);
10166 		enum multiplier = toolbarIconSize / 8;
10167 		enum divisor = 2 + ((toolbarIconSize % 8) ? 1 : 0);
10168 		switch(action.iconId) {
10169 			case GenericIcons.New:
10170 				painter.fillColor = Color.white;
10171 				painter.drawPolygon(
10172 					Point(3, 2) * multiplier / divisor, Point(3, 13) * multiplier / divisor, Point(12, 13) * multiplier / divisor, Point(12, 6) * multiplier / divisor,
10173 					Point(8, 2) * multiplier / divisor, Point(8, 6) * multiplier / divisor, Point(12, 6) * multiplier / divisor, Point(8, 2) * multiplier / divisor,
10174 					Point(3, 2) * multiplier / divisor, Point(3, 13) * multiplier / divisor
10175 				);
10176 			break;
10177 			case GenericIcons.Save:
10178 				painter.fillColor = Color.white;
10179 				painter.outlineColor = Color.black;
10180 				painter.drawRectangle(Point(2, 2) * multiplier / divisor, Point(13, 13) * multiplier / divisor);
10181 
10182 				// the label
10183 				painter.drawRectangle(Point(4, 8) * multiplier / divisor, Point(11, 13) * multiplier / divisor);
10184 
10185 				// the slider
10186 				painter.fillColor = Color.black;
10187 				painter.outlineColor = Color.black;
10188 				painter.drawRectangle(Point(4, 3) * multiplier / divisor, Point(10, 6) * multiplier / divisor);
10189 
10190 				painter.fillColor = Color.white;
10191 				painter.outlineColor = Color.white;
10192 				// the disc window
10193 				painter.drawRectangle(Point(5, 3) * multiplier / divisor, Point(6, 5) * multiplier / divisor);
10194 			break;
10195 			case GenericIcons.Open:
10196 				painter.fillColor = Color.white;
10197 				painter.drawPolygon(
10198 					Point(4, 4) * multiplier / divisor, Point(4, 12) * multiplier / divisor, Point(13, 12) * multiplier / divisor, Point(13, 3) * multiplier / divisor,
10199 					Point(9, 3) * multiplier / divisor, Point(9, 4) * multiplier / divisor, Point(4, 4) * multiplier / divisor);
10200 				painter.drawPolygon(
10201 					Point(2, 6) * multiplier / divisor, Point(11, 6) * multiplier / divisor,
10202 					Point(12, 12) * multiplier / divisor, Point(4, 12) * multiplier / divisor,
10203 					Point(2, 6) * multiplier / divisor);
10204 				//painter.drawLine(Point(9, 6) * multiplier / divisor, Point(13, 7) * multiplier / divisor);
10205 			break;
10206 			case GenericIcons.Copy:
10207 				painter.fillColor = Color.white;
10208 				painter.drawRectangle(Point(3, 2) * multiplier / divisor, Point(9, 10) * multiplier / divisor);
10209 				painter.drawRectangle(Point(6, 5) * multiplier / divisor, Point(12, 13) * multiplier / divisor);
10210 			break;
10211 			case GenericIcons.Cut:
10212 				painter.fillColor = Color.transparent;
10213 				painter.outlineColor = getComputedStyle.foregroundColor();
10214 				painter.drawLine(Point(3, 2) * multiplier / divisor, Point(10, 9) * multiplier / divisor);
10215 				painter.drawLine(Point(4, 9) * multiplier / divisor, Point(11, 2) * multiplier / divisor);
10216 				painter.drawRectangle(Point(3, 9) * multiplier / divisor, Point(5, 13) * multiplier / divisor);
10217 				painter.drawRectangle(Point(9, 9) * multiplier / divisor, Point(11, 12) * multiplier / divisor);
10218 			break;
10219 			case GenericIcons.Paste:
10220 				painter.fillColor = Color.white;
10221 				painter.drawRectangle(Point(2, 3) * multiplier / divisor, Point(11, 11) * multiplier / divisor);
10222 				painter.drawRectangle(Point(6, 8) * multiplier / divisor, Point(13, 13) * multiplier / divisor);
10223 				painter.drawLine(Point(6, 2) * multiplier / divisor, Point(4, 5) * multiplier / divisor);
10224 				painter.drawLine(Point(6, 2) * multiplier / divisor, Point(9, 5) * multiplier / divisor);
10225 				painter.fillColor = Color.black;
10226 				painter.drawRectangle(Point(4, 5) * multiplier / divisor, Point(9, 6) * multiplier / divisor);
10227 			break;
10228 			case GenericIcons.Help:
10229 				painter.outlineColor = getComputedStyle.foregroundColor();
10230 				painter.drawText(Point(0, 0), "?", Point(width, height), TextAlignment.Center | TextAlignment.VerticalCenter);
10231 			break;
10232 			case GenericIcons.Undo:
10233 				painter.fillColor = Color.transparent;
10234 				painter.drawArc(Point(3, 4) * multiplier / divisor, 9 * multiplier / divisor, 9 * multiplier / divisor, 0, 360 * 64);
10235 				painter.outlineColor = Color.black;
10236 				painter.fillColor = Color.black;
10237 				painter.drawPolygon(
10238 					Point(4, 4) * multiplier / divisor,
10239 					Point(8, 2) * multiplier / divisor,
10240 					Point(8, 6) * multiplier / divisor,
10241 					Point(4, 4) * multiplier / divisor,
10242 				);
10243 			break;
10244 			case GenericIcons.Redo:
10245 				painter.fillColor = Color.transparent;
10246 				painter.drawArc(Point(3, 4) * multiplier / divisor, 9 * multiplier / divisor, 9 * multiplier / divisor, 0, 360 * 64);
10247 				painter.outlineColor = Color.black;
10248 				painter.fillColor = Color.black;
10249 				painter.drawPolygon(
10250 					Point(10, 4) * multiplier / divisor,
10251 					Point(6, 2) * multiplier / divisor,
10252 					Point(6, 6) * multiplier / divisor,
10253 					Point(10, 4) * multiplier / divisor,
10254 				);
10255 			break;
10256 			default:
10257 				painter.drawText(Point(0, 0), action.label, Point(width, height), TextAlignment.Center | TextAlignment.VerticalCenter);
10258 		}
10259 		return bounds;
10260 		});
10261 	}
10262 
10263 }
10264 
10265 
10266 ///
10267 class MenuBar : Widget {
10268 	MenuItem[] items;
10269 	Menu[] subMenus;
10270 
10271 	version(win32_widgets) {
10272 		HMENU handle;
10273 		///
10274 		this(Widget parent = null) {
10275 			super(parent);
10276 
10277 			handle = CreateMenu();
10278 			tabStop = false;
10279 		}
10280 	} else version(custom_widgets) {
10281 		///
10282 		this(Widget parent = null) {
10283 			tabStop = false; // these are selected some other way
10284 			super(parent);
10285 		}
10286 
10287 		mixin Padding!q{2};
10288 	} else static assert(false);
10289 
10290 	version(custom_widgets)
10291 	override void paint(WidgetPainter painter) {
10292 		draw3dFrame(this, painter, FrameStyle.risen, getComputedStyle().background.color);
10293 	}
10294 
10295 	///
10296 	MenuItem addItem(MenuItem item) {
10297 		this.addChild(item);
10298 		items ~= item;
10299 		version(win32_widgets) {
10300 			AppendMenuW(handle, MF_STRING, item.action is null ? 9000 : item.action.id, toWstringzInternal(item.label));
10301 		}
10302 		return item;
10303 	}
10304 
10305 
10306 	///
10307 	Menu addItem(Menu item) {
10308 
10309 		subMenus ~= item;
10310 
10311 		auto mbItem = new MenuItem(item.label, null);// this.parentWindow); // I'ma add the child down below so hopefully this isn't too insane
10312 
10313 		addChild(mbItem);
10314 		items ~= mbItem;
10315 
10316 		version(win32_widgets) {
10317 			AppendMenuW(handle, MF_STRING | MF_POPUP, cast(UINT) item.handle, toWstringzInternal(item.label));
10318 		} else version(custom_widgets) {
10319 			mbItem.defaultEventHandlers["mousedown"] = (Widget e, Event ev) {
10320 				item.popup(mbItem);
10321 			};
10322 		} else static assert(false);
10323 
10324 		return item;
10325 	}
10326 
10327 	override void recomputeChildLayout() {
10328 		.recomputeChildLayout!"width"(this);
10329 	}
10330 
10331 	override int maxHeight() { return defaultLineHeight + 4; }
10332 	override int minHeight() { return defaultLineHeight + 4; }
10333 }
10334 
10335 
10336 /**
10337 	Status bars appear at the bottom of a MainWindow.
10338 	They are made out of Parts, with a width and content.
10339 
10340 	They can have multiple parts or be in simple mode. FIXME: implement simple mode.
10341 
10342 
10343 	sb.parts[0].content = "Status bar text!";
10344 */
10345 class StatusBar : Widget {
10346 	private Part[] partsArray;
10347 	///
10348 	struct Parts {
10349 		@disable this();
10350 		this(StatusBar owner) { this.owner = owner; }
10351 		//@disable this(this);
10352 		///
10353 		@property int length() { return cast(int) owner.partsArray.length; }
10354 		private StatusBar owner;
10355 		private this(StatusBar owner, Part[] parts) {
10356 			this.owner.partsArray = parts;
10357 			this.owner = owner;
10358 		}
10359 		///
10360 		Part opIndex(int p) {
10361 			if(owner.partsArray.length == 0)
10362 				this ~= new StatusBar.Part(0);
10363 			return owner.partsArray[p];
10364 		}
10365 
10366 		///
10367 		Part opOpAssign(string op : "~" )(Part p) {
10368 			assert(owner.partsArray.length < 255);
10369 			p.owner = this.owner;
10370 			p.idx = cast(int) owner.partsArray.length;
10371 			owner.partsArray ~= p;
10372 
10373 			owner.recomputeChildLayout();
10374 
10375 			version(win32_widgets) {
10376 				int[256] pos;
10377 				int cpos;
10378 				foreach(idx, part; owner.partsArray) {
10379 					if(idx + 1 == owner.partsArray.length)
10380 						pos[idx] = -1;
10381 					else {
10382 						cpos += part.currentlyAssignedWidth;
10383 						pos[idx] = cpos;
10384 					}
10385 				}
10386 				SendMessageW(owner.hwnd, WM_USER + 4 /*SB_SETPARTS*/, owner.partsArray.length, cast(size_t) pos.ptr);
10387 			} else version(custom_widgets) {
10388 				owner.redraw();
10389 			} else static assert(false);
10390 
10391 			return p;
10392 		}
10393 	}
10394 
10395 	private Parts _parts;
10396 	///
10397 	final @property Parts parts() {
10398 		return _parts;
10399 	}
10400 
10401 	/++
10402 
10403 	+/
10404 	static class Part {
10405 		/++
10406 			History:
10407 				Added September 1, 2023 (dub v11.1)
10408 		+/
10409 		enum WidthUnits {
10410 			/++
10411 				Unscaled pixels as they appear on screen.
10412 
10413 				If you pass 0, it will treat it as a [Proportional] unit for compatibility with code written against older versions of minigui.
10414 			+/
10415 			DeviceDependentPixels,
10416 			/++
10417 				Pixels at the assumed DPI, but will be automatically scaled with the rest of the ui.
10418 			+/
10419 			DeviceIndependentPixels,
10420 			/++
10421 				An approximate character count in the currently selected font (at layout time) of the status bar. This will use the x-width (similar to css `ch`).
10422 			+/
10423 			ApproximateCharacters,
10424 			/++
10425 				These take a proportion of the remaining space in the window after all other parts have been assigned. The sum of all proportional parts is then divided by the current item to get the amount of space it uses.
10426 
10427 				If you pass 0, it will assume that this item takes an average of all remaining proportional space. This is there primarily to provide compatibility with code written against older versions of minigui.
10428 			+/
10429 			Proportional
10430 		}
10431 		private WidthUnits units;
10432 		private int width;
10433 		private StatusBar owner;
10434 
10435 		private int currentlyAssignedWidth;
10436 
10437 		/++
10438 			History:
10439 				Prior to September 1, 2023, this took a default value of 100 and was interpreted as pixels, unless the value was 0 and it was the last item in the list, in which case it would use the remaining space in the window.
10440 
10441 				It now allows you to provide your own value for [WidthUnits].
10442 
10443 				Additionally, the default value used to be an arbitrary value of 100. It is now 0, to take advantage of the automatic proportional calculator in the new version. If you want the old behavior, pass `100, StatusBar.Part.WidthUnits.DeviceIndependentPixels`.
10444 		+/
10445 		this(int w, WidthUnits units = WidthUnits.Proportional) {
10446 			this.units = units;
10447 			this.width = w;
10448 		}
10449 
10450 		/// ditto
10451 		this(int w = 0) {
10452 			if(w == 0)
10453 				this(w, WidthUnits.Proportional);
10454 			else
10455 				this(w, WidthUnits.DeviceDependentPixels);
10456 		}
10457 
10458 		private int idx;
10459 		private string _content;
10460 		///
10461 		@property string content() { return _content; }
10462 		///
10463 		@property void content(string s) {
10464 			version(win32_widgets) {
10465 				_content = s;
10466 				WCharzBuffer bfr = WCharzBuffer(s);
10467 				SendMessageW(owner.hwnd, SB_SETTEXT, idx, cast(LPARAM) bfr.ptr);
10468 			} else version(custom_widgets) {
10469 				if(_content != s) {
10470 					_content = s;
10471 					owner.redraw();
10472 				}
10473 			} else static assert(false);
10474 		}
10475 	}
10476 	string simpleModeContent;
10477 	bool inSimpleMode;
10478 
10479 
10480 	///
10481 	this(Widget parent) {
10482 		super(null); // FIXME
10483 		_parts = Parts(this);
10484 		tabStop = false;
10485 		version(win32_widgets) {
10486 			parentWindow = parent.parentWindow;
10487 			createWin32Window(this, "msctls_statusbar32"w, "", 0);
10488 
10489 			RECT rect;
10490 			GetWindowRect(hwnd, &rect);
10491 			idealHeight = rect.bottom - rect.top;
10492 			assert(idealHeight);
10493 		} else version(custom_widgets) {
10494 		} else static assert(false);
10495 	}
10496 
10497 	override void recomputeChildLayout() {
10498 		int remainingLength = this.width;
10499 
10500 		int proportionalSum;
10501 		int proportionalCount;
10502 		foreach(idx, part; this.partsArray) {
10503 			with(Part.WidthUnits)
10504 			final switch(part.units) {
10505 				case DeviceDependentPixels:
10506 					part.currentlyAssignedWidth = part.width;
10507 					remainingLength -= part.currentlyAssignedWidth;
10508 				break;
10509 				case DeviceIndependentPixels:
10510 					part.currentlyAssignedWidth = scaleWithDpi(part.width);
10511 					remainingLength -= part.currentlyAssignedWidth;
10512 				break;
10513 				case ApproximateCharacters:
10514 					auto cs = getComputedStyle();
10515 					auto font = cs.font;
10516 
10517 					part.currentlyAssignedWidth = font.averageWidth * this.width;
10518 					remainingLength -= part.currentlyAssignedWidth;
10519 				break;
10520 				case Proportional:
10521 					proportionalSum += part.width;
10522 					proportionalCount ++;
10523 				break;
10524 			}
10525 		}
10526 
10527 		foreach(part; this.partsArray) {
10528 			if(part.units == Part.WidthUnits.Proportional) {
10529 				auto proportion = part.width == 0 ? proportionalSum / proportionalCount : part.width;
10530 				if(proportion == 0)
10531 					proportion = 1;
10532 
10533 				if(proportionalSum == 0)
10534 					proportionalSum = proportionalCount;
10535 
10536 				part.currentlyAssignedWidth = remainingLength * proportion / proportionalSum;
10537 			}
10538 		}
10539 
10540 		super.recomputeChildLayout();
10541 	}
10542 
10543 	version(win32_widgets)
10544 	override protected void dpiChanged() {
10545 		RECT rect;
10546 		GetWindowRect(hwnd, &rect);
10547 		idealHeight = rect.bottom - rect.top;
10548 		assert(idealHeight);
10549 	}
10550 
10551 	version(custom_widgets)
10552 	override void paint(WidgetPainter painter) {
10553 		auto cs = getComputedStyle();
10554 		this.draw3dFrame(painter, FrameStyle.sunk, cs.background.color);
10555 		int cpos = 0;
10556 		foreach(idx, part; this.partsArray) {
10557 			auto partWidth = part.currentlyAssignedWidth;
10558 			// part.width ? part.width : ((idx + 1 == this.partsArray.length) ? remainingLength : 100);
10559 			painter.setClipRectangle(Point(cpos, 0), partWidth, height);
10560 			draw3dFrame(cpos, 0, partWidth, height, painter, FrameStyle.sunk, cs.background.color);
10561 			painter.setClipRectangle(Point(cpos + 2, 2), partWidth - 4, height - 4);
10562 
10563 			painter.outlineColor = cs.foregroundColor();
10564 			painter.fillColor = cs.foregroundColor();
10565 
10566 			painter.drawText(Point(cpos + 4, 0), part.content, Point(width, height), TextAlignment.VerticalCenter);
10567 			cpos += partWidth;
10568 		}
10569 	}
10570 
10571 
10572 	version(win32_widgets) {
10573 		private int idealHeight;
10574 		override int maxHeight() { return idealHeight; }
10575 		override int minHeight() { return idealHeight; }
10576 	} else version(custom_widgets) {
10577 		override int maxHeight() { return defaultLineHeight + 4; }
10578 		override int minHeight() { return defaultLineHeight + 4; }
10579 	} else static assert(false);
10580 }
10581 
10582 /// Displays an in-progress indicator without known values
10583 version(none)
10584 class IndefiniteProgressBar : Widget {
10585 	version(win32_widgets)
10586 	this(Widget parent) {
10587 		super(parent);
10588 		createWin32Window(this, "msctls_progress32"w, "", 8 /* PBS_MARQUEE */);
10589 		tabStop = false;
10590 	}
10591 	override int minHeight() { return 10; }
10592 }
10593 
10594 /// A progress bar with a known endpoint and completion amount
10595 class ProgressBar : Widget {
10596 	/++
10597 		History:
10598 			Added March 16, 2022 (dub v10.7)
10599 	+/
10600 	this(int min, int max, Widget parent) {
10601 		this(parent);
10602 		setRange(cast(ushort) min, cast(ushort) max); // FIXME
10603 	}
10604 	this(Widget parent) {
10605 		version(win32_widgets) {
10606 			super(parent);
10607 			createWin32Window(this, "msctls_progress32"w, "", 0);
10608 			tabStop = false;
10609 		} else version(custom_widgets) {
10610 			super(parent);
10611 			max = 100;
10612 			step = 10;
10613 			tabStop = false;
10614 		} else static assert(0);
10615 	}
10616 
10617 	version(custom_widgets)
10618 	override void paint(WidgetPainter painter) {
10619 		auto cs = getComputedStyle();
10620 		this.draw3dFrame(painter, FrameStyle.sunk, cs.background.color);
10621 		painter.fillColor = cs.progressBarColor;
10622 		painter.drawRectangle(Point(0, 0), width * current / max, height);
10623 	}
10624 
10625 
10626 	version(custom_widgets) {
10627 		int current;
10628 		int max;
10629 		int step;
10630 	}
10631 
10632 	///
10633 	void advanceOneStep() {
10634 		version(win32_widgets)
10635 			SendMessageW(hwnd, PBM_STEPIT, 0, 0);
10636 		else version(custom_widgets)
10637 			addToPosition(step);
10638 		else static assert(false);
10639 	}
10640 
10641 	///
10642 	void setStepIncrement(int increment) {
10643 		version(win32_widgets)
10644 			SendMessageW(hwnd, PBM_SETSTEP, increment, 0);
10645 		else version(custom_widgets)
10646 			step = increment;
10647 		else static assert(false);
10648 	}
10649 
10650 	///
10651 	void addToPosition(int amount) {
10652 		version(win32_widgets)
10653 			SendMessageW(hwnd, PBM_DELTAPOS, amount, 0);
10654 		else version(custom_widgets)
10655 			setPosition(current + amount);
10656 		else static assert(false);
10657 	}
10658 
10659 	///
10660 	void setPosition(int pos) {
10661 		version(win32_widgets)
10662 			SendMessageW(hwnd, PBM_SETPOS, pos, 0);
10663 		else version(custom_widgets) {
10664 			current = pos;
10665 			if(current > max)
10666 				current = max;
10667 			redraw();
10668 		}
10669 		else static assert(false);
10670 	}
10671 
10672 	///
10673 	void setRange(ushort min, ushort max) {
10674 		version(win32_widgets)
10675 			SendMessageW(hwnd, PBM_SETRANGE, 0, MAKELONG(min, max));
10676 		else version(custom_widgets) {
10677 			this.max = max;
10678 		}
10679 		else static assert(false);
10680 	}
10681 
10682 	override int minHeight() { return 10; }
10683 }
10684 
10685 version(custom_widgets)
10686 private void extractWindowsStyleLabel(scope const char[] label, out string thisLabel, out dchar thisAccelerator) {
10687 	thisLabel.reserve(label.length);
10688 	bool justSawAmpersand;
10689 	foreach(ch; label) {
10690 		if(justSawAmpersand) {
10691 			justSawAmpersand = false;
10692 			if(ch == '&') {
10693 				goto plain;
10694 			}
10695 			thisAccelerator = ch;
10696 		} else {
10697 			if(ch == '&') {
10698 				justSawAmpersand = true;
10699 				continue;
10700 			}
10701 			plain:
10702 			thisLabel ~= ch;
10703 		}
10704 	}
10705 }
10706 
10707 /++
10708 	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.
10709 
10710 
10711 	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
10712 
10713 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
10714 
10715 	History:
10716 		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.
10717 +/
10718 class Fieldset : Widget {
10719 	// FIXME: on Windows,it doesn't draw the background on the label
10720 	// on X, it doesn't fix the clipping rectangle for it
10721 	version(win32_widgets)
10722 		override int paddingTop() { return defaultLineHeight; }
10723 	else version(custom_widgets)
10724 		override int paddingTop() { return defaultLineHeight + 2; }
10725 	else static assert(false);
10726 	override int paddingBottom() { return 6; }
10727 	override int paddingLeft() { return 6; }
10728 	override int paddingRight() { return 6; }
10729 
10730 	override int marginLeft() { return 6; }
10731 	override int marginRight() { return 6; }
10732 	override int marginTop() { return 2; }
10733 	override int marginBottom() { return 2; }
10734 
10735 	string legend;
10736 
10737 	version(custom_widgets) private dchar accelerator;
10738 
10739 	this(string legend, Widget parent) {
10740 		version(win32_widgets) {
10741 			super(parent);
10742 			this.legend = legend;
10743 			createWin32Window(this, "button"w, legend, BS_GROUPBOX);
10744 			tabStop = false;
10745 		} else version(custom_widgets) {
10746 			super(parent);
10747 			tabStop = false;
10748 
10749 			legend.extractWindowsStyleLabel(this.legend, this.accelerator);
10750 		} else static assert(0);
10751 	}
10752 
10753 	version(custom_widgets)
10754 	override void paint(WidgetPainter painter) {
10755 		auto dlh = defaultLineHeight;
10756 
10757 		painter.fillColor = Color.transparent;
10758 		auto cs = getComputedStyle();
10759 		painter.pen = Pen(cs.foregroundColor, 1);
10760 		painter.drawRectangle(Point(0, dlh / 2), width, height - dlh / 2);
10761 
10762 		auto tx = painter.textSize(legend);
10763 		painter.outlineColor = Color.transparent;
10764 
10765 		static if(UsingSimpledisplayX11) {
10766 			painter.fillColor = getComputedStyle().windowBackgroundColor;
10767 			painter.drawRectangle(Point(8, 0), tx.width, tx.height);
10768 		} else version(Windows) {
10769 			auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
10770 			painter.drawRectangle(Point(8, -tx.height/2), tx.width, tx.height);
10771 			SelectObject(painter.impl.hdc, b);
10772 		} else static assert(0);
10773 		painter.outlineColor = cs.foregroundColor;
10774 		painter.drawText(Point(8, 0), legend);
10775 	}
10776 
10777 	override int maxHeight() {
10778 		auto m = paddingTop() + paddingBottom();
10779 		foreach(child; children) {
10780 			auto mh = child.maxHeight();
10781 			if(mh == int.max)
10782 				return int.max;
10783 			m += mh;
10784 			m += child.marginBottom();
10785 			m += child.marginTop();
10786 		}
10787 		m += 6;
10788 		if(m < minHeight)
10789 			return minHeight;
10790 		return m;
10791 	}
10792 
10793 	override int minHeight() {
10794 		auto m = paddingTop() + paddingBottom();
10795 		foreach(child; children) {
10796 			m += child.minHeight();
10797 			m += child.marginBottom();
10798 			m += child.marginTop();
10799 		}
10800 		return m + 6;
10801 	}
10802 
10803 	override int minWidth() {
10804 		return 6 + cast(int) this.legend.length * 7;
10805 	}
10806 }
10807 
10808 /++
10809 	$(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")
10810 	$(IMG //arsdnet.net/minigui-screenshots/linux/Fieldset.png, Same thing, but in the default Linux theme.)
10811 +/
10812 version(minigui_screenshots)
10813 @Screenshot("Fieldset")
10814 unittest {
10815 	auto window = new Window(200, 100);
10816 	auto set = new Fieldset("Baby will", window);
10817 	auto option1 = new Radiobox("Eat", set);
10818 	auto option2 = new Radiobox("Cry", set);
10819 	auto option3 = new Radiobox("Sleep", set);
10820 	window.loop();
10821 }
10822 
10823 /// Draws a line
10824 class HorizontalRule : Widget {
10825 	mixin Margin!q{ 2 };
10826 	override int minHeight() { return 2; }
10827 	override int maxHeight() { return 2; }
10828 
10829 	///
10830 	this(Widget parent) {
10831 		super(parent);
10832 	}
10833 
10834 	override void paint(WidgetPainter painter) {
10835 		auto cs = getComputedStyle();
10836 		painter.outlineColor = cs.darkAccentColor;
10837 		painter.drawLine(Point(0, 0), Point(width, 0));
10838 		painter.outlineColor = cs.lightAccentColor;
10839 		painter.drawLine(Point(0, 1), Point(width, 1));
10840 	}
10841 }
10842 
10843 version(minigui_screenshots)
10844 @Screenshot("HorizontalRule")
10845 /++
10846 	$(IMG //arsdnet.net/minigui-screenshots/linux/HorizontalRule.png, Same thing, but in the default Linux theme.)
10847 
10848 +/
10849 unittest {
10850 	auto window = new Window(200, 100);
10851 	auto above = new TextLabel("Above the line", TextAlignment.Left, window);
10852 	new HorizontalRule(window);
10853 	auto below = new TextLabel("Below the line", TextAlignment.Left, window);
10854 	window.loop();
10855 }
10856 
10857 /// ditto
10858 class VerticalRule : Widget {
10859 	mixin Margin!q{ 2 };
10860 	override int minWidth() { return 2; }
10861 	override int maxWidth() { return 2; }
10862 
10863 	///
10864 	this(Widget parent) {
10865 		super(parent);
10866 	}
10867 
10868 	override void paint(WidgetPainter painter) {
10869 		auto cs = getComputedStyle();
10870 		painter.outlineColor = cs.darkAccentColor;
10871 		painter.drawLine(Point(0, 0), Point(0, height));
10872 		painter.outlineColor = cs.lightAccentColor;
10873 		painter.drawLine(Point(1, 0), Point(1, height));
10874 	}
10875 }
10876 
10877 
10878 ///
10879 class Menu : Window {
10880 	void remove() {
10881 		foreach(i, child; parentWindow.children)
10882 			if(child is this) {
10883 				parentWindow._children = parentWindow._children[0 .. i] ~ parentWindow._children[i + 1 .. $];
10884 				break;
10885 			}
10886 		parentWindow.redraw();
10887 
10888 		parentWindow.releaseMouseCapture();
10889 	}
10890 
10891 	///
10892 	void addSeparator() {
10893 		version(win32_widgets)
10894 			AppendMenu(handle, MF_SEPARATOR, 0, null);
10895 		else version(custom_widgets)
10896 			auto hr = new HorizontalRule(this);
10897 		else static assert(0);
10898 	}
10899 
10900 	override int paddingTop() { return 4; }
10901 	override int paddingBottom() { return 4; }
10902 	override int paddingLeft() { return 2; }
10903 	override int paddingRight() { return 2; }
10904 
10905 	version(win32_widgets) {}
10906 	else version(custom_widgets) {
10907 		SimpleWindow dropDown;
10908 		Widget menuParent;
10909 		void popup(Widget parent, int offsetX = 0, int offsetY = int.min) {
10910 			this.menuParent = parent;
10911 
10912 			int w = 150;
10913 			int h = paddingTop + paddingBottom;
10914 			if(this.children.length) {
10915 				// hacking it to get the ideal height out of recomputeChildLayout
10916 				this.width = w;
10917 				this.height = h;
10918 				this.recomputeChildLayout();
10919 				h = this.children[$-1].y + this.children[$-1].height + this.children[$-1].marginBottom;
10920 				h += paddingBottom;
10921 
10922 				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
10923 			}
10924 
10925 			if(offsetY == int.min)
10926 				offsetY = parent.defaultLineHeight;
10927 
10928 			auto coord = parent.globalCoordinates();
10929 			dropDown.moveResize(coord.x + offsetX, coord.y + offsetY, w, h);
10930 			this.x = 0;
10931 			this.y = 0;
10932 			this.width = dropDown.width;
10933 			this.height = dropDown.height;
10934 			this.drawableWindow = dropDown;
10935 			this.recomputeChildLayout();
10936 
10937 			static if(UsingSimpledisplayX11)
10938 				XSync(XDisplayConnection.get, 0);
10939 
10940 			dropDown.visibilityChanged = (bool visible) {
10941 				if(visible) {
10942 					this.redraw();
10943 					dropDown.grabInput();
10944 				} else {
10945 					dropDown.releaseInputGrab();
10946 				}
10947 			};
10948 
10949 			dropDown.show();
10950 
10951 			clickListener = this.addEventListener((scope ClickEvent ev) {
10952 				unpopup();
10953 				// need to unlock asap just in case other user handlers block...
10954 				static if(UsingSimpledisplayX11)
10955 					flushGui();
10956 			}, true /* again for asap action */);
10957 		}
10958 
10959 		EventListener clickListener;
10960 	}
10961 	else static assert(false);
10962 
10963 	version(custom_widgets)
10964 	void unpopup() {
10965 		mouseLastOver = mouseLastDownOn = null;
10966 		dropDown.hide();
10967 		if(!menuParent.parentWindow.win.closed) {
10968 			if(auto maw = cast(MouseActivatedWidget) menuParent) {
10969 				maw.setDynamicState(DynamicState.depressed, false);
10970 				maw.setDynamicState(DynamicState.hover, false);
10971 				maw.redraw();
10972 			}
10973 			// menuParent.parentWindow.win.focus();
10974 		}
10975 		clickListener.disconnect();
10976 	}
10977 
10978 	MenuItem[] items;
10979 
10980 	///
10981 	MenuItem addItem(MenuItem item) {
10982 		addChild(item);
10983 		items ~= item;
10984 		version(win32_widgets) {
10985 			AppendMenuW(handle, MF_STRING, item.action is null ? 9000 : item.action.id, toWstringzInternal(item.label));
10986 		}
10987 		return item;
10988 	}
10989 
10990 	string label;
10991 
10992 	version(win32_widgets) {
10993 		HMENU handle;
10994 		///
10995 		this(string label, Widget parent) {
10996 			// not actually passing the parent since it effs up the drawing
10997 			super(cast(Widget) null);// parent);
10998 			this.label = label;
10999 			handle = CreatePopupMenu();
11000 		}
11001 	} else version(custom_widgets) {
11002 		///
11003 		this(string label, Widget parent) {
11004 
11005 			if(dropDown) {
11006 				dropDown.close();
11007 			}
11008 			dropDown = new SimpleWindow(
11009 				150, 4,
11010 				null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.dropdownMenu, WindowFlags.dontAutoShow, parent ? parent.parentWindow.win : null);
11011 
11012 			this.label = label;
11013 
11014 			super(dropDown);
11015 		}
11016 	} else static assert(false);
11017 
11018 	override int maxHeight() { return defaultLineHeight; }
11019 	override int minHeight() { return defaultLineHeight; }
11020 
11021 	version(custom_widgets)
11022 	override void paint(WidgetPainter painter) {
11023 		this.draw3dFrame(painter, FrameStyle.risen, getComputedStyle.background.color);
11024 	}
11025 }
11026 
11027 /++
11028 	A MenuItem belongs to a [Menu] - use [Menu.addItem] to add one - and calls an [Action] when it is clicked.
11029 +/
11030 class MenuItem : MouseActivatedWidget {
11031 	Menu submenu;
11032 
11033 	Action action;
11034 	string label;
11035 
11036 	override int paddingLeft() { return 4; }
11037 
11038 	override int maxHeight() { return defaultLineHeight + 4; }
11039 	override int minHeight() { return defaultLineHeight + 4; }
11040 	override int minWidth() { return defaultTextWidth(label) + 8 + scaleWithDpi(12); }
11041 	override int maxWidth() {
11042 		if(cast(MenuBar) parent) {
11043 			return minWidth();
11044 		}
11045 		return int.max;
11046 	}
11047 	/// This should ONLY be used if there is no associated action, for example, if the menu item is just a submenu.
11048 	this(string lbl, Widget parent = null) {
11049 		super(parent);
11050 		//label = lbl; // FIXME
11051 		foreach(char ch; lbl) // FIXME
11052 			if(ch != '&') // FIXME
11053 				label ~= ch; // FIXME
11054 		tabStop = false; // these are selected some other way
11055 	}
11056 
11057 	///
11058 	this(Action action, Widget parent = null) {
11059 		assert(action !is null);
11060 		this(action.label, parent);
11061 		this.action = action;
11062 		tabStop = false; // these are selected some other way
11063 	}
11064 
11065 	version(custom_widgets)
11066 	override void paint(WidgetPainter painter) {
11067 		auto cs = getComputedStyle();
11068 		if(dynamicState & DynamicState.depressed)
11069 			this.draw3dFrame(painter, FrameStyle.sunk, cs.background.color);
11070 		if(dynamicState & DynamicState.hover)
11071 			painter.outlineColor = cs.activeMenuItemColor;
11072 		else
11073 			painter.outlineColor = cs.foregroundColor;
11074 		painter.fillColor = Color.transparent;
11075 		painter.drawText(scaleWithDpi(Point(cast(MenuBar) this.parent ? 4 : 20, 0)), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
11076 		if(action && action.accelerator !is KeyEvent.init) {
11077 			painter.drawText(scaleWithDpi(Point(cast(MenuBar) this.parent ? 4 : 20, 0)), action.accelerator.toStr(), Point(width - 4, height), TextAlignment.Right | TextAlignment.VerticalCenter);
11078 
11079 		}
11080 	}
11081 
11082 	static class Style : Widget.Style {
11083 		override bool variesWithState(ulong dynamicStateFlags) {
11084 			return super.variesWithState(dynamicStateFlags) || (dynamicStateFlags & (DynamicState.depressed | DynamicState.hover));
11085 		}
11086 	}
11087 	mixin OverrideStyle!Style;
11088 
11089 	override void defaultEventHandler_triggered(Event event) {
11090 		if(action)
11091 		foreach(handler; action.triggered)
11092 			handler();
11093 
11094 		if(auto pmenu = cast(Menu) this.parent)
11095 			pmenu.remove();
11096 
11097 		super.defaultEventHandler_triggered(event);
11098 	}
11099 }
11100 
11101 version(win32_widgets)
11102 /// A "mouse activiated widget" is really just an abstract variant of button.
11103 class MouseActivatedWidget : Widget {
11104 	@property bool isChecked() {
11105 		assert(hwnd);
11106 		return SendMessageW(hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED;
11107 
11108 	}
11109 	@property void isChecked(bool state) {
11110 		assert(hwnd);
11111 		SendMessageW(hwnd, BM_SETCHECK, state ? BST_CHECKED : BST_UNCHECKED, 0);
11112 
11113 	}
11114 
11115 	override void handleWmCommand(ushort cmd, ushort id) {
11116 		if(cmd == 0) {
11117 			auto event = new Event(EventType.triggered, this);
11118 			event.dispatch();
11119 		}
11120 	}
11121 
11122 	this(Widget parent) {
11123 		super(parent);
11124 	}
11125 }
11126 else version(custom_widgets)
11127 /// ditto
11128 class MouseActivatedWidget : Widget {
11129 	@property bool isChecked() { return isChecked_; }
11130 	@property bool isChecked(bool b) { return isChecked_ = b; }
11131 
11132 	private bool isChecked_;
11133 
11134 	this(Widget parent) {
11135 		super(parent);
11136 
11137 		addEventListener((MouseDownEvent ev) {
11138 			if(ev.button == MouseButton.left) {
11139 				setDynamicState(DynamicState.depressed, true);
11140 				setDynamicState(DynamicState.hover, true);
11141 				redraw();
11142 			}
11143 		});
11144 
11145 		addEventListener((MouseUpEvent ev) {
11146 			if(ev.button == MouseButton.left) {
11147 				setDynamicState(DynamicState.depressed, false);
11148 				setDynamicState(DynamicState.hover, false);
11149 				redraw();
11150 			}
11151 		});
11152 
11153 		addEventListener((MouseMoveEvent mme) {
11154 			if(!(mme.state & ModifierState.leftButtonDown)) {
11155 				if(dynamicState_ & DynamicState.depressed) {
11156 					setDynamicState(DynamicState.depressed, false);
11157 					redraw();
11158 				}
11159 			}
11160 		});
11161 	}
11162 
11163 	override void defaultEventHandler_focus(Event ev) {
11164 		super.defaultEventHandler_focus(ev);
11165 		this.redraw();
11166 	}
11167 	override void defaultEventHandler_blur(Event ev) {
11168 		super.defaultEventHandler_blur(ev);
11169 		setDynamicState(DynamicState.depressed, false);
11170 		this.redraw();
11171 	}
11172 	override void defaultEventHandler_keydown(KeyDownEvent ev) {
11173 		super.defaultEventHandler_keydown(ev);
11174 		if(ev.key == Key.Space || ev.key == Key.Enter || ev.key == Key.PadEnter) {
11175 			setDynamicState(DynamicState.depressed, true);
11176 			setDynamicState(DynamicState.hover, true);
11177 			this.redraw();
11178 		}
11179 	}
11180 	override void defaultEventHandler_keyup(KeyUpEvent ev) {
11181 		super.defaultEventHandler_keyup(ev);
11182 		if(!(dynamicState & DynamicState.depressed))
11183 			return;
11184 		setDynamicState(DynamicState.depressed, false);
11185 		setDynamicState(DynamicState.hover, false);
11186 		this.redraw();
11187 
11188 		auto event = new Event(EventType.triggered, this);
11189 		event.sendDirectly();
11190 	}
11191 	override void defaultEventHandler_click(ClickEvent ev) {
11192 		super.defaultEventHandler_click(ev);
11193 		if(ev.button == MouseButton.left) {
11194 			auto event = new Event(EventType.triggered, this);
11195 			event.sendDirectly();
11196 		}
11197 	}
11198 
11199 }
11200 else static assert(false);
11201 
11202 /*
11203 /++
11204 	Like the tablet thing, it would have a label, a description, and a switch slider thingy.
11205 
11206 	Basically the same as a checkbox.
11207 +/
11208 class OnOffSwitch : MouseActivatedWidget {
11209 
11210 }
11211 */
11212 
11213 /++
11214 	History:
11215 		Added June 15, 2021 (dub v10.1)
11216 +/
11217 struct ImageLabel {
11218 	/++
11219 		Defines a label+image combo used by some widgets.
11220 
11221 		If you provide just a text label, that is all the widget will try to
11222 		display. Or just an image will display just that. If you provide both,
11223 		it may display both text and image side by side or display the image
11224 		and offer text on an input event depending on the widget.
11225 
11226 		History:
11227 			The `alignment` parameter was added on September 27, 2021
11228 	+/
11229 	this(string label, TextAlignment alignment = TextAlignment.Center) {
11230 		this.label = label;
11231 		this.displayFlags = DisplayFlags.displayText;
11232 		this.alignment = alignment;
11233 	}
11234 
11235 	/// ditto
11236 	this(string label, MemoryImage image, TextAlignment alignment = TextAlignment.Center) {
11237 		this.label = label;
11238 		this.image = image;
11239 		this.displayFlags = DisplayFlags.displayText | DisplayFlags.displayImage;
11240 		this.alignment = alignment;
11241 	}
11242 
11243 	/// ditto
11244 	this(MemoryImage image, TextAlignment alignment = TextAlignment.Center) {
11245 		this.image = image;
11246 		this.displayFlags = DisplayFlags.displayImage;
11247 		this.alignment = alignment;
11248 	}
11249 
11250 	/// ditto
11251 	this(string label, MemoryImage image, int displayFlags, TextAlignment alignment = TextAlignment.Center) {
11252 		this.label = label;
11253 		this.image = image;
11254 		this.alignment = alignment;
11255 		this.displayFlags = displayFlags;
11256 	}
11257 
11258 	string label;
11259 	MemoryImage image;
11260 
11261 	enum DisplayFlags {
11262 		displayText = 1 << 0,
11263 		displayImage = 1 << 1,
11264 	}
11265 
11266 	int displayFlags = DisplayFlags.displayText | DisplayFlags.displayImage;
11267 
11268 	TextAlignment alignment;
11269 }
11270 
11271 /++
11272 	A basic checked or not checked box with an attached label.
11273 
11274 
11275 	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
11276 
11277 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
11278 
11279 	History:
11280 		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.
11281 +/
11282 class Checkbox : MouseActivatedWidget {
11283 	version(win32_widgets) {
11284 		override int maxHeight() { return scaleWithDpi(16); }
11285 		override int minHeight() { return scaleWithDpi(16); }
11286 	} else version(custom_widgets) {
11287 		private enum buttonSize = 16;
11288 		override int maxHeight() { return mymax(defaultLineHeight, scaleWithDpi(buttonSize)); }
11289 		override int minHeight() { return maxHeight(); }
11290 	} else static assert(0);
11291 
11292 	override int marginLeft() { return 4; }
11293 
11294 	override int flexBasisWidth() { return 24 + cast(int) label.length * 7; }
11295 
11296 	/++
11297 		Just an alias because I keep typing checked out of web habit.
11298 
11299 		History:
11300 			Added May 31, 2021
11301 	+/
11302 	alias checked = isChecked;
11303 
11304 	private string label;
11305 	private dchar accelerator;
11306 
11307 	/++
11308 	+/
11309 	this(string label, Widget parent) {
11310 		this(ImageLabel(label), Appearance.checkbox, parent);
11311 	}
11312 
11313 	/// ditto
11314 	this(string label, Appearance appearance, Widget parent) {
11315 		this(ImageLabel(label), appearance, parent);
11316 	}
11317 
11318 	/++
11319 		Changes the look and may change the ideal size of the widget without changing its behavior. The precise look is platform-specific.
11320 
11321 		History:
11322 			Added June 29, 2021 (dub v10.2)
11323 	+/
11324 	enum Appearance {
11325 		checkbox, /// a normal checkbox
11326 		pushbutton, /// a button that is showed as pushed when checked and up when unchecked. Similar to the bold button in a toolbar in Wordpad.
11327 		//sliderswitch,
11328 	}
11329 	private Appearance appearance;
11330 
11331 	/// ditto
11332 	private this(ImageLabel label, Appearance appearance, Widget parent) {
11333 		super(parent);
11334 		version(win32_widgets) {
11335 			this.label = label.label;
11336 
11337 			uint extraStyle;
11338 			final switch(appearance) {
11339 				case Appearance.checkbox:
11340 				break;
11341 				case Appearance.pushbutton:
11342 					extraStyle |= BS_PUSHLIKE;
11343 				break;
11344 			}
11345 
11346 			createWin32Window(this, "button"w, label.label, BS_CHECKBOX | extraStyle);
11347 		} else version(custom_widgets) {
11348 			label.label.extractWindowsStyleLabel(this.label, this.accelerator);
11349 		} else static assert(0);
11350 	}
11351 
11352 	version(custom_widgets)
11353 	override void paint(WidgetPainter painter) {
11354 		auto cs = getComputedStyle();
11355 		if(isFocused()) {
11356 			painter.pen = Pen(Color.black, 1, Pen.Style.Dotted);
11357 			painter.fillColor = cs.windowBackgroundColor;
11358 			painter.drawRectangle(Point(0, 0), width, height);
11359 			painter.pen = Pen(Color.black, 1, Pen.Style.Solid);
11360 		} else {
11361 			painter.pen = Pen(cs.windowBackgroundColor, 1, Pen.Style.Solid);
11362 			painter.fillColor = cs.windowBackgroundColor;
11363 			painter.drawRectangle(Point(0, 0), width, height);
11364 		}
11365 
11366 
11367 		painter.outlineColor = Color.black;
11368 		painter.fillColor = Color.white;
11369 		enum rectOffset = 2;
11370 		painter.drawRectangle(scaleWithDpi(Point(rectOffset, rectOffset)), scaleWithDpi(buttonSize - rectOffset - rectOffset), scaleWithDpi(buttonSize - rectOffset - rectOffset));
11371 
11372 		if(isChecked) {
11373 			auto size = scaleWithDpi(2);
11374 			painter.pen = Pen(Color.black, size);
11375 			// I'm using height so the checkbox is square
11376 			enum padding = 3;
11377 			painter.drawLine(
11378 				scaleWithDpi(Point(rectOffset + padding, rectOffset + padding)),
11379 				scaleWithDpi(Point(buttonSize - padding - rectOffset, buttonSize - padding - rectOffset)) - Point(1 - size % 2, 1 - size % 2)
11380 			);
11381 			painter.drawLine(
11382 				scaleWithDpi(Point(buttonSize - padding - rectOffset, padding + rectOffset)) - Point(1 - size % 2, 0),
11383 				scaleWithDpi(Point(padding + rectOffset, buttonSize - padding - rectOffset)) - Point(0,1 -  size % 2)
11384 			);
11385 
11386 			painter.pen = Pen(Color.black, 1);
11387 		}
11388 
11389 		if(label !is null) {
11390 			painter.outlineColor = cs.foregroundColor();
11391 			painter.fillColor = cs.foregroundColor();
11392 
11393 			// i want the centerline of the text to be aligned with the centerline of the checkbox
11394 			/+
11395 			auto font = cs.font();
11396 			auto y = scaleWithDpi(rectOffset + buttonSize / 2) - font.height / 2;
11397 			painter.drawText(Point(scaleWithDpi(buttonSize + 4), y), label);
11398 			+/
11399 			painter.drawText(scaleWithDpi(Point(buttonSize + 4, rectOffset)), label, Point(width, height - scaleWithDpi(rectOffset)), TextAlignment.Left | TextAlignment.VerticalCenter);
11400 		}
11401 	}
11402 
11403 	override void defaultEventHandler_triggered(Event ev) {
11404 		isChecked = !isChecked;
11405 
11406 		this.emit!(ChangeEvent!bool)(&isChecked);
11407 
11408 		redraw();
11409 	}
11410 
11411 	/// Emits a change event with the checked state
11412 	mixin Emits!(ChangeEvent!bool);
11413 }
11414 
11415 /// Adds empty space to a layout.
11416 class VerticalSpacer : Widget {
11417 	///
11418 	this(Widget parent) {
11419 		super(parent);
11420 	}
11421 }
11422 
11423 /// ditto
11424 class HorizontalSpacer : Widget {
11425 	///
11426 	this(Widget parent) {
11427 		super(parent);
11428 		this.tabStop = false;
11429 	}
11430 }
11431 
11432 
11433 /++
11434 	Creates a radio button with an associated label. These are usually put inside a [Fieldset].
11435 
11436 
11437 	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
11438 
11439 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
11440 
11441 	History:
11442 		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.
11443 +/
11444 class Radiobox : MouseActivatedWidget {
11445 
11446 	version(win32_widgets) {
11447 		override int maxHeight() { return scaleWithDpi(16); }
11448 		override int minHeight() { return scaleWithDpi(16); }
11449 	} else version(custom_widgets) {
11450 		private enum buttonSize = 16;
11451 		override int maxHeight() { return mymax(defaultLineHeight, scaleWithDpi(buttonSize)); }
11452 		override int minHeight() { return maxHeight(); }
11453 	} else static assert(0);
11454 
11455 	override int marginLeft() { return 4; }
11456 
11457 	// FIXME: make a label getter
11458 	private string label;
11459 	private dchar accelerator;
11460 
11461 	version(win32_widgets)
11462 	this(string label, Widget parent) {
11463 		super(parent);
11464 		this.label = label;
11465 		createWin32Window(this, "button"w, label, BS_AUTORADIOBUTTON);
11466 	}
11467 	else version(custom_widgets)
11468 	this(string label, Widget parent) {
11469 		super(parent);
11470 		label.extractWindowsStyleLabel(this.label, this.accelerator);
11471 		height = 16;
11472 		width = height + 4 + cast(int) label.length * 16;
11473 	}
11474 	else static assert(false);
11475 
11476 	version(custom_widgets)
11477 	override void paint(WidgetPainter painter) {
11478 		auto cs = getComputedStyle();
11479 
11480 		if(isFocused) {
11481 			painter.fillColor = cs.windowBackgroundColor;
11482 			painter.pen = Pen(Color.black, 1, Pen.Style.Dotted);
11483 		} else {
11484 			painter.fillColor = cs.windowBackgroundColor;
11485 			painter.outlineColor = cs.windowBackgroundColor;
11486 		}
11487 		painter.drawRectangle(Point(0, 0), width, height);
11488 
11489 		painter.pen = Pen(Color.black, 1, Pen.Style.Solid);
11490 
11491 		painter.outlineColor = Color.black;
11492 		painter.fillColor = Color.white;
11493 		painter.drawEllipse(scaleWithDpi(Point(2, 2)), scaleWithDpi(Point(buttonSize - 2, buttonSize - 2)));
11494 		if(isChecked) {
11495 			painter.outlineColor = Color.black;
11496 			painter.fillColor = Color.black;
11497 			// I'm using height so the checkbox is square
11498 			painter.drawEllipse(scaleWithDpi(Point(5, 5)), scaleWithDpi(Point(buttonSize - 5, buttonSize - 5)));
11499 		}
11500 
11501 		painter.outlineColor = cs.foregroundColor();
11502 		painter.fillColor = cs.foregroundColor();
11503 
11504 		painter.drawText(scaleWithDpi(Point(buttonSize + 4, 0)), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
11505 	}
11506 
11507 
11508 	override void defaultEventHandler_triggered(Event ev) {
11509 		isChecked = true;
11510 
11511 		if(this.parent) {
11512 			foreach(child; this.parent.children) {
11513 				if(child is this) continue;
11514 				if(auto rb = cast(Radiobox) child) {
11515 					rb.isChecked = false;
11516 					rb.emit!(ChangeEvent!bool)(&rb.isChecked);
11517 					rb.redraw();
11518 				}
11519 			}
11520 		}
11521 
11522 		this.emit!(ChangeEvent!bool)(&this.isChecked);
11523 
11524 		redraw();
11525 	}
11526 
11527 	/// 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.
11528 	mixin Emits!(ChangeEvent!bool);
11529 }
11530 
11531 
11532 /++
11533 	Creates a push button with unbounded size. When it is clicked, it emits a `triggered` event.
11534 
11535 
11536 	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
11537 
11538 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
11539 
11540 	History:
11541 		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.
11542 +/
11543 class Button : MouseActivatedWidget {
11544 	override int heightStretchiness() { return 3; }
11545 	override int widthStretchiness() { return 3; }
11546 
11547 	/++
11548 		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.
11549 
11550 		History:
11551 			Added July 2, 2021
11552 	+/
11553 	public bool triggersOnMultiClick;
11554 
11555 	private string label_;
11556 	private TextAlignment alignment;
11557 	private dchar accelerator;
11558 
11559 	///
11560 	string label() { return label_; }
11561 	///
11562 	void label(string l) {
11563 		label_ = l;
11564 		version(win32_widgets) {
11565 			WCharzBuffer bfr = WCharzBuffer(l);
11566 			SetWindowTextW(hwnd, bfr.ptr);
11567 		} else version(custom_widgets) {
11568 			redraw();
11569 		}
11570 	}
11571 
11572 	override void defaultEventHandler_dblclick(DoubleClickEvent ev) {
11573 		super.defaultEventHandler_dblclick(ev);
11574 		if(triggersOnMultiClick) {
11575 			if(ev.button == MouseButton.left) {
11576 				auto event = new Event(EventType.triggered, this);
11577 				event.sendDirectly();
11578 			}
11579 		}
11580 	}
11581 
11582 	private Sprite sprite;
11583 	private int displayFlags;
11584 
11585 	/++
11586 		Creates a push button with the given label, which may be an image or some text.
11587 
11588 		Bugs:
11589 			If the image is bigger than the button, it may not be displayed in the right position on Linux.
11590 
11591 		History:
11592 			The [ImageLabel] overload was added on June 21, 2021 (dub v10.1).
11593 
11594 			The button with label and image will respect requests to show both on Windows as
11595 			of March 28, 2022 iff you provide a manifest file to opt into common controls v6.
11596 	+/
11597 	this(ImageLabel label, Widget parent) {
11598 		version(win32_widgets) {
11599 			// FIXME: use ideal button size instead
11600 			width = 50;
11601 			height = 30;
11602 			super(parent);
11603 
11604 			// BS_BITMAP is set when we want image only, so checking for exactly that combination
11605 			enum imgFlags = ImageLabel.DisplayFlags.displayImage | ImageLabel.DisplayFlags.displayText;
11606 			auto extraStyle = ((label.displayFlags & imgFlags) == ImageLabel.DisplayFlags.displayImage) ? BS_BITMAP : 0;
11607 
11608 			// the transparent thing can mess up borders in other cases, so only going to keep it for bitmap things where it might matter
11609 			createWin32Window(this, "button"w, label.label, BS_PUSHBUTTON | extraStyle, extraStyle == BS_BITMAP ? WS_EX_TRANSPARENT : 0 );
11610 
11611 			if(label.image) {
11612 				sprite = Sprite.fromMemoryImage(parentWindow.win, label.image, true);
11613 
11614 				SendMessageW(hwnd, BM_SETIMAGE, IMAGE_BITMAP, cast(LPARAM) sprite.nativeHandle);
11615 			}
11616 
11617 			this.label = label.label;
11618 		} else version(custom_widgets) {
11619 			width = 50;
11620 			height = 30;
11621 			super(parent);
11622 
11623 			label.label.extractWindowsStyleLabel(this.label_, this.accelerator);
11624 
11625 			if(label.image) {
11626 				this.sprite = Sprite.fromMemoryImage(parentWindow.win, label.image);
11627 				this.displayFlags = label.displayFlags;
11628 			}
11629 
11630 			this.alignment = label.alignment;
11631 		}
11632 	}
11633 
11634 	///
11635 	this(string label, Widget parent) {
11636 		this(ImageLabel(label), parent);
11637 	}
11638 
11639 	override int minHeight() { return defaultLineHeight + 4; }
11640 
11641 	static class Style : Widget.Style {
11642 		override WidgetBackground background() {
11643 			auto cs = widget.getComputedStyle(); // FIXME: this is potentially recursive
11644 
11645 			auto pressed = DynamicState.depressed | DynamicState.hover;
11646 			if((widget.dynamicState & pressed) == pressed) {
11647 				return WidgetBackground(cs.depressedButtonColor());
11648 			} else if(widget.dynamicState & DynamicState.hover) {
11649 				return WidgetBackground(cs.hoveringColor());
11650 			} else {
11651 				return WidgetBackground(cs.buttonColor());
11652 			}
11653 		}
11654 
11655 		override FrameStyle borderStyle() {
11656 			auto pressed = DynamicState.depressed | DynamicState.hover;
11657 			if((widget.dynamicState & pressed) == pressed) {
11658 				return FrameStyle.sunk;
11659 			} else {
11660 				return FrameStyle.risen;
11661 			}
11662 
11663 		}
11664 
11665 		override bool variesWithState(ulong dynamicStateFlags) {
11666 			return super.variesWithState(dynamicStateFlags) || (dynamicStateFlags & (DynamicState.depressed | DynamicState.hover));
11667 		}
11668 	}
11669 	mixin OverrideStyle!Style;
11670 
11671 	version(custom_widgets)
11672 	override void paint(WidgetPainter painter) {
11673 		painter.drawThemed(delegate Rectangle(const Rectangle bounds) {
11674 			if(sprite) {
11675 				sprite.drawAt(
11676 					painter,
11677 					bounds.upperLeft + Point((bounds.width - sprite.width) / 2, (bounds.height - sprite.height) / 2),
11678 					Point(0, 0)
11679 				);
11680 			} else {
11681 				painter.drawText(bounds.upperLeft, label, bounds.lowerRight, alignment | TextAlignment.VerticalCenter);
11682 			}
11683 			return bounds;
11684 		});
11685 	}
11686 
11687 	override int flexBasisWidth() {
11688 		version(win32_widgets) {
11689 			SIZE size;
11690 			SendMessage(hwnd, BCM_GETIDEALSIZE, 0, cast(LPARAM) &size);
11691 			if(size.cx == 0)
11692 				goto fallback;
11693 			return size.cx + scaleWithDpi(16);
11694 		}
11695 		fallback:
11696 			return scaleWithDpi(cast(int) label.length * 8 + 16);
11697 	}
11698 
11699 	override int flexBasisHeight() {
11700 		version(win32_widgets) {
11701 			SIZE size;
11702 			SendMessage(hwnd, BCM_GETIDEALSIZE, 0, cast(LPARAM) &size);
11703 			if(size.cy == 0)
11704 				goto fallback;
11705 			return size.cy + scaleWithDpi(6);
11706 		}
11707 		fallback:
11708 			return defaultLineHeight + 4;
11709 	}
11710 }
11711 
11712 /++
11713 	A button with a consistent size, suitable for user commands like OK and CANCEL.
11714 +/
11715 class CommandButton : Button {
11716 	this(string label, Widget parent) {
11717 		super(label, parent);
11718 	}
11719 
11720 	// FIXME: I think I can simply make this 0 stretchiness instead of max now that the flex basis is there
11721 
11722 	override int maxHeight() {
11723 		return defaultLineHeight + 4;
11724 	}
11725 
11726 	override int maxWidth() {
11727 		return defaultLineHeight * 4;
11728 	}
11729 
11730 	override int marginLeft() { return 12; }
11731 	override int marginRight() { return 12; }
11732 	override int marginTop() { return 12; }
11733 	override int marginBottom() { return 12; }
11734 }
11735 
11736 ///
11737 enum ArrowDirection {
11738 	left, ///
11739 	right, ///
11740 	up, ///
11741 	down ///
11742 }
11743 
11744 ///
11745 version(custom_widgets)
11746 class ArrowButton : Button {
11747 	///
11748 	this(ArrowDirection direction, Widget parent) {
11749 		super("", parent);
11750 		this.direction = direction;
11751 		triggersOnMultiClick = true;
11752 	}
11753 
11754 	private ArrowDirection direction;
11755 
11756 	override int minHeight() { return scaleWithDpi(16); }
11757 	override int maxHeight() { return scaleWithDpi(16); }
11758 	override int minWidth() { return scaleWithDpi(16); }
11759 	override int maxWidth() { return scaleWithDpi(16); }
11760 
11761 	override void paint(WidgetPainter painter) {
11762 		super.paint(painter);
11763 
11764 		auto cs = getComputedStyle();
11765 
11766 		painter.outlineColor = cs.foregroundColor;
11767 		painter.fillColor = cs.foregroundColor;
11768 
11769 		auto offset = Point((this.width - scaleWithDpi(16)) / 2, (this.height - scaleWithDpi(16)) / 2);
11770 
11771 		final switch(direction) {
11772 			case ArrowDirection.up:
11773 				painter.drawPolygon(
11774 					scaleWithDpi(Point(2, 10) + offset),
11775 					scaleWithDpi(Point(7, 5) + offset),
11776 					scaleWithDpi(Point(12, 10) + offset),
11777 					scaleWithDpi(Point(2, 10) + offset)
11778 				);
11779 			break;
11780 			case ArrowDirection.down:
11781 				painter.drawPolygon(
11782 					scaleWithDpi(Point(2, 6) + offset),
11783 					scaleWithDpi(Point(7, 11) + offset),
11784 					scaleWithDpi(Point(12, 6) + offset),
11785 					scaleWithDpi(Point(2, 6) + offset)
11786 				);
11787 			break;
11788 			case ArrowDirection.left:
11789 				painter.drawPolygon(
11790 					scaleWithDpi(Point(10, 2) + offset),
11791 					scaleWithDpi(Point(5, 7) + offset),
11792 					scaleWithDpi(Point(10, 12) + offset),
11793 					scaleWithDpi(Point(10, 2) + offset)
11794 				);
11795 			break;
11796 			case ArrowDirection.right:
11797 				painter.drawPolygon(
11798 					scaleWithDpi(Point(6, 2) + offset),
11799 					scaleWithDpi(Point(11, 7) + offset),
11800 					scaleWithDpi(Point(6, 12) + offset),
11801 					scaleWithDpi(Point(6, 2) + offset)
11802 				);
11803 			break;
11804 		}
11805 	}
11806 }
11807 
11808 private
11809 int[2] getChildPositionRelativeToParentOrigin(Widget c) nothrow {
11810 	int x, y;
11811 	Widget par = c;
11812 	while(par) {
11813 		x += par.x;
11814 		y += par.y;
11815 		par = par.parent;
11816 	}
11817 	return [x, y];
11818 }
11819 
11820 version(win32_widgets)
11821 private
11822 int[2] getChildPositionRelativeToParentHwnd(Widget c) nothrow {
11823 // MapWindowPoints?
11824 	int x, y;
11825 	Widget par = c;
11826 	while(par) {
11827 		x += par.x;
11828 		y += par.y;
11829 		par = par.parent;
11830 		if(par !is null && par.useNativeDrawing())
11831 			break;
11832 	}
11833 	return [x, y];
11834 }
11835 
11836 ///
11837 class ImageBox : Widget {
11838 	private MemoryImage image_;
11839 
11840 	override int widthStretchiness() { return 1; }
11841 	override int heightStretchiness() { return 1; }
11842 	override int widthShrinkiness() { return 1; }
11843 	override int heightShrinkiness() { return 1; }
11844 
11845 	override int flexBasisHeight() {
11846 		return image_.height;
11847 	}
11848 
11849 	override int flexBasisWidth() {
11850 		return image_.width;
11851 	}
11852 
11853 	///
11854 	public void setImage(MemoryImage image){
11855 		this.image_ = image;
11856 		if(this.parentWindow && this.parentWindow.win) {
11857 			if(sprite)
11858 				sprite.dispose();
11859 			sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_, true));
11860 		}
11861 		redraw();
11862 	}
11863 
11864 	/// How to fit the image in the box if they aren't an exact match in size?
11865 	enum HowToFit {
11866 		center, /// centers the image, cropping around all the edges as needed
11867 		crop, /// always draws the image in the upper left, cropping the lower right if needed
11868 		// stretch, /// not implemented
11869 	}
11870 
11871 	private Sprite sprite;
11872 	private HowToFit howToFit_;
11873 
11874 	private Color backgroundColor_;
11875 
11876 	///
11877 	this(MemoryImage image, HowToFit howToFit, Color backgroundColor, Widget parent) {
11878 		this.image_ = image;
11879 		this.tabStop = false;
11880 		this.howToFit_ = howToFit;
11881 		this.backgroundColor_ = backgroundColor;
11882 		super(parent);
11883 		updateSprite();
11884 	}
11885 
11886 	/// ditto
11887 	this(MemoryImage image, HowToFit howToFit, Widget parent) {
11888 		this(image, howToFit, Color.transparent, parent);
11889 	}
11890 
11891 	private void updateSprite() {
11892 		if(sprite is null && this.parentWindow && this.parentWindow.win) {
11893 			sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_, true));
11894 		}
11895 	}
11896 
11897 	override void paint(WidgetPainter painter) {
11898 		updateSprite();
11899 		if(backgroundColor_.a) {
11900 			painter.fillColor = backgroundColor_;
11901 			painter.drawRectangle(Point(0, 0), width, height);
11902 		}
11903 		if(howToFit_ == HowToFit.crop)
11904 			sprite.drawAt(painter, Point(0, 0));
11905 		else if(howToFit_ == HowToFit.center) {
11906 			sprite.drawAt(painter, Point((width - image_.width) / 2, (height - image_.height) / 2));
11907 		}
11908 	}
11909 }
11910 
11911 ///
11912 class TextLabel : Widget {
11913 	override int minHeight() { return borderBoxForContentBox(Rectangle(Point(0, 0), Size(0, defaultTextHeight()))).height; }
11914 	override int maxHeight() { return minHeight; }
11915 	override int minWidth() { return 32; }
11916 
11917 	override int flexBasisHeight() { return minHeight(); }
11918 	override int flexBasisWidth() { return defaultTextWidth(label); }
11919 
11920 	string label_;
11921 
11922 	/++
11923 		Indicates which other control this label is here for. Similar to HTML `for` attribute.
11924 
11925 		In practice this means a click on the label will focus the `labelFor`. In future versions
11926 		it will also set screen reader hints but that is not yet implemented.
11927 
11928 		History:
11929 			Added October 3, 2021 (dub v10.4)
11930 	+/
11931 	Widget labelFor;
11932 
11933 	///
11934 	@scriptable
11935 	string label() { return label_; }
11936 
11937 	///
11938 	@scriptable
11939 	void label(string l) {
11940 		label_ = l;
11941 		version(win32_widgets) {
11942 			WCharzBuffer bfr = WCharzBuffer(l);
11943 			SetWindowTextW(hwnd, bfr.ptr);
11944 		} else version(custom_widgets)
11945 			redraw();
11946 	}
11947 
11948 	override void defaultEventHandler_click(scope ClickEvent ce) {
11949 		if(this.labelFor !is null)
11950 			this.labelFor.focus();
11951 	}
11952 
11953 	/++
11954 		WARNING: this currently sets TextAlignment.Right as the default. That will change in a future version.
11955 		For future-proofing of your code, if you rely on TextAlignment.Right, you MUST specify that explicitly.
11956 	+/
11957 	this(string label, TextAlignment alignment, Widget parent) {
11958 		this.label_ = label;
11959 		this.alignment = alignment;
11960 		this.tabStop = false;
11961 		super(parent);
11962 
11963 		version(win32_widgets)
11964 		createWin32Window(this, "static"w, label, (alignment & TextAlignment.Center) ? SS_CENTER : 0, (alignment & TextAlignment.Right) ? WS_EX_RIGHT : WS_EX_LEFT);
11965 	}
11966 
11967 	/// ditto
11968 	this(string label, Widget parent) {
11969 		this(label, TextAlignment.Right, parent);
11970 	}
11971 
11972 	TextAlignment alignment;
11973 
11974 	version(custom_widgets)
11975 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
11976 		painter.outlineColor = getComputedStyle().foregroundColor;
11977 		painter.drawText(bounds.upperLeft, this.label, bounds.lowerRight, alignment);
11978 		return bounds;
11979 	}
11980 
11981 }
11982 
11983 version(custom_widgets)
11984 	private struct etc {
11985 		mixin ExperimentalTextComponent;
11986 	}
11987 
11988 version(win32_widgets)
11989 	alias EditableTextWidgetParent = Widget; ///
11990 else version(custom_widgets) {
11991 	version(trash_text) {
11992 		alias EditableTextWidgetParent = ScrollableWidget; ///
11993 	} else {
11994 		alias EditableTextWidgetParent = Widget;
11995 		version=use_new_text_system;
11996 		import arsd.textlayouter;
11997 	}
11998 } else static assert(0);
11999 
12000 version(use_new_text_system)
12001 class TextDisplayHelper : Widget {
12002 	protected TextLayouter l;
12003 	protected ScrollMessageWidget smw;
12004 
12005 	private const(TextLayouter.State)*[] undoStack;
12006 	private const(TextLayouter.State)*[] redoStack;
12007 
12008 	bool readonly;
12009 	bool caretNavigation; // scroll lock can flip this
12010 	bool singleLine;
12011 	bool acceptsTabInput;
12012 
12013 	private Menu ctx;
12014 	override Menu contextMenu(int x, int y) {
12015 		if(ctx is null) {
12016 			ctx = new Menu("Actions", this);
12017 			ctx.addItem(new MenuItem(new Action("&Undo", GenericIcons.Undo, &undo)));
12018 			ctx.addItem(new MenuItem(new Action("&Redo", GenericIcons.Redo, &redo)));
12019 			ctx.addSeparator();
12020 			ctx.addItem(new MenuItem(new Action("Cu&t", GenericIcons.Cut, &cut)));
12021 			ctx.addItem(new MenuItem(new Action("&Copy", GenericIcons.Copy, &copy)));
12022 			ctx.addItem(new MenuItem(new Action("&Paste", GenericIcons.Paste, &paste)));
12023 			ctx.addItem(new MenuItem(new Action("&Delete", 0, &deleteContentOfSelection)));
12024 			ctx.addSeparator();
12025 			ctx.addItem(new MenuItem(new Action("Select &All", 0, &selectAll)));
12026 		}
12027 		return ctx;
12028 	}
12029 
12030 	override void defaultEventHandler_blur(Event ev) {
12031 		super.defaultEventHandler_blur(ev);
12032 		if(l.wasMutated()) {
12033 			auto evt = new ChangeEvent!string(this, &this.content);
12034 			evt.dispatch();
12035 			l.clearWasMutatedFlag();
12036 		}
12037 	}
12038 
12039 	private string content() {
12040 		return l.getTextString();
12041 	}
12042 
12043 	void undo() {
12044 		if(undoStack.length) {
12045 			auto state = undoStack[$-1];
12046 			undoStack = undoStack[0 .. $-1];
12047 			undoStack.assumeSafeAppend();
12048 			redoStack ~= l.saveState();
12049 			l.restoreState(state);
12050 			adjustScrollbarSizes();
12051 			scrollForCaret();
12052 			redraw();
12053 			stateCheckpoint = true;
12054 		}
12055 	}
12056 
12057 	void redo() {
12058 		if(redoStack.length) {
12059 			doStateCheckpoint();
12060 			auto state = redoStack[$-1];
12061 			redoStack = redoStack[0 .. $-1];
12062 			redoStack.assumeSafeAppend();
12063 			l.restoreState(state);
12064 			adjustScrollbarSizes();
12065 			scrollForCaret();
12066 			redraw();
12067 			stateCheckpoint = true;
12068 		}
12069 	}
12070 
12071 	void cut() {
12072 		with(l.selection()) {
12073 			if(!isEmpty()) {
12074 				setClipboardText(parentWindow.win, getContentString());
12075 				doStateCheckpoint();
12076 				replaceContent("");
12077 				adjustScrollbarSizes();
12078 				scrollForCaret();
12079 				this.redraw();
12080 			}
12081 		}
12082 
12083 	}
12084 
12085 	void copy() {
12086 		with(l.selection()) {
12087 			if(!isEmpty()) {
12088 				setClipboardText(parentWindow.win, getContentString());
12089 				this.redraw();
12090 			}
12091 		}
12092 	}
12093 
12094 	void paste() {
12095 		getClipboardText(parentWindow.win, (txt) {
12096 			doStateCheckpoint();
12097 			l.selection.replaceContent(txt);
12098 			adjustScrollbarSizes();
12099 			scrollForCaret();
12100 			this.redraw();
12101 		});
12102 	}
12103 
12104 	void deleteContentOfSelection() {
12105 		doStateCheckpoint();
12106 		l.selection.replaceContent("");
12107 		l.selection.setUserXCoordinate();
12108 		adjustScrollbarSizes();
12109 		scrollForCaret();
12110 		redraw();
12111 	}
12112 
12113 	void selectAll() {
12114 		with(l.selection) {
12115 			moveToStartOfDocument();
12116 			setAnchor();
12117 			moveToEndOfDocument();
12118 			setFocus();
12119 		}
12120 		redraw();
12121 	}
12122 
12123 	protected bool stateCheckpoint = true;
12124 
12125 	protected void doStateCheckpoint() {
12126 		if(stateCheckpoint) {
12127 			undoStack ~= l.saveState();
12128 			stateCheckpoint = false;
12129 		}
12130 	}
12131 
12132 	protected void adjustScrollbarSizes() {
12133 		// FIXME: will want a content area helper function instead of doing all these subtractions myself
12134 		auto borderWidth = 2;
12135 		this.smw.setTotalArea(l.width, l.height);
12136 		this.smw.setViewableArea(
12137 			this.width - this.paddingLeft - this.paddingRight - borderWidth * 2,
12138 			this.height - this.paddingTop - this.paddingBottom - borderWidth * 2);
12139 	}
12140 
12141 	protected void scrollForCaret() {
12142 		// writeln(l.width, "x", l.height); writeln(this.width - this.paddingLeft - this.paddingRight, " ", this.height - this.paddingTop - this.paddingBottom);
12143 		smw.scrollIntoView(l.selection.focusBoundingBox());
12144 	}
12145 
12146 	// FIXME: this should be a theme changed event listener instead
12147 	private BaseVisualTheme currentTheme;
12148 	override void recomputeChildLayout() {
12149 		if(currentTheme is null)
12150 			currentTheme = WidgetPainter.visualTheme;
12151 		if(WidgetPainter.visualTheme !is currentTheme) {
12152 			currentTheme = WidgetPainter.visualTheme;
12153 			auto ds = this.l.defaultStyle;
12154 			if(auto ms = cast(MyTextStyle) ds) {
12155 				auto cs = getComputedStyle();
12156 				auto font = cs.font();
12157 				if(font !is null)
12158 					ms.font_ = font;
12159 				else {
12160 					auto osc = new OperatingSystemFont();
12161 					osc.loadDefault;
12162 					ms.font_ = osc;
12163 				}
12164 			}
12165 		}
12166 		super.recomputeChildLayout();
12167 	}
12168 
12169 	private Point adjustForSingleLine(Point p) {
12170 		if(singleLine)
12171 			return Point(p.x, this.height / 2);
12172 		else
12173 			return p;
12174 	}
12175 
12176 	private bool wordWrapEnabled_;
12177 
12178 	this(TextLayouter l, ScrollMessageWidget parent) {
12179 		this.smw = parent;
12180 
12181 		smw.addDefaultWheelListeners(16, 16, 8);
12182 		smw.movementPerButtonClick(16, 16);
12183 
12184 		this.defaultPadding = Rectangle(2, 2, 2, 2);
12185 
12186 		this.l = l;
12187 		super(parent);
12188 
12189 		smw.addEventListener((scope ScrollEvent se) {
12190 			this.redraw();
12191 		});
12192 
12193 		bool mouseDown;
12194 
12195 		this.addEventListener((scope ResizeEvent re) {
12196 			// FIXME: I should add a method to give this client area width thing
12197 			if(wordWrapEnabled_)
12198 				this.l.wordWrapWidth = this.width - this.paddingLeft - this.paddingRight;
12199 
12200 			adjustScrollbarSizes();
12201 			scrollForCaret();
12202 
12203 			this.redraw();
12204 		});
12205 
12206 		this.addEventListener((scope KeyDownEvent kde) {
12207 			switch(kde.key) {
12208 				case Key.Up, Key.Down, Key.Left, Key.Right:
12209 				case Key.Home, Key.End:
12210 					stateCheckpoint = true;
12211 					bool setPosition = false;
12212 					switch(kde.key) {
12213 						case Key.Up: l.selection.moveUp(); break;
12214 						case Key.Down: l.selection.moveDown(); break;
12215 						case Key.Left: l.selection.moveLeft(); setPosition = true; break;
12216 						case Key.Right: l.selection.moveRight(); setPosition = true; break;
12217 						case Key.Home: l.selection.moveToStartOfLine(); setPosition = true; break;
12218 						case Key.End: l.selection.moveToEndOfLine(); setPosition = true; break;
12219 						default: assert(0);
12220 					}
12221 
12222 					if(kde.shiftKey)
12223 						l.selection.setFocus();
12224 					else
12225 						l.selection.setAnchor();
12226 					if(setPosition)
12227 						l.selection.setUserXCoordinate();
12228 					scrollForCaret();
12229 					redraw();
12230 				break;
12231 				case Key.PageUp, Key.PageDown:
12232 					// FIXME
12233 					scrollForCaret();
12234 				break;
12235 				case Key.Delete:
12236 					if(l.selection.isEmpty()) {
12237 						l.selection.setAnchor();
12238 						l.selection.moveRight();
12239 						l.selection.setFocus();
12240 					}
12241 					deleteContentOfSelection();
12242 					adjustScrollbarSizes();
12243 					scrollForCaret();
12244 				break;
12245 				case Key.Insert:
12246 				break;
12247 				case Key.A:
12248 					if(kde.ctrlKey)
12249 						selectAll();
12250 				break;
12251 				case Key.F:
12252 					// find
12253 				break;
12254 				case Key.Z:
12255 					if(kde.ctrlKey)
12256 						undo();
12257 				break;
12258 				case Key.R:
12259 					if(kde.ctrlKey)
12260 						redo();
12261 				break;
12262 				case Key.X:
12263 					if(kde.ctrlKey)
12264 						cut();
12265 				break;
12266 				case Key.C:
12267 					if(kde.ctrlKey)
12268 						copy();
12269 				break;
12270 				case Key.V:
12271 					if(kde.ctrlKey)
12272 						paste();
12273 				break;
12274 				case Key.F1:
12275 					with(l.selection()) {
12276 						moveToStartOfLine();
12277 						setAnchor();
12278 						moveToEndOfLine();
12279 						moveToIncludeAdjacentEndOfLineMarker();
12280 						setFocus();
12281 						replaceContent("");
12282 					}
12283 
12284 					redraw();
12285 				break;
12286 				/*
12287 				case Key.F2:
12288 					l.selection().changeStyle((old) => l.registerStyle(new MyTextStyle(
12289 						//(cast(MyTextStyle) old).font,
12290 						font2,
12291 						Color.red)));
12292 					redraw();
12293 				break;
12294 				*/
12295 				case Key.Tab:
12296 					// we process the char event, so don't want to change focus on it
12297 					if(acceptsTabInput)
12298 						kde.preventDefault();
12299 				break;
12300 				default:
12301 			}
12302 		});
12303 
12304 		Point downAt;
12305 
12306 		static if(UsingSimpledisplayX11)
12307 		this.addEventListener((scope ClickEvent ce) {
12308 			if(ce.button == MouseButton.middle) {
12309 				parentWindow.win.getPrimarySelection((txt) {
12310 					l.selection.replaceContent(txt);
12311 					redraw();
12312 				});
12313 			}
12314 		});
12315 
12316 		this.addEventListener((scope MouseDownEvent ce) {
12317 			if(ce.button == MouseButton.left) {
12318 				downAt = Point(ce.clientX - this.paddingLeft, ce.clientY - this.paddingTop);
12319 				l.selection.moveTo(adjustForSingleLine(smw.position + downAt));
12320 				l.selection.setAnchor();
12321 				mouseDown = true;
12322 				parentWindow.captureMouse(this);
12323 				this.redraw();
12324 			} else if(ce.button == MouseButton.right) {
12325 				this.showContextMenu(ce.clientX, ce.clientY);
12326 			}
12327 			//writeln(ce.clientX, ", ", ce.clientY, " = ", l.offsetOfClick(Point(ce.clientX, ce.clientY)));
12328 		});
12329 
12330 		Timer autoscrollTimer;
12331 		int autoscrollDirection;
12332 		int autoscrollAmount;
12333 
12334 		void autoscroll() {
12335 			switch(autoscrollDirection) {
12336 				case 0: smw.scrollUp(autoscrollAmount); break;
12337 				case 1: smw.scrollDown(autoscrollAmount); break;
12338 				case 2: smw.scrollLeft(autoscrollAmount); break;
12339 				case 3: smw.scrollRight(autoscrollAmount); break;
12340 				default: assert(0);
12341 			}
12342 
12343 			this.redraw();
12344 		}
12345 
12346 		void setAutoscrollTimer(int direction, int amount) {
12347 			if(autoscrollTimer is null) {
12348 				autoscrollTimer = new Timer(1000 / 60, &autoscroll);
12349 			}
12350 
12351 			autoscrollDirection = direction;
12352 			autoscrollAmount = amount;
12353 		}
12354 
12355 		void stopAutoscrollTimer() {
12356 			if(autoscrollTimer !is null) {
12357 				autoscrollTimer.dispose();
12358 				autoscrollTimer = null;
12359 			}
12360 			autoscrollAmount = 0;
12361 			autoscrollDirection = 0;
12362 		}
12363 
12364 		this.addEventListener((scope MouseMoveEvent ce) {
12365 			if(mouseDown) {
12366 				auto movedTo = Point(ce.clientX - this.paddingLeft, ce.clientY - this.paddingTop);
12367 
12368 				// FIXME: when scrolling i actually do want a timer.
12369 				// i also want a zone near the sides of the window where i can auto scroll
12370 
12371 				auto scrollMultiplier = scaleWithDpi(16);
12372 				auto scrollDivisor = scaleWithDpi(16); // if you go more than 64px up it will scroll faster
12373 
12374 				if(!singleLine && movedTo.y < 4) {
12375 					setAutoscrollTimer(0, scrollMultiplier * -(movedTo.y-4) / scrollDivisor);
12376 				} else
12377 				if(!singleLine && (movedTo.y + 6) > this.height) {
12378 					setAutoscrollTimer(1, scrollMultiplier * (movedTo.y + 6 - this.height) / scrollDivisor);
12379 				} else
12380 				if(movedTo.x < 4) {
12381 					setAutoscrollTimer(2, scrollMultiplier * -(movedTo.x-4) / scrollDivisor);
12382 				} else
12383 				if((movedTo.x + 6) > this.width) {
12384 					setAutoscrollTimer(3, scrollMultiplier * (movedTo.x + 6 - this.width) / scrollDivisor);
12385 				} else
12386 					stopAutoscrollTimer();
12387 
12388 				l.selection.moveTo(adjustForSingleLine(smw.position + movedTo));
12389 				l.selection.setFocus();
12390 				this.redraw();
12391 			}
12392 		});
12393 
12394 		this.addEventListener((scope MouseUpEvent ce) {
12395 			// FIXME: assert primary selection
12396 			if(mouseDown && ce.button == MouseButton.left) {
12397 				stateCheckpoint = true;
12398 				//l.selection.moveTo(adjustForSingleLine(smw.position + Point(ce.clientX - this.paddingLeft, ce.clientY - this.paddingTop)));
12399 				//l.selection.setFocus();
12400 				mouseDown = false;
12401 				parentWindow.releaseMouseCapture();
12402 				stopAutoscrollTimer();
12403 				this.redraw();
12404 			}
12405 			//writeln(ce.clientX, ", ", ce.clientY, " = ", l.offsetOfClick(Point(ce.clientX, ce.clientY)));
12406 		});
12407 
12408 		this.addEventListener((scope CharEvent ce) {
12409 			if(readonly)
12410 				return;
12411 			if(ce.character < 32 && ce.character != '\t' && ce.character != '\n' && ce.character != '\b')
12412 				return; // skip the ctrl+x characters we don't care about as plain text
12413 
12414 			if(singleLine && ce.character == '\n')
12415 				return;
12416 			if(!acceptsTabInput && ce.character == '\t')
12417 				return;
12418 
12419 			doStateCheckpoint();
12420 
12421 			char[4] buffer;
12422 			import std.utf; // FIXME: i should remove this. compile time not significant but the logs get spammed with phobos' import web
12423 			auto stride = encode(buffer, ce.character);
12424 			l.selection.replaceContent(buffer[0 .. stride]);
12425 			l.selection.setUserXCoordinate();
12426 			adjustScrollbarSizes();
12427 			scrollForCaret();
12428 			redraw();
12429 		});
12430 	}
12431 
12432 	static class Style : Widget.Style {
12433 		override WidgetBackground background() {
12434 			return WidgetBackground(WidgetPainter.visualTheme.widgetBackgroundColor);
12435 		}
12436 
12437 		override Color foregroundColor() {
12438 			return WidgetPainter.visualTheme.foregroundColor;
12439 		}
12440 
12441 		override FrameStyle borderStyle() {
12442 			return FrameStyle.sunk;
12443 		}
12444 
12445 		override MouseCursor cursor() {
12446 			return GenericCursor.Text;
12447 		}
12448 	}
12449 	mixin OverrideStyle!Style;
12450 
12451 	override int minHeight() { return borderBoxForContentBox(Rectangle(Point(0, 0), Size(0, defaultTextHeight))).height; }
12452 	override int maxHeight() {
12453 		if(singleLine)
12454 			return minHeight;
12455 		else
12456 			return super.maxHeight();
12457 	}
12458 
12459 	void drawTextSegment(WidgetPainter painter, Point upperLeft, scope const(char)[] text) {
12460 		painter.drawText(upperLeft, text);
12461 	}
12462 
12463 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
12464 		//painter.setFont(font);
12465 
12466 		auto cs = getComputedStyle();
12467 		auto defaultColor = cs.foregroundColor;
12468 
12469 		auto old = painter.setClipRectangle(bounds);
12470 		scope(exit) painter.setClipRectangle(old);
12471 
12472 		l.getDrawableText(delegate bool(txt, style, info, carets...) {
12473 			//writeln("Segment: ", txt);
12474 			assert(style !is null);
12475 
12476 			auto myStyle = cast(MyTextStyle) style;
12477 			assert(myStyle !is null);
12478 
12479 			painter.setFont(myStyle.font);
12480 			// defaultColor = myStyle.color; // FIXME: so wrong
12481 
12482 			if(info.selections && info.boundingBox.width > 0) {
12483 				auto color = this.isFocused ? cs.selectionBackgroundColor : Color(128, 128, 128); // FIXME don't hardcode
12484 				painter.fillColor = color;
12485 				painter.outlineColor = color;
12486 				painter.drawRectangle(Rectangle(info.boundingBox.upperLeft - smw.position() + bounds.upperLeft, info.boundingBox.size));
12487 				painter.outlineColor = cs.selectionForegroundColor;
12488 				//painter.fillColor = Color.white;
12489 			} else {
12490 				painter.outlineColor = defaultColor;
12491 			}
12492 
12493 			if(this.isFocused)
12494 			foreach(idx, caret; carets) {
12495 				if(idx == 0)
12496 					painter.notifyCursorPosition(caret.boundingBox.left - smw.position.x + bounds.left, caret.boundingBox.top - smw.position.y + bounds.top, caret.boundingBox.width, caret.boundingBox.height);
12497 				painter.drawLine(
12498 					caret.boundingBox.upperLeft + bounds.upperLeft - smw.position(),
12499 					bounds.upperLeft + Point(caret.boundingBox.left, caret.boundingBox.bottom) - smw.position()
12500 				);
12501 			}
12502 
12503 			if(txt.stripInternal.length) {
12504 				drawTextSegment(painter, info.boundingBox.upperLeft - smw.position() + bounds.upperLeft, txt.stripRightInternal);
12505 			}
12506 
12507 			if(info.boundingBox.upperLeft.y - smw.position().y > this.height) {
12508 				return false;
12509 			} else {
12510 				return true;
12511 			}
12512 		}, Rectangle(smw.position(), bounds.size));
12513 
12514 		/+
12515 		int place = 0;
12516 		int y = 75;
12517 		foreach(width; widths) {
12518 			painter.fillColor = Color.red;
12519 			painter.drawRectangle(Point(place, y), Size(width, 75));
12520 			//y += 15;
12521 			place += width;
12522 		}
12523 		+/
12524 
12525 		return bounds;
12526 	}
12527 
12528 	static class MyTextStyle : TextStyle {
12529 		OperatingSystemFont font_;
12530 		this(OperatingSystemFont font, bool passwordMode = false) {
12531 			this.font_ = font;
12532 		}
12533 
12534 		override OperatingSystemFont font() {
12535 			return font_;
12536 		}
12537 	}
12538 }
12539 
12540 /+
12541 version(use_new_text_system)
12542 class TextWidget : Widget {
12543 	TextLayouter l;
12544 	ScrollMessageWidget smw;
12545 	TextDisplayHelper helper;
12546 	this(TextLayouter l, Widget parent) {
12547 		this.l = l;
12548 		super(parent);
12549 
12550 		smw = new ScrollMessageWidget(this);
12551 		//smw.horizontalScrollBar.hide;
12552 		//smw.verticalScrollBar.hide;
12553 		smw.addDefaultWheelListeners(16, 16, 8);
12554 		smw.movementPerButtonClick(16, 16);
12555 		helper = new TextDisplayHelper(l, smw);
12556 
12557 		// no need to do this here since there's gonna be a resize
12558 		// event immediately before any drawing
12559 		// smw.setTotalArea(l.width, l.height);
12560 		smw.setViewableArea(
12561 			this.width - this.paddingLeft - this.paddingRight,
12562 			this.height - this.paddingTop - this.paddingBottom);
12563 
12564 		/+
12565 		writeln(l.width, "x", l.height);
12566 		+/
12567 	}
12568 }
12569 +/
12570 
12571 
12572 
12573 
12574 /+
12575 	This awful thing has to be rewritten. And it needs to takecare of parentWindow.inputProxy.setIMEPopupLocation too
12576 +/
12577 
12578 /// Contains the implementation of text editing
12579 abstract class EditableTextWidget : EditableTextWidgetParent {
12580 	this(Widget parent) {
12581 		super(parent);
12582 
12583 		version(custom_widgets)
12584 			setupCustomTextEditing();
12585 	}
12586 
12587 	private bool wordWrapEnabled_;
12588 	void wordWrapEnabled(bool enabled) {
12589 		version(win32_widgets) {
12590 			SendMessageW(hwnd, EM_FMTLINES, enabled ? 1 : 0, 0);
12591 		} else version(custom_widgets) {
12592 			wordWrapEnabled_ = enabled;
12593 			version(use_new_text_system)
12594 			textLayout.wordWrapWidth = enabled ? this.width : 0; // FIXME
12595 		} else static assert(false);
12596 	}
12597 
12598 	override int minWidth() { return scaleWithDpi(16); }
12599 	override int widthStretchiness() { return 7; }
12600 
12601 	version(use_new_text_system)
12602 	override int maxHeight() { return tdh.maxHeight; }
12603 
12604 	version(use_new_text_system)
12605 	override void focus() { if(tdh) tdh.focus(); else super.focus(); }
12606 
12607 	void selectAll() {
12608 		version(win32_widgets)
12609 			SendMessage(hwnd, EM_SETSEL, 0, -1);
12610 		else version(custom_widgets) {
12611 			version(use_new_text_system)
12612 				tdh.selectAll();
12613 			else
12614 				textLayout.selectAll();
12615 			redraw();
12616 		}
12617 	}
12618 
12619 	version(use_new_text_system)
12620 		TextDisplayHelper tdh;
12621 
12622 	@property string content() {
12623 		version(win32_widgets) {
12624 			wchar[4096] bufferstack;
12625 			wchar[] buffer;
12626 			auto len = GetWindowTextLength(hwnd);
12627 			if(len < bufferstack.length)
12628 				buffer = bufferstack[0 .. len + 1];
12629 			else
12630 				buffer = new wchar[](len + 1);
12631 
12632 			auto l = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
12633 			if(l >= 0)
12634 				return makeUtf8StringFromWindowsString(buffer[0 .. l]);
12635 			else
12636 				return null;
12637 		} else version(custom_widgets) {
12638 			version(use_new_text_system) {
12639 				return textLayout.getTextString();
12640 			} else
12641 				return textLayout.getPlainText();
12642 		} else static assert(false);
12643 	}
12644 	@property void content(string s) {
12645 		version(win32_widgets) {
12646 			WCharzBuffer bfr = WCharzBuffer(s, WindowsStringConversionFlags.convertNewLines);
12647 			SetWindowTextW(hwnd, bfr.ptr);
12648 		} else version(custom_widgets) {
12649 			version(use_new_text_system) {
12650 				selectAll();
12651 				textLayout.selection.replaceContent(s);
12652 
12653 				tdh.adjustScrollbarSizes();
12654 				// these don't seem to help
12655 				// tdh.smw.setPosition(0, 0);
12656 				// tdh.scrollForCaret();
12657 
12658 				redraw();
12659 			} else {
12660 				textLayout.clear();
12661 				textLayout.addText(s);
12662 
12663 				{
12664 				// FIXME: it should be able to get this info easier
12665 				auto painter = draw();
12666 				textLayout.redoLayout(painter);
12667 				}
12668 				auto cbb = textLayout.contentBoundingBox();
12669 				setContentSize(cbb.width, cbb.height);
12670 				/*
12671 				textLayout.addText(ForegroundColor.red, s);
12672 				textLayout.addText(ForegroundColor.blue, TextFormat.underline, "http://dpldocs.info/");
12673 				textLayout.addText(" is the best!");
12674 				*/
12675 				redraw();
12676 			}
12677 		}
12678 		else static assert(false);
12679 	}
12680 
12681 	void addText(string txt) {
12682 		version(custom_widgets) {
12683 			version(use_new_text_system) {
12684 				textLayout.appendText(txt);
12685 				tdh.adjustScrollbarSizes();
12686 				redraw();
12687 			} else {
12688 				textLayout.addText(txt);
12689 
12690 				{
12691 				// FIXME: it should be able to get this info easier
12692 				auto painter = draw();
12693 				textLayout.redoLayout(painter);
12694 				}
12695 				auto cbb = textLayout.contentBoundingBox();
12696 				setContentSize(cbb.width, cbb.height);
12697 			}
12698 		} else version(win32_widgets) {
12699 			// get the current selection
12700 			DWORD StartPos, EndPos;
12701 			SendMessageW( hwnd, EM_GETSEL, cast(WPARAM)(&StartPos), cast(LPARAM)(&EndPos) );
12702 
12703 			// move the caret to the end of the text
12704 			int outLength = GetWindowTextLengthW(hwnd);
12705 			SendMessageW( hwnd, EM_SETSEL, outLength, outLength );
12706 
12707 			// insert the text at the new caret position
12708 			WCharzBuffer bfr = WCharzBuffer(txt, WindowsStringConversionFlags.convertNewLines);
12709 			SendMessageW( hwnd, EM_REPLACESEL, TRUE, cast(LPARAM) bfr.ptr );
12710 
12711 			// restore the previous selection
12712 			SendMessageW( hwnd, EM_SETSEL, StartPos, EndPos );
12713 		} else static assert(0);
12714 	}
12715 
12716 	version(custom_widgets)
12717 	version(trash_text)
12718 	override void paintFrameAndBackground(WidgetPainter painter) {
12719 		this.draw3dFrame(painter, FrameStyle.sunk, Color.white);
12720 	}
12721 
12722 	version(use_new_text_system)
12723 	TextDisplayHelper textDisplayHelperFactory(TextLayouter textLayout, ScrollMessageWidget smw) {
12724 		return new TextDisplayHelper(textLayout, smw);
12725 	}
12726 
12727 	version(use_new_text_system)
12728 	TextStyle defaultTextStyle() {
12729 		return new TextDisplayHelper.MyTextStyle(getUsedFont());
12730 	}
12731 
12732 	version(use_new_text_system)
12733 	private OperatingSystemFont getUsedFont() {
12734 		auto cs = getComputedStyle();
12735 		auto font = cs.font;
12736 		if(font is null) {
12737 			font = new OperatingSystemFont;
12738 			font.loadDefault();
12739 		}
12740 		return font;
12741 	}
12742 
12743 	version(win32_widgets) { /* will do it with Windows calls in the classes */ }
12744 	else version(custom_widgets) {
12745 		// FIXME
12746 		version(use_new_text_system) {
12747 			TextLayouter textLayout;
12748 
12749 			void setupCustomTextEditing() {
12750 				textLayout = new TextLayouter(defaultTextStyle());
12751 				auto smw = new ScrollMessageWidget(this);
12752 				if(!showingHorizontalScroll)
12753 					smw.horizontalScrollBar.hide();
12754 				if(!showingVerticalScroll)
12755 					smw.verticalScrollBar.hide();
12756 				this.tabStop = false;
12757 				smw.tabStop = false;
12758 				tdh = textDisplayHelperFactory(textLayout, smw);
12759 
12760 				this.parentWindow.addEventListener((scope DpiChangedEvent dce) {
12761 					if(textLayout) {
12762 						if(auto style = cast(TextDisplayHelper.MyTextStyle) textLayout.defaultStyle()) {
12763 							// the dpi change can change the font, so this informs the layouter that it has changed too
12764 							style.font_ = getUsedFont();
12765 
12766 							// arsd.core.writeln(this.parentWindow.win.actualDpi);
12767 						}
12768 					}
12769 				});
12770 			}
12771 
12772 		} else {
12773 
12774 			static if(SimpledisplayTimerAvailable)
12775 				Timer caretTimer;
12776 			etc.TextLayout textLayout;
12777 
12778 			void setupCustomTextEditing() {
12779 				textLayout = new etc.TextLayout(Rectangle(4, 2, width - 8, height - 4));
12780 				textLayout.selectionXorColor = getComputedStyle().activeListXorColor;
12781 			}
12782 
12783 			override void paint(WidgetPainter painter) {
12784 				if(parentWindow.win.closed) return;
12785 
12786 				textLayout.boundingBox = Rectangle(4, 2, width - 8, height - 4);
12787 
12788 				/*
12789 				painter.outlineColor = Color.white;
12790 				painter.fillColor = Color.white;
12791 				painter.drawRectangle(Point(4, 4), contentWidth, contentHeight);
12792 				*/
12793 
12794 				painter.outlineColor = Color.black;
12795 				// painter.drawText(Point(4, 4), content, Point(width - 4, height - 4));
12796 
12797 				textLayout.caretShowingOnScreen = false;
12798 
12799 				textLayout.drawInto(painter, !parentWindow.win.closed && isFocused());
12800 			}
12801 		}
12802 
12803 		static class Style : Widget.Style {
12804 			override FrameStyle borderStyle() {
12805 				return FrameStyle.sunk;
12806 			}
12807 			override MouseCursor cursor() {
12808 				return GenericCursor.Text;
12809 			}
12810 		}
12811 		mixin OverrideStyle!Style;
12812 	}
12813 	else static assert(false);
12814 
12815 	version(trash_text)
12816 	version(custom_widgets)
12817 	override void defaultEventHandler_mousedown(MouseDownEvent ev) {
12818 		super.defaultEventHandler_mousedown(ev);
12819 		if(parentWindow.win.closed) return;
12820 		if(ev.button == MouseButton.left) {
12821 			if(textLayout.selectNone())
12822 				redraw();
12823 			textLayout.moveCaretToPixelCoordinates(ev.clientX, ev.clientY);
12824 			this.focus();
12825 			//this.parentWindow.win.grabInput();
12826 		} else if(ev.button == MouseButton.middle) {
12827 			static if(UsingSimpledisplayX11) {
12828 				getPrimarySelection(parentWindow.win, (in char[] txt) {
12829 					textLayout.insert(txt);
12830 					redraw();
12831 
12832 					auto cbb = textLayout.contentBoundingBox();
12833 					setContentSize(cbb.width, cbb.height);
12834 				});
12835 			}
12836 		}
12837 	}
12838 
12839 	version(trash_text)
12840 	version(custom_widgets)
12841 	override void defaultEventHandler_mouseup(MouseUpEvent ev) {
12842 		//this.parentWindow.win.releaseInputGrab();
12843 		super.defaultEventHandler_mouseup(ev);
12844 	}
12845 
12846 	version(trash_text)
12847 	version(custom_widgets)
12848 	override void defaultEventHandler_mousemove(MouseMoveEvent ev) {
12849 		super.defaultEventHandler_mousemove(ev);
12850 		if(ev.state & ModifierState.leftButtonDown) {
12851 			textLayout.selectToPixelCoordinates(ev.clientX, ev.clientY);
12852 			redraw();
12853 		}
12854 	}
12855 
12856 	version(trash_text)
12857 	version(custom_widgets)
12858 	override void defaultEventHandler_focus(Event ev) {
12859 		super.defaultEventHandler_focus(ev);
12860 		if(parentWindow.win.closed) return;
12861 		auto painter = this.draw();
12862 		textLayout.drawCaret(painter);
12863 
12864 		static if(SimpledisplayTimerAvailable)
12865 		if(caretTimer) {
12866 			caretTimer.destroy();
12867 			caretTimer = null;
12868 		}
12869 
12870 		bool blinkingCaret = true;
12871 		static if(UsingSimpledisplayX11)
12872 			if(!Image.impl.xshmAvailable)
12873 				blinkingCaret = false; // if on a remote connection, don't waste bandwidth on an expendable blink
12874 
12875 		if(blinkingCaret)
12876 		static if(SimpledisplayTimerAvailable)
12877 		caretTimer = new Timer(500, {
12878 			if(parentWindow.win.closed) {
12879 				caretTimer.destroy();
12880 				return;
12881 			}
12882 			if(isFocused()) {
12883 				auto painter = this.draw();
12884 				textLayout.drawCaret(painter);
12885 			} else if(textLayout.caretShowingOnScreen) {
12886 				auto painter = this.draw();
12887 				textLayout.eraseCaret(painter);
12888 			}
12889 		});
12890 	}
12891 
12892 	version(trash_text) {
12893 		private string lastContentBlur;
12894 
12895 		override void defaultEventHandler_blur(Event ev) {
12896 			super.defaultEventHandler_blur(ev);
12897 			if(parentWindow.win.closed) return;
12898 			version(custom_widgets) {
12899 				auto painter = this.draw();
12900 				textLayout.eraseCaret(painter);
12901 				static if(SimpledisplayTimerAvailable)
12902 				if(caretTimer) {
12903 					caretTimer.destroy();
12904 					caretTimer = null;
12905 				}
12906 			}
12907 
12908 			if(this.content != lastContentBlur) {
12909 				auto evt = new ChangeEvent!string(this, &this.content);
12910 				evt.dispatch();
12911 				lastContentBlur = this.content;
12912 			}
12913 		}
12914 	}
12915 
12916 	version(win32_widgets) {
12917 		private string lastContentBlur;
12918 
12919 		override void defaultEventHandler_blur(Event ev) {
12920 			super.defaultEventHandler_blur(ev);
12921 
12922 			if(this.content != lastContentBlur) {
12923 				auto evt = new ChangeEvent!string(this, &this.content);
12924 				evt.dispatch();
12925 				lastContentBlur = this.content;
12926 			}
12927 		}
12928 	}
12929 
12930 
12931 	version(trash_text)
12932 	version(custom_widgets)
12933 	override void defaultEventHandler_char(CharEvent ev) {
12934 		super.defaultEventHandler_char(ev);
12935 		textLayout.insert(ev.character);
12936 		redraw();
12937 
12938 		// FIXME: too inefficient
12939 		auto cbb = textLayout.contentBoundingBox();
12940 		setContentSize(cbb.width, cbb.height);
12941 	}
12942 	version(trash_text)
12943 	version(custom_widgets)
12944 	override void defaultEventHandler_keydown(KeyDownEvent ev) {
12945 		//super.defaultEventHandler_keydown(ev);
12946 		switch(ev.key) {
12947 			case Key.Delete:
12948 				textLayout.delete_();
12949 				redraw();
12950 			break;
12951 			case Key.Left:
12952 				textLayout.moveLeft();
12953 				redraw();
12954 			break;
12955 			case Key.Right:
12956 				textLayout.moveRight();
12957 				redraw();
12958 			break;
12959 			case Key.Up:
12960 				textLayout.moveUp();
12961 				redraw();
12962 			break;
12963 			case Key.Down:
12964 				textLayout.moveDown();
12965 				redraw();
12966 			break;
12967 			case Key.Home:
12968 				textLayout.moveHome();
12969 				redraw();
12970 			break;
12971 			case Key.End:
12972 				textLayout.moveEnd();
12973 				redraw();
12974 			break;
12975 			case Key.PageUp:
12976 				foreach(i; 0 .. 32)
12977 				textLayout.moveUp();
12978 				redraw();
12979 			break;
12980 			case Key.PageDown:
12981 				foreach(i; 0 .. 32)
12982 				textLayout.moveDown();
12983 				redraw();
12984 			break;
12985 
12986 			default:
12987 				 {} // intentionally blank, let "char" handle it
12988 		}
12989 		/*
12990 		if(ev.key == Key.Backspace) {
12991 			textLayout.backspace();
12992 			redraw();
12993 		}
12994 		*/
12995 		ensureVisibleInScroll(textLayout.caretBoundingBox());
12996 	}
12997 
12998 	version(use_new_text_system) {
12999 		bool showingVerticalScroll() { return true; }
13000 		bool showingHorizontalScroll() { return true; }
13001 	}
13002 }
13003 
13004 ///
13005 class LineEdit : EditableTextWidget {
13006 	// FIXME: hack
13007 	version(custom_widgets) {
13008 	override bool showingVerticalScroll() { return false; }
13009 	override bool showingHorizontalScroll() { return false; }
13010 	}
13011 
13012 	override int flexBasisWidth() { return 250; }
13013 
13014 	///
13015 	this(Widget parent) {
13016 		super(parent);
13017 		version(win32_widgets) {
13018 			createWin32Window(this, "edit"w, "",
13019 				0, WS_EX_CLIENTEDGE);//|WS_HSCROLL|ES_AUTOHSCROLL);
13020 		} else version(custom_widgets) {
13021 			version(trash_text) {
13022 				setupCustomTextEditing();
13023 				addEventListener(delegate(CharEvent ev) {
13024 					if(ev.character == '\n')
13025 						ev.preventDefault();
13026 				});
13027 			}
13028 		} else static assert(false);
13029 	}
13030 
13031 	version(use_new_text_system)
13032 	override TextDisplayHelper textDisplayHelperFactory(TextLayouter textLayout, ScrollMessageWidget smw) {
13033 		auto tdh = new TextDisplayHelper(textLayout, smw);
13034 		tdh.singleLine = true;
13035 		return tdh;
13036 	}
13037 
13038 	version(win32_widgets) {
13039 		mixin Padding!q{2};
13040 		override int minHeight() { return borderBoxForContentBox(Rectangle(Point(0, 0), Size(0, defaultLineHeight))).height; }
13041 		override int maxHeight() { return minHeight; }
13042 	}
13043 
13044 	/+
13045 	@property void passwordMode(bool p) {
13046 		SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) | ES_PASSWORD);
13047 	}
13048 	+/
13049 }
13050 
13051 /++
13052 	A [LineEdit] that displays `*` in place of the actual characters.
13053 
13054 	Alas, Windows requires the window to be created differently to use this style,
13055 	so it had to be a new class instead of a toggle on and off on an existing object.
13056 
13057 	FIXME: this is not yet implemented on Linux, it will work the same as a TextEdit there for now.
13058 
13059 	History:
13060 		Added January 24, 2021
13061 +/
13062 class PasswordEdit : EditableTextWidget {
13063 	version(custom_widgets) {
13064 	override bool showingVerticalScroll() { return false; }
13065 	override bool showingHorizontalScroll() { return false; }
13066 	}
13067 
13068 	override int flexBasisWidth() { return 250; }
13069 
13070 	version(use_new_text_system)
13071 	override TextStyle defaultTextStyle() {
13072 		auto cs = getComputedStyle();
13073 
13074 		auto osf = new class OperatingSystemFont {
13075 			this() {
13076 				super(cs.font);
13077 			}
13078 			override int stringWidth(scope const(char)[] text, SimpleWindow window = null) {
13079 				int count = 0;
13080 				foreach(dchar ch; text)
13081 					count++;
13082 				return count * super.stringWidth("*", window);
13083 			}
13084 		};
13085 
13086 		return new TextDisplayHelper.MyTextStyle(osf);
13087 	}
13088 
13089 	version(use_new_text_system)
13090 	override TextDisplayHelper textDisplayHelperFactory(TextLayouter textLayout, ScrollMessageWidget smw) {
13091 		static class TDH : TextDisplayHelper {
13092 			this(TextLayouter textLayout, ScrollMessageWidget smw) {
13093 				singleLine = true;
13094 				super(textLayout, smw);
13095 			}
13096 
13097 			override void drawTextSegment(WidgetPainter painter, Point upperLeft, scope const(char)[] text) {
13098 				char[256] buffer = void;
13099 				int bufferLength = 0;
13100 				foreach(dchar ch; text)
13101 					buffer[bufferLength++] = '*';
13102 				painter.drawText(upperLeft, buffer[0..bufferLength]);
13103 			}
13104 		}
13105 
13106 		return new TDH(textLayout, smw);
13107 	}
13108 
13109 	///
13110 	this(Widget parent) {
13111 		super(parent);
13112 		version(win32_widgets) {
13113 			createWin32Window(this, "edit"w, "",
13114 				ES_PASSWORD, WS_EX_CLIENTEDGE);//|WS_HSCROLL|ES_AUTOHSCROLL);
13115 		} else version(custom_widgets) {
13116 			version(trash_text)
13117 			setupCustomTextEditing();
13118 			addEventListener(delegate(CharEvent ev) {
13119 				if(ev.character == '\n')
13120 					ev.preventDefault();
13121 			});
13122 		} else static assert(false);
13123 	}
13124 	version(win32_widgets) {
13125 		mixin Padding!q{2};
13126 		override int minHeight() { return borderBoxForContentBox(Rectangle(Point(0, 0), Size(0, defaultLineHeight))).height; }
13127 		override int maxHeight() { return minHeight; }
13128 	}
13129 }
13130 
13131 
13132 ///
13133 class TextEdit : EditableTextWidget {
13134 	///
13135 	this(Widget parent) {
13136 		super(parent);
13137 		version(win32_widgets) {
13138 			createWin32Window(this, "edit"w, "",
13139 				0|WS_VSCROLL|WS_HSCROLL|ES_MULTILINE|ES_WANTRETURN|ES_AUTOHSCROLL|ES_AUTOVSCROLL, WS_EX_CLIENTEDGE);
13140 		} else version(custom_widgets) {
13141 			version(trash_text)
13142 			setupCustomTextEditing();
13143 		} else static assert(false);
13144 	}
13145 	override int maxHeight() { return int.max; }
13146 	override int heightStretchiness() { return 7; }
13147 
13148 	override int flexBasisWidth() { return 250; }
13149 	override int flexBasisHeight() { return 25; }
13150 }
13151 
13152 
13153 /++
13154 
13155 +/
13156 version(none)
13157 class RichTextDisplay : Widget {
13158 	@property void content(string c) {}
13159 	void appendContent(string c) {}
13160 }
13161 
13162 ///
13163 class MessageBox : Window {
13164 	private string message;
13165 	MessageBoxButton buttonPressed = MessageBoxButton.None;
13166 	///
13167 	this(string message, string[] buttons = ["OK"], MessageBoxButton[] buttonIds = [MessageBoxButton.OK]) {
13168 		super(300, 100);
13169 
13170 		assert(buttons.length);
13171 		assert(buttons.length ==  buttonIds.length);
13172 
13173 		this.message = message;
13174 
13175 		auto label = new TextLabel(message, TextAlignment.Center, this);
13176 
13177 		auto hl = new HorizontalLayout(this);
13178 		auto spacer = new HorizontalSpacer(hl); // to right align
13179 
13180 		foreach(idx, buttonText; buttons) {
13181 			auto button = new CommandButton(buttonText, hl);
13182 
13183 			button.addEventListener(EventType.triggered, ((size_t idx) { return () {
13184 				this.buttonPressed = buttonIds[idx];
13185 				win.close();
13186 			}; })(idx));
13187 
13188 			if(idx == 0)
13189 				button.focus();
13190 		}
13191 
13192 		if(buttons.length == 1)
13193 			auto spacer2 = new HorizontalSpacer(hl); // to center it
13194 
13195 		win.resize(scaleWithDpi(300), this.minHeight());
13196 
13197 		win.show();
13198 		redraw();
13199 	}
13200 
13201 	mixin Padding!q{16};
13202 }
13203 
13204 ///
13205 enum MessageBoxStyle {
13206 	OK, ///
13207 	OKCancel, ///
13208 	RetryCancel, ///
13209 	YesNo, ///
13210 	YesNoCancel, ///
13211 	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.
13212 }
13213 
13214 ///
13215 enum MessageBoxIcon {
13216 	None, ///
13217 	Info, ///
13218 	Warning, ///
13219 	Error ///
13220 }
13221 
13222 /// Identifies the button the user pressed on a message box.
13223 enum MessageBoxButton {
13224 	None, /// The user closed the message box without clicking any of the buttons.
13225 	OK, ///
13226 	Cancel, ///
13227 	Retry, ///
13228 	Yes, ///
13229 	No, ///
13230 	Continue ///
13231 }
13232 
13233 
13234 /++
13235 	Displays a modal message box, blocking until the user dismisses it.
13236 
13237 	Returns: the button pressed.
13238 +/
13239 MessageBoxButton messageBox(string title, string message, MessageBoxStyle style = MessageBoxStyle.OK, MessageBoxIcon icon = MessageBoxIcon.None) {
13240 	version(win32_widgets) {
13241 		WCharzBuffer t = WCharzBuffer(title);
13242 		WCharzBuffer m = WCharzBuffer(message);
13243 		UINT type;
13244 		with(MessageBoxStyle)
13245 		final switch(style) {
13246 			case OK: type |= MB_OK; break;
13247 			case OKCancel: type |= MB_OKCANCEL; break;
13248 			case RetryCancel: type |= MB_RETRYCANCEL; break;
13249 			case YesNo: type |= MB_YESNO; break;
13250 			case YesNoCancel: type |= MB_YESNOCANCEL; break;
13251 			case RetryCancelContinue: type |= MB_CANCELTRYCONTINUE; break;
13252 		}
13253 		with(MessageBoxIcon)
13254 		final switch(icon) {
13255 			case None: break;
13256 			case Info: type |= MB_ICONINFORMATION; break;
13257 			case Warning: type |= MB_ICONWARNING; break;
13258 			case Error: type |= MB_ICONERROR; break;
13259 		}
13260 		switch(MessageBoxW(null, m.ptr, t.ptr, type)) {
13261 			case IDOK: return MessageBoxButton.OK;
13262 			case IDCANCEL: return MessageBoxButton.Cancel;
13263 			case IDTRYAGAIN, IDRETRY: return MessageBoxButton.Retry;
13264 			case IDYES: return MessageBoxButton.Yes;
13265 			case IDNO: return MessageBoxButton.No;
13266 			case IDCONTINUE: return MessageBoxButton.Continue;
13267 			default: return MessageBoxButton.None;
13268 		}
13269 	} else {
13270 		string[] buttons;
13271 		MessageBoxButton[] buttonIds;
13272 		with(MessageBoxStyle)
13273 		final switch(style) {
13274 			case OK:
13275 				buttons = ["OK"];
13276 				buttonIds = [MessageBoxButton.OK];
13277 			break;
13278 			case OKCancel:
13279 				buttons = ["OK", "Cancel"];
13280 				buttonIds = [MessageBoxButton.OK, MessageBoxButton.Cancel];
13281 			break;
13282 			case RetryCancel:
13283 				buttons = ["Retry", "Cancel"];
13284 				buttonIds = [MessageBoxButton.Retry, MessageBoxButton.Cancel];
13285 			break;
13286 			case YesNo:
13287 				buttons = ["Yes", "No"];
13288 				buttonIds = [MessageBoxButton.Yes, MessageBoxButton.No];
13289 			break;
13290 			case YesNoCancel:
13291 				buttons = ["Yes", "No", "Cancel"];
13292 				buttonIds = [MessageBoxButton.Yes, MessageBoxButton.No, MessageBoxButton.Cancel];
13293 			break;
13294 			case RetryCancelContinue:
13295 				buttons = ["Try Again", "Cancel", "Continue"];
13296 				buttonIds = [MessageBoxButton.Retry, MessageBoxButton.Cancel, MessageBoxButton.Continue];
13297 			break;
13298 		}
13299 		auto mb = new MessageBox(message, buttons, buttonIds);
13300 		EventLoop el = EventLoop.get;
13301 		el.run(() { return !mb.win.closed; });
13302 		return mb.buttonPressed;
13303 	}
13304 }
13305 
13306 /// ditto
13307 int messageBox(string message, MessageBoxStyle style = MessageBoxStyle.OK, MessageBoxIcon icon = MessageBoxIcon.None) {
13308 	return messageBox(null, message, style, icon);
13309 }
13310 
13311 
13312 
13313 ///
13314 alias void delegate(Widget handlerAttachedTo, Event event) EventHandler;
13315 
13316 /++
13317 	This is an opaque type you can use to disconnect an event handler when you're no longer interested.
13318 
13319 	History:
13320 		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.
13321 +/
13322 struct EventListener {
13323 	private Widget widget;
13324 	private string event;
13325 	private EventHandler handler;
13326 	private bool useCapture;
13327 
13328 	///
13329 	void disconnect() {
13330 		widget.removeEventListener(this);
13331 	}
13332 }
13333 
13334 /++
13335 	The purpose of this enum was to give a compile-time checked version of various standard event strings.
13336 
13337 	Now, I recommend you use a statically typed event object instead.
13338 
13339 	See_Also: [Event]
13340 +/
13341 enum EventType : string {
13342 	click = "click", ///
13343 
13344 	mouseenter = "mouseenter", ///
13345 	mouseleave = "mouseleave", ///
13346 	mousein = "mousein", ///
13347 	mouseout = "mouseout", ///
13348 	mouseup = "mouseup", ///
13349 	mousedown = "mousedown", ///
13350 	mousemove = "mousemove", ///
13351 
13352 	keydown = "keydown", ///
13353 	keyup = "keyup", ///
13354 	char_ = "char", ///
13355 
13356 	focus = "focus", ///
13357 	blur = "blur", ///
13358 
13359 	triggered = "triggered", ///
13360 
13361 	change = "change", ///
13362 }
13363 
13364 /++
13365 	Represents an event that is currently being processed.
13366 
13367 
13368 	Minigui's event model is based on the web browser. An event has a name, a target,
13369 	and an associated data object. It starts from the window and works its way down through
13370 	the target through all intermediate [Widget]s, triggering capture phase handlers as it goes,
13371 	then goes back up again all the way back to the window, triggering bubble phase handlers. At
13372 	the end, if [Event.preventDefault] has not been called, it calls the target widget's default
13373 	handlers for the event (please note that default handlers will be called even if [Event.stopPropagation]
13374 	was called; that just stops it from calling other handlers in the widget tree, but the default happens
13375 	whenever propagation is done, not only if it gets to the end of the chain).
13376 
13377 	This model has several nice points:
13378 
13379 	$(LIST
13380 		* It is easy to delegate dynamic handlers to a parent. You can have a parent container
13381 		  with event handlers set, then add/remove children as much as you want without needing
13382 		  to manage the event handlers on them - the parent alone can manage everything.
13383 
13384 		* It is easy to create new custom events in your application.
13385 
13386 		* It is familiar to many web developers.
13387 	)
13388 
13389 	There's a few downsides though:
13390 
13391 	$(LIST
13392 		* There's not a lot of type safety.
13393 
13394 		* You don't get a static list of what events a widget can emit.
13395 
13396 		* Tracing where an event got cancelled along the chain can get difficult; the downside of
13397 		  the central delegation benefit is it can be lead to debugging of action at a distance.
13398 	)
13399 
13400 	In May 2021, I started to adjust this model to minigui takes better advantage of D over Javascript
13401 	while keeping the benefits - and most compatibility with - the existing model. The main idea is
13402 	to simply use a D object type which provides a static interface as well as a built-in event name.
13403 	Then, a new static interface allows you to see what an event can emit and attach handlers to it
13404 	similarly to C#, which just forwards to the JS style api. They're fully compatible so you can still
13405 	delegate to a parent and use custom events as well as using the runtime dynamic access, in addition
13406 	to having a little more help from the D compiler and documentation generator.
13407 
13408 	Your code would change like this:
13409 
13410 	---
13411 	// old
13412 	widget.addEventListener("keydown", (Event ev) { ... }, /* optional arg */ useCapture );
13413 
13414 	// new
13415 	widget.addEventListener((KeyDownEvent ev) { ... }, /* optional arg */ useCapture );
13416 	---
13417 
13418 	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.
13419 
13420 	All you have to do is replace the string with a specific Event subclass. It will figure out the event string from the class.
13421 
13422 	Alternatively, you can cast the Event yourself to the appropriate subclass, but it is easier to let the library do it for you!
13423 
13424 	Thus the family of functions are:
13425 
13426 	[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.
13427 
13428 	[Widget.addDirectEventListener] is addEventListener, but only calls the handler if target == this. Useful for something you can't afford to delegate.
13429 
13430 	[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.
13431 
13432 	Let's implement a custom widget that can emit a ChangeEvent describing its `checked` property:
13433 
13434 	---
13435 	class MyCheckbox : Widget {
13436 		/// This gives a chance to document it and generates a convenience function to send it and attach handlers.
13437 		/// It is NOT actually required but should be used whenever possible.
13438 		mixin Emits!(ChangeEvent!bool);
13439 
13440 		this(Widget parent) {
13441 			super(parent);
13442 			setDefaultEventHandler((ClickEvent) { checked = !checked; });
13443 		}
13444 
13445 		private bool _checked;
13446 		@property bool checked() { return _checked; }
13447 		@property void checked(bool set) {
13448 			_checked = set;
13449 			emit!(ChangeEvent!bool)(&checked);
13450 		}
13451 	}
13452 	---
13453 
13454 	## Creating Your Own Events
13455 
13456 	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.
13457 
13458 	---
13459 	class MyEvent : Event {
13460 		this(Widget target) { super(EventString, target); }
13461 		mixin Register; // adds EventString and other reflection information
13462 	}
13463 	---
13464 
13465 	Then declare that it is sent with the [Emits] mixin, so you can use [Widget.emit] to dispatch it.
13466 
13467 	History:
13468 		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.
13469 
13470 		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.
13471 +/
13472 /+
13473 
13474 	## General Conventions
13475 
13476 	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.
13477 
13478 
13479 	## Qt-style signals and slots
13480 
13481 	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.
13482 
13483 	The intention is for events to be used when
13484 
13485 	---
13486 	class Demo : Widget {
13487 		this() {
13488 			myPropertyChanged = Signal!int(this);
13489 		}
13490 		@property myProperty(int v) {
13491 			myPropertyChanged.emit(v);
13492 		}
13493 
13494 		Signal!int myPropertyChanged; // i need to get `this` off it and inspect the name...
13495 		// but it can just genuinely not care about `this` since that's not really passed.
13496 	}
13497 
13498 	class Foo : Widget {
13499 		// the slot uda is not necessary, but it helps the script and ui builder find it.
13500 		@slot void setValue(int v) { ... }
13501 	}
13502 
13503 	demo.myPropertyChanged.connect(&foo.setValue);
13504 	---
13505 
13506 	The Signal type has a disabled default constructor, meaning your widget constructor must pass `this` to it in its constructor.
13507 
13508 	Some events may also wish to implement the Signal interface. These use particular arguments to call a method automatically.
13509 
13510 	class StringChangeEvent : ChangeEvent, Signal!string {
13511 		mixin SignalImpl
13512 	}
13513 
13514 +/
13515 class Event : ReflectableProperties {
13516 	/// Creates an event without populating any members and without sending it. See [dispatch]
13517 	this(string eventName, Widget emittedBy) {
13518 		this.eventName = eventName;
13519 		this.srcElement = emittedBy;
13520 	}
13521 
13522 
13523 	/// Implementations for the [ReflectableProperties] interface/
13524 	void getPropertiesList(scope void delegate(string name) sink) const {}
13525 	/// ditto
13526 	void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink) { }
13527 	/// ditto
13528 	SetPropertyResult setPropertyFromString(string name, scope const(char)[] str, bool strIsJson) {
13529 		return SetPropertyResult.notPermitted;
13530 	}
13531 
13532 
13533 	/+
13534 	/++
13535 		This is an internal implementation detail of [Register] and is subject to be changed or removed at any time without notice.
13536 
13537 		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.
13538 	+/
13539 	protected final void sinkJsonString(string memberName, scope const(char)[] value, scope void delegate(string name, scope const(char)[] value) finalSink) {
13540 		if(value.length == 0) {
13541 			finalSink(memberName, `""`);
13542 			return;
13543 		}
13544 
13545 		char[1024] bufferBacking;
13546 		char[] buffer = bufferBacking;
13547 		int bufferPosition;
13548 
13549 		void sink(char ch) {
13550 			if(bufferPosition >= buffer.length)
13551 				buffer.length = buffer.length + 1024;
13552 			buffer[bufferPosition++] = ch;
13553 		}
13554 
13555 		sink('"');
13556 
13557 		foreach(ch; value) {
13558 			switch(ch) {
13559 				case '\\':
13560 					sink('\\'); sink('\\');
13561 				break;
13562 				case '"':
13563 					sink('\\'); sink('"');
13564 				break;
13565 				case '\n':
13566 					sink('\\'); sink('n');
13567 				break;
13568 				case '\r':
13569 					sink('\\'); sink('r');
13570 				break;
13571 				case '\t':
13572 					sink('\\'); sink('t');
13573 				break;
13574 				default:
13575 					sink(ch);
13576 			}
13577 		}
13578 
13579 		sink('"');
13580 
13581 		finalSink(memberName, buffer[0 .. bufferPosition]);
13582 	}
13583 	+/
13584 
13585 	/+
13586 	enum EventInitiator {
13587 		system,
13588 		minigui,
13589 		user
13590 	}
13591 
13592 	immutable EventInitiator; initiatedBy;
13593 	+/
13594 
13595 	/++
13596 		Events should generally follow the propagation model, but there's some exceptions
13597 		to that rule. If so, they should override this to return false. In that case, only
13598 		bubbling event handlers on the target itself and capturing event handlers on the containing
13599 		window will be called. (That is, [dispatch] will call [sendDirectly] instead of doing the normal
13600 		capture -> target -> bubble process.)
13601 
13602 		History:
13603 			Added May 12, 2021
13604 	+/
13605 	bool propagates() const pure nothrow @nogc @safe {
13606 		return true;
13607 	}
13608 
13609 	/++
13610 		hints as to whether preventDefault will actually do anything. not entirely reliable.
13611 
13612 		History:
13613 			Added May 14, 2021
13614 	+/
13615 	bool cancelable() const pure nothrow @nogc @safe {
13616 		return true;
13617 	}
13618 
13619 	/++
13620 		You can mix this into child class to register some boilerplate. It includes the `EventString`
13621 		member, a constructor, and implementations of the dynamic get data interfaces.
13622 
13623 		If you fail to do this, your event will probably not have full compatibility but it might still work for you.
13624 
13625 
13626 		You can override the default EventString by simply providing your own in the form of
13627 		`enum string EventString = "some.name";` The default is the name of your class and its parent entity
13628 		which provides some namespace protection against conflicts in other libraries while still being fairly
13629 		easy to use.
13630 
13631 		If you provide your own constructor, it will override the default constructor provided here. A constructor
13632 		must call `super(EventString, passed_widget_target)` at some point. The `passed_widget_target` must be the
13633 		first argument to your constructor.
13634 
13635 		History:
13636 			Added May 13, 2021.
13637 	+/
13638 	protected static mixin template Register() {
13639 		public enum string EventString = __traits(identifier, __traits(parent, typeof(this))) ~ "." ~  __traits(identifier, typeof(this));
13640 		this(Widget target) { super(EventString, target); }
13641 
13642 		mixin ReflectableProperties.RegisterGetters;
13643 	}
13644 
13645 	/++
13646 		This is the widget that emitted the event.
13647 
13648 
13649 		The aliased names come from Javascript for ease of web developers to transition in, but they're all synonyms.
13650 
13651 		History:
13652 			The `source` name was added on May 14, 2021. It is a little weird that `source` and `target` are synonyms,
13653 			but that's a side effect of it doing both capture and bubble handlers and people are used to it from the web
13654 			so I don't intend to remove these aliases.
13655 	+/
13656 	Widget source;
13657 	/// ditto
13658 	alias source target;
13659 	/// ditto
13660 	alias source srcElement;
13661 
13662 	Widget relatedTarget; /// Note: likely to be deprecated at some point.
13663 
13664 	/// Prevents the default event handler (if there is one) from being called
13665 	void preventDefault() {
13666 		lastDefaultPrevented = true;
13667 		defaultPrevented = true;
13668 	}
13669 
13670 	/// Stops the event propagation immediately.
13671 	void stopPropagation() {
13672 		propagationStopped = true;
13673 	}
13674 
13675 	private bool defaultPrevented;
13676 	private bool propagationStopped;
13677 	private string eventName;
13678 
13679 	private bool isBubbling;
13680 
13681 	/// 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.
13682 	protected void adjustScrolling() { }
13683 	/// ditto
13684 	protected void adjustClientCoordinates(int deltaX, int deltaY) { }
13685 
13686 	/++
13687 		this sends it only to the target. If you want propagation, use dispatch() instead.
13688 
13689 		This should be made private!!!
13690 
13691 	+/
13692 	void sendDirectly() {
13693 		if(srcElement is null)
13694 			return;
13695 
13696 		// 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.
13697 
13698 		//debug if(eventName != "mousemove" && target !is null && target.parentWindow && target.parentWindow.devTools)
13699 			//target.parentWindow.devTools.log("Event ", eventName, " dispatched directly to ", srcElement);
13700 
13701 		adjustScrolling();
13702 
13703 		if(auto e = target.parentWindow) {
13704 			if(auto handlers = "*" in e.capturingEventHandlers)
13705 			foreach(handler; *handlers)
13706 				if(handler) handler(e, this);
13707 			if(auto handlers = eventName in e.capturingEventHandlers)
13708 			foreach(handler; *handlers)
13709 				if(handler) handler(e, this);
13710 		}
13711 
13712 		auto e = srcElement;
13713 
13714 		if(auto handlers = eventName in e.bubblingEventHandlers)
13715 		foreach(handler; *handlers)
13716 			if(handler) handler(e, this);
13717 
13718 		if(auto handlers = "*" in e.bubblingEventHandlers)
13719 		foreach(handler; *handlers)
13720 			if(handler) handler(e, this);
13721 
13722 		// there's never a default for a catch-all event
13723 		if(!defaultPrevented)
13724 			if(eventName in e.defaultEventHandlers)
13725 				e.defaultEventHandlers[eventName](e, this);
13726 	}
13727 
13728 	/// this dispatches the element using the capture -> target -> bubble process
13729 	void dispatch() {
13730 		if(srcElement is null)
13731 			return;
13732 
13733 		if(!propagates) {
13734 			sendDirectly;
13735 			return;
13736 		}
13737 
13738 		//debug if(eventName != "mousemove" && target !is null && target.parentWindow && target.parentWindow.devTools)
13739 			//target.parentWindow.devTools.log("Event ", eventName, " dispatched to ", srcElement);
13740 
13741 		adjustScrolling();
13742 		// first capture, then bubble
13743 
13744 		Widget[] chain;
13745 		Widget curr = srcElement;
13746 		while(curr) {
13747 			auto l = curr;
13748 			chain ~= l;
13749 			curr = curr.parent;
13750 		}
13751 
13752 		isBubbling = false;
13753 
13754 		foreach_reverse(e; chain) {
13755 			if(auto handlers = "*" in e.capturingEventHandlers)
13756 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
13757 
13758 			if(propagationStopped)
13759 				break;
13760 
13761 			if(auto handlers = eventName in e.capturingEventHandlers)
13762 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
13763 
13764 			// the default on capture should really be to always do nothing
13765 
13766 			//if(!defaultPrevented)
13767 			//	if(eventName in e.defaultEventHandlers)
13768 			//		e.defaultEventHandlers[eventName](e.element, this);
13769 
13770 			if(propagationStopped)
13771 				break;
13772 		}
13773 
13774 		int adjustX;
13775 		int adjustY;
13776 
13777 		isBubbling = true;
13778 		if(!propagationStopped)
13779 		foreach(e; chain) {
13780 			if(auto handlers = eventName in e.bubblingEventHandlers)
13781 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
13782 
13783 			if(propagationStopped)
13784 				break;
13785 
13786 			if(auto handlers = "*" in e.bubblingEventHandlers)
13787 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
13788 
13789 			if(propagationStopped)
13790 				break;
13791 
13792 			if(e.encapsulatedChildren()) {
13793 				adjustClientCoordinates(adjustX, adjustY);
13794 				target = e;
13795 			} else {
13796 				adjustX += e.x;
13797 				adjustY += e.y;
13798 			}
13799 		}
13800 
13801 		if(!defaultPrevented)
13802 		foreach(e; chain) {
13803 			if(eventName in e.defaultEventHandlers)
13804 				e.defaultEventHandlers[eventName](e, this);
13805 		}
13806 	}
13807 
13808 
13809 	/* old compatibility things */
13810 	deprecated("Use some subclass of KeyEventBase instead of plain Event in your handler going forward. WARNING these may crash on non-key events!")
13811 	final @property {
13812 		Key key() { return (cast(KeyEventBase) this).key; }
13813 		KeyEvent originalKeyEvent() { return (cast(KeyEventBase) this).originalKeyEvent; }
13814 
13815 		bool ctrlKey() { return (cast(KeyEventBase) this).ctrlKey; }
13816 		bool altKey() { return (cast(KeyEventBase) this).altKey; }
13817 		bool shiftKey() { return (cast(KeyEventBase) this).shiftKey; }
13818 	}
13819 
13820 	deprecated("Use some subclass of MouseEventBase instead of Event in your handler going forward. WARNING these may crash on non-mouse events!")
13821 	final @property {
13822 		int clientX() { return (cast(MouseEventBase) this).clientX; }
13823 		int clientY() { return (cast(MouseEventBase) this).clientY; }
13824 
13825 		int viewportX() { return (cast(MouseEventBase) this).viewportX; }
13826 		int viewportY() { return (cast(MouseEventBase) this).viewportY; }
13827 
13828 		int button() { return (cast(MouseEventBase) this).button; }
13829 		int buttonLinear() { return (cast(MouseEventBase) this).buttonLinear; }
13830 	}
13831 
13832 	deprecated("Use either a KeyEventBase or a MouseEventBase instead of Event in your handler going forward")
13833 	final @property {
13834 		int state() {
13835 			if(auto meb = cast(MouseEventBase) this)
13836 				return meb.state;
13837 			if(auto keb = cast(KeyEventBase) this)
13838 				return keb.state;
13839 			assert(0);
13840 		}
13841 	}
13842 
13843 	deprecated("Use a CharEvent instead of Event in your handler going forward")
13844 	final @property {
13845 		dchar character() {
13846 			if(auto ce = cast(CharEvent) this)
13847 				return ce.character;
13848 			return dchar.init;
13849 		}
13850 	}
13851 
13852 	// for change events
13853 	@property {
13854 		///
13855 		int intValue() { return 0; }
13856 		///
13857 		string stringValue() { return null; }
13858 	}
13859 }
13860 
13861 /++
13862 	This lets you statically verify you send the events you claim you send and gives you a hook to document them.
13863 
13864 	Please note that a widget may send events not listed as Emits. You can always construct and dispatch
13865 	dynamic and custom events, but the static list helps ensure you get them right.
13866 
13867 	If this is declared, you can use [Widget.emit] to send the event.
13868 
13869 	All events work the same way though, following the capture->widget->bubble model described under [Event].
13870 
13871 	History:
13872 		Added May 4, 2021
13873 +/
13874 mixin template Emits(EventType) {
13875 	import arsd.minigui : EventString;
13876 	static if(is(EventType : Event) && !is(EventType == Event))
13877 		mixin("private EventType[0] emits_" ~ EventStringIdentifier!EventType ~";");
13878 	else
13879 		static assert(0, "You can only emit subclasses of Event");
13880 }
13881 
13882 /// ditto
13883 mixin template Emits(string eventString) {
13884 	mixin("private Event[0] emits_" ~ eventString ~";");
13885 }
13886 
13887 /*
13888 class SignalEvent(string name) : Event {
13889 
13890 }
13891 */
13892 
13893 /++
13894 	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".
13895 
13896 
13897 	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.
13898 
13899 	History:
13900 		Added on May 13, 2021. Prior to that, you'd most likely `addEventListener(EventType.triggered, ...)` to handle similar things.
13901 +/
13902 class CommandEvent : Event {
13903 	enum EventString = "command";
13904 	this(Widget source, string CommandString = EventString) {
13905 		super(CommandString, source);
13906 	}
13907 }
13908 
13909 /++
13910 	A [CommandEvent] is typically actually an instance of these to hold the strongly-typed arguments.
13911 +/
13912 class CommandEventWithArgs(Args...) : CommandEvent {
13913 	this(Widget source, string CommandString, Args args) { super(source, CommandString); this.args = args; }
13914 	Args args;
13915 }
13916 
13917 /++
13918 	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.
13919 
13920 	See [CommandEvent] for more information.
13921 
13922 	Returns:
13923 		The [EventListener] you can use to remove the handler.
13924 +/
13925 EventListener consumesCommand(string CommandString, WidgetType, Args...)(WidgetType w, void delegate(Args) handler) {
13926 	return w.addEventListener(CommandString, (Event ev) {
13927 		if(ev.target is w)
13928 			return; // it does not consume its own commands!
13929 		if(auto cev = cast(CommandEventWithArgs!Args) ev) {
13930 			handler(cev.args);
13931 			ev.stopPropagation();
13932 		}
13933 	});
13934 }
13935 
13936 /++
13937 	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.
13938 +/
13939 void emitCommand(string CommandString, WidgetType, Args...)(WidgetType w, Args args) {
13940 	auto event = new CommandEventWithArgs!Args(w, CommandString, args);
13941 	event.dispatch();
13942 }
13943 
13944 class ResizeEvent : Event {
13945 	enum EventString = "resize";
13946 
13947 	this(Widget target) { super(EventString, target); }
13948 
13949 	override bool propagates() const { return false; }
13950 }
13951 
13952 /++
13953 	ClosingEvent is fired when a user is attempting to close a window. You can `preventDefault` to cancel the close.
13954 
13955 	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.
13956 
13957 	History:
13958 		Added June 21, 2021 (dub v10.1)
13959 +/
13960 class ClosingEvent : Event {
13961 	enum EventString = "closing";
13962 
13963 	this(Widget target) { super(EventString, target); }
13964 
13965 	override bool propagates() const { return false; }
13966 	override bool cancelable() const { return true; }
13967 }
13968 
13969 /// ditto
13970 class ClosedEvent : Event {
13971 	enum EventString = "closed";
13972 
13973 	this(Widget target) { super(EventString, target); }
13974 
13975 	override bool propagates() const { return false; }
13976 	override bool cancelable() const { return false; }
13977 }
13978 
13979 ///
13980 class BlurEvent : Event {
13981 	enum EventString = "blur";
13982 
13983 	// FIXME: related target?
13984 	this(Widget target) { super(EventString, target); }
13985 
13986 	override bool propagates() const { return false; }
13987 }
13988 
13989 ///
13990 class FocusEvent : Event {
13991 	enum EventString = "focus";
13992 
13993 	// FIXME: related target?
13994 	this(Widget target) { super(EventString, target); }
13995 
13996 	override bool propagates() const { return false; }
13997 }
13998 
13999 /++
14000 	FocusInEvent is a FocusEvent that propagates, while FocusOutEvent is a BlurEvent that propagates.
14001 
14002 	History:
14003 		Added July 3, 2021
14004 +/
14005 class FocusInEvent : Event {
14006 	enum EventString = "focusin";
14007 
14008 	// FIXME: related target?
14009 	this(Widget target) { super(EventString, target); }
14010 
14011 	override bool cancelable() const { return false; }
14012 }
14013 
14014 /// ditto
14015 class FocusOutEvent : Event {
14016 	enum EventString = "focusout";
14017 
14018 	// FIXME: related target?
14019 	this(Widget target) { super(EventString, target); }
14020 
14021 	override bool cancelable() const { return false; }
14022 }
14023 
14024 ///
14025 class ScrollEvent : Event {
14026 	enum EventString = "scroll";
14027 	this(Widget target) { super(EventString, target); }
14028 
14029 	override bool cancelable() const { return false; }
14030 }
14031 
14032 /++
14033 	Indicates that a character has been typed by the user. Normally dispatched to the currently focused widget.
14034 
14035 	History:
14036 		Added May 2, 2021. Previously, this was simply a "char" event and `character` as a member of the [Event] base class.
14037 +/
14038 class CharEvent : Event {
14039 	enum EventString = "char";
14040 	this(Widget target, dchar ch) {
14041 		character = ch;
14042 		super(EventString, target);
14043 	}
14044 
14045 	immutable dchar character;
14046 }
14047 
14048 /++
14049 	You should generally use a `ChangeEvent!Type` instead of this directly. See [ChangeEvent] for more information.
14050 +/
14051 abstract class ChangeEventBase : Event {
14052 	enum EventString = "change";
14053 	this(Widget target) {
14054 		super(EventString, target);
14055 	}
14056 
14057 	/+
14058 		// idk where or how exactly i want to do this.
14059 		// i might come back to it later.
14060 
14061 	// If a widget itself broadcasts one of theses itself, it stops propagation going down
14062 	// this way the source doesn't get too confused (think of a nested scroll widget)
14063 	//
14064 	// the idea is like the scroll bar emits a command event saying like "scroll left one line"
14065 	// then you consume that command and change you scroll x position to whatever. then you do
14066 	// some kind of change event that is broadcast back to the children and any horizontal scroll
14067 	// listeners are now able to update, without having an explicit connection between them.
14068 	void broadcastToChildren(string fieldName) {
14069 
14070 	}
14071 	+/
14072 }
14073 
14074 /++
14075 	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.
14076 
14077 
14078 	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).
14079 
14080 	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);`
14081 
14082 	Since it is emitted after the value has already changed, [preventDefault] is unlikely to do anything.
14083 
14084 	History:
14085 		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.
14086 +/
14087 class ChangeEvent(T) : ChangeEventBase {
14088 	this(Widget target, T delegate() getNewValue) {
14089 		assert(getNewValue !is null);
14090 		this.getNewValue = getNewValue;
14091 		super(target);
14092 	}
14093 
14094 	private T delegate() getNewValue;
14095 
14096 	/++
14097 		Gets the new value that just changed.
14098 	+/
14099 	@property T value() {
14100 		return getNewValue();
14101 	}
14102 
14103 	/// compatibility method for old generic Events
14104 	static if(is(immutable T == immutable int))
14105 		override int intValue() { return value; }
14106 	/// ditto
14107 	static if(is(immutable T == immutable string))
14108 		override string stringValue() { return value; }
14109 }
14110 
14111 /++
14112 	Contains shared properties for [KeyDownEvent]s and [KeyUpEvent]s.
14113 
14114 
14115 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
14116 
14117 	History:
14118 		Added May 2, 2021. Previously, its properties were members of the [Event] base class.
14119 +/
14120 abstract class KeyEventBase : Event {
14121 	this(string name, Widget target) {
14122 		super(name, target);
14123 	}
14124 
14125 	// for key events
14126 	Key key; ///
14127 
14128 	KeyEvent originalKeyEvent;
14129 
14130 	/++
14131 		Indicates the current state of the given keyboard modifier keys.
14132 
14133 		History:
14134 			Added to events on April 15, 2020.
14135 	+/
14136 	bool ctrlKey;
14137 
14138 	/// ditto
14139 	bool altKey;
14140 
14141 	/// ditto
14142 	bool shiftKey;
14143 
14144 	/++
14145 		The raw bitflags that are parsed out into [ctrlKey], [altKey], and [shiftKey].
14146 
14147 		See [arsd.simpledisplay.ModifierState] for other possible flags.
14148 	+/
14149 	int state;
14150 
14151 	mixin Register;
14152 }
14153 
14154 /++
14155 	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].
14156 
14157 
14158 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
14159 
14160 	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.
14161 
14162 	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.
14163 
14164 	See_Also: [KeyUpEvent], [CharEvent]
14165 
14166 	History:
14167 		Added May 2, 2021. Previously, it was only seen as the base [Event] class on "keydown" event listeners.
14168 +/
14169 class KeyDownEvent : KeyEventBase {
14170 	enum EventString = "keydown";
14171 	this(Widget target) { super(EventString, target); }
14172 }
14173 
14174 /++
14175 	Indicates that the user has released a key on the keyboard. For available properties, see [KeyEventBase].
14176 
14177 
14178 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
14179 
14180 	See_Also: [KeyDownEvent], [CharEvent]
14181 
14182 	History:
14183 		Added May 2, 2021. Previously, it was only seen as the base [Event] class on "keyup" event listeners.
14184 +/
14185 class KeyUpEvent : KeyEventBase {
14186 	enum EventString = "keyup";
14187 	this(Widget target) { super(EventString, target); }
14188 }
14189 
14190 /++
14191 	Contains shared properties for various mouse events;
14192 
14193 
14194 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
14195 
14196 	History:
14197 		Added May 2, 2021. Previously, its properties were members of the [Event] base class.
14198 +/
14199 abstract class MouseEventBase : Event {
14200 	this(string name, Widget target) {
14201 		super(name, target);
14202 	}
14203 
14204 	// for mouse events
14205 	int clientX; /// The mouse event location relative to the target widget
14206 	int clientY; /// ditto
14207 
14208 	int viewportX; /// The mouse event location relative to the window origin
14209 	int viewportY; /// ditto
14210 
14211 	int button; /// See: [MouseEvent.button]
14212 	int buttonLinear; /// See: [MouseEvent.buttonLinear]
14213 
14214 	/++
14215 		Indicates the current state of the given keyboard modifier keys.
14216 
14217 		History:
14218 			Added to mouse events on September 28, 2010.
14219 	+/
14220 	bool ctrlKey;
14221 
14222 	/// ditto
14223 	bool altKey;
14224 
14225 	/// ditto
14226 	bool shiftKey;
14227 
14228 
14229 
14230 	int state; ///
14231 
14232 	/++
14233 		for consistent names with key event.
14234 
14235 		History:
14236 			Added September 28, 2021 (dub v10.3)
14237 	+/
14238 	alias modifierState = state;
14239 
14240 	/++
14241 		Mouse wheel movement sends down/up/click events just like other buttons clicking. This method is to help you filter that out.
14242 
14243 		History:
14244 			Added May 15, 2021
14245 	+/
14246 	bool isMouseWheel() {
14247 		return button == MouseButton.wheelUp || button == MouseButton.wheelDown;
14248 	}
14249 
14250 	// private
14251 	override void adjustClientCoordinates(int deltaX, int deltaY) {
14252 		clientX += deltaX;
14253 		clientY += deltaY;
14254 	}
14255 
14256 	override void adjustScrolling() {
14257 	version(custom_widgets) { // TEMP
14258 		viewportX = clientX;
14259 		viewportY = clientY;
14260 		if(auto se = cast(ScrollableWidget) srcElement) {
14261 			clientX += se.scrollOrigin.x;
14262 			clientY += se.scrollOrigin.y;
14263 		} else if(auto se = cast(ScrollableContainerWidget) srcElement) {
14264 			//clientX += se.scrollX_;
14265 			//clientY += se.scrollY_;
14266 		}
14267 	}
14268 	}
14269 
14270 	mixin Register;
14271 }
14272 
14273 /++
14274 	Indicates that the user has worked with the mouse over your widget. For available properties, see [MouseEventBase].
14275 
14276 
14277 	$(WARNING
14278 		Important: MouseDownEvent, MouseUpEvent, ClickEvent, and DoubleClickEvent are all sent for all mouse buttons and
14279 		for wheel movement! You should check the [MouseEventBase.button|button] property in most your handlers to get correct
14280 		behavior.
14281 	)
14282 
14283 	[MouseDownEvent] is sent when the user presses a mouse button. It is also sent on mouse wheel movement.
14284 
14285 	[MouseUpEvent] is sent when the user releases a mouse button.
14286 
14287 	[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.)
14288 
14289 	[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.
14290 
14291 	[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.
14292 
14293 	[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.
14294 
14295 	[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.
14296 
14297 	[MouseEnterEvent] is sent when the mouse enters the bounding box of a widget.
14298 
14299 	[MouseLeaveEvent] is sent when the mouse leaves the bounding box of a widget.
14300 
14301 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
14302 
14303 	Rationale:
14304 
14305 		If you only want to do drag, mousedown/up works just fine being consistently sent.
14306 
14307 		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).
14308 
14309 		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.
14310 
14311 	History:
14312 		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.
14313 +/
14314 class MouseUpEvent : MouseEventBase {
14315 	enum EventString = "mouseup"; ///
14316 	this(Widget target) { super(EventString, target); }
14317 }
14318 /// ditto
14319 class MouseDownEvent : MouseEventBase {
14320 	enum EventString = "mousedown"; ///
14321 	this(Widget target) { super(EventString, target); }
14322 }
14323 /// ditto
14324 class MouseMoveEvent : MouseEventBase {
14325 	enum EventString = "mousemove"; ///
14326 	this(Widget target) { super(EventString, target); }
14327 }
14328 /// ditto
14329 class ClickEvent : MouseEventBase {
14330 	enum EventString = "click"; ///
14331 	this(Widget target) { super(EventString, target); }
14332 }
14333 /// ditto
14334 class DoubleClickEvent : MouseEventBase {
14335 	enum EventString = "dblclick"; ///
14336 	this(Widget target) { super(EventString, target); }
14337 }
14338 /// ditto
14339 class MouseOverEvent : Event {
14340 	enum EventString = "mouseover"; ///
14341 	this(Widget target) { super(EventString, target); }
14342 }
14343 /// ditto
14344 class MouseOutEvent : Event {
14345 	enum EventString = "mouseout"; ///
14346 	this(Widget target) { super(EventString, target); }
14347 }
14348 /// ditto
14349 class MouseEnterEvent : Event {
14350 	enum EventString = "mouseenter"; ///
14351 	this(Widget target) { super(EventString, target); }
14352 
14353 	override bool propagates() const { return false; }
14354 }
14355 /// ditto
14356 class MouseLeaveEvent : Event {
14357 	enum EventString = "mouseleave"; ///
14358 	this(Widget target) { super(EventString, target); }
14359 
14360 	override bool propagates() const { return false; }
14361 }
14362 
14363 private bool isAParentOf(Widget a, Widget b) {
14364 	if(a is null || b is null)
14365 		return false;
14366 
14367 	while(b !is null) {
14368 		if(a is b)
14369 			return true;
14370 		b = b.parent;
14371 	}
14372 
14373 	return false;
14374 }
14375 
14376 private struct WidgetAtPointResponse {
14377 	Widget widget;
14378 
14379 	// x, y relative to the widget in the response.
14380 	int x;
14381 	int y;
14382 }
14383 
14384 private WidgetAtPointResponse widgetAtPoint(Widget starting, int x, int y) {
14385 	assert(starting !is null);
14386 
14387 	starting.addScrollPosition(x, y);
14388 
14389 	auto child = starting.getChildAtPosition(x, y);
14390 	while(child) {
14391 		if(child.hidden)
14392 			continue;
14393 		starting = child;
14394 		x -= child.x;
14395 		y -= child.y;
14396 		auto r = starting.widgetAtPoint(x, y);//starting.getChildAtPosition(x, y);
14397 		child = r.widget;
14398 		if(child is starting)
14399 			break;
14400 	}
14401 	return WidgetAtPointResponse(starting, x, y);
14402 }
14403 
14404 version(win32_widgets) {
14405 private:
14406 	import core.sys.windows.commctrl;
14407 
14408 	pragma(lib, "comctl32");
14409 	shared static this() {
14410 		// http://msdn.microsoft.com/en-us/library/windows/desktop/bb775507(v=vs.85).aspx
14411 		INITCOMMONCONTROLSEX ic;
14412 		ic.dwSize = cast(DWORD) ic.sizeof;
14413 		ic.dwICC = ICC_UPDOWN_CLASS | ICC_WIN95_CLASSES | ICC_BAR_CLASSES | ICC_PROGRESS_CLASS | ICC_COOL_CLASSES | ICC_STANDARD_CLASSES | ICC_USEREX_CLASSES;
14414 		if(!InitCommonControlsEx(&ic)) {
14415 			//writeln("ICC failed");
14416 		}
14417 	}
14418 
14419 
14420 	// everything from here is just win32 headers copy pasta
14421 private:
14422 extern(Windows):
14423 
14424 	alias HANDLE HMENU;
14425 	HMENU CreateMenu();
14426 	bool SetMenu(HWND, HMENU);
14427 	HMENU CreatePopupMenu();
14428 	enum MF_POPUP = 0x10;
14429 	enum MF_STRING = 0;
14430 
14431 
14432 	BOOL InitCommonControlsEx(const INITCOMMONCONTROLSEX*);
14433 	struct INITCOMMONCONTROLSEX {
14434 		DWORD dwSize;
14435 		DWORD dwICC;
14436 	}
14437 	enum HINST_COMMCTRL = cast(HINSTANCE) (-1);
14438 enum {
14439         IDB_STD_SMALL_COLOR,
14440         IDB_STD_LARGE_COLOR,
14441         IDB_VIEW_SMALL_COLOR = 4,
14442         IDB_VIEW_LARGE_COLOR = 5
14443 }
14444 enum {
14445         STD_CUT,
14446         STD_COPY,
14447         STD_PASTE,
14448         STD_UNDO,
14449         STD_REDOW,
14450         STD_DELETE,
14451         STD_FILENEW,
14452         STD_FILEOPEN,
14453         STD_FILESAVE,
14454         STD_PRINTPRE,
14455         STD_PROPERTIES,
14456         STD_HELP,
14457         STD_FIND,
14458         STD_REPLACE,
14459         STD_PRINT // = 14
14460 }
14461 
14462 alias HANDLE HIMAGELIST;
14463 	HIMAGELIST ImageList_Create(int, int, UINT, int, int);
14464 	int ImageList_Add(HIMAGELIST, HBITMAP, HBITMAP);
14465         BOOL ImageList_Destroy(HIMAGELIST);
14466 
14467 uint MAKELONG(ushort a, ushort b) {
14468         return cast(uint) ((b << 16) | a);
14469 }
14470 
14471 
14472 struct TBBUTTON {
14473 	int   iBitmap;
14474 	int   idCommand;
14475 	BYTE  fsState;
14476 	BYTE  fsStyle;
14477 	version(Win64)
14478 	BYTE[6] bReserved;
14479 	else
14480 	BYTE[2]  bReserved;
14481 	DWORD dwData;
14482 	INT_PTR   iString;
14483 }
14484 
14485 	enum {
14486 		TB_ADDBUTTONSA   = WM_USER + 20,
14487 		TB_INSERTBUTTONA = WM_USER + 21,
14488 		TB_GETIDEALSIZE = WM_USER + 99,
14489 	}
14490 
14491 struct SIZE {
14492 	LONG cx;
14493 	LONG cy;
14494 }
14495 
14496 
14497 enum {
14498 	TBSTATE_CHECKED       = 1,
14499 	TBSTATE_PRESSED       = 2,
14500 	TBSTATE_ENABLED       = 4,
14501 	TBSTATE_HIDDEN        = 8,
14502 	TBSTATE_INDETERMINATE = 16,
14503 	TBSTATE_WRAP          = 32
14504 }
14505 
14506 
14507 
14508 enum {
14509 	ILC_COLOR    = 0,
14510 	ILC_COLOR4   = 4,
14511 	ILC_COLOR8   = 8,
14512 	ILC_COLOR16  = 16,
14513 	ILC_COLOR24  = 24,
14514 	ILC_COLOR32  = 32,
14515 	ILC_COLORDDB = 254,
14516 	ILC_MASK     = 1,
14517 	ILC_PALETTE  = 2048
14518 }
14519 
14520 
14521 alias TBBUTTON*       PTBBUTTON, LPTBBUTTON;
14522 
14523 
14524 enum {
14525 	TB_ENABLEBUTTON          = WM_USER + 1,
14526 	TB_CHECKBUTTON,
14527 	TB_PRESSBUTTON,
14528 	TB_HIDEBUTTON,
14529 	TB_INDETERMINATE, //     = WM_USER + 5,
14530 	TB_ISBUTTONENABLED       = WM_USER + 9,
14531 	TB_ISBUTTONCHECKED,
14532 	TB_ISBUTTONPRESSED,
14533 	TB_ISBUTTONHIDDEN,
14534 	TB_ISBUTTONINDETERMINATE, // = WM_USER + 13,
14535 	TB_SETSTATE              = WM_USER + 17,
14536 	TB_GETSTATE              = WM_USER + 18,
14537 	TB_ADDBITMAP             = WM_USER + 19,
14538 	TB_DELETEBUTTON          = WM_USER + 22,
14539 	TB_GETBUTTON,
14540 	TB_BUTTONCOUNT,
14541 	TB_COMMANDTOINDEX,
14542 	TB_SAVERESTOREA,
14543 	TB_CUSTOMIZE,
14544 	TB_ADDSTRINGA,
14545 	TB_GETITEMRECT,
14546 	TB_BUTTONSTRUCTSIZE,
14547 	TB_SETBUTTONSIZE,
14548 	TB_SETBITMAPSIZE,
14549 	TB_AUTOSIZE, //          = WM_USER + 33,
14550 	TB_GETTOOLTIPS           = WM_USER + 35,
14551 	TB_SETTOOLTIPS           = WM_USER + 36,
14552 	TB_SETPARENT             = WM_USER + 37,
14553 	TB_SETROWS               = WM_USER + 39,
14554 	TB_GETROWS,
14555 	TB_GETBITMAPFLAGS,
14556 	TB_SETCMDID,
14557 	TB_CHANGEBITMAP,
14558 	TB_GETBITMAP,
14559 	TB_GETBUTTONTEXTA,
14560 	TB_REPLACEBITMAP, //     = WM_USER + 46,
14561 	TB_GETBUTTONSIZE         = WM_USER + 58,
14562 	TB_SETBUTTONWIDTH        = WM_USER + 59,
14563 	TB_GETBUTTONTEXTW        = WM_USER + 75,
14564 	TB_SAVERESTOREW          = WM_USER + 76,
14565 	TB_ADDSTRINGW            = WM_USER + 77,
14566 }
14567 
14568 extern(Windows)
14569 BOOL EnumChildWindows(HWND, WNDENUMPROC, LPARAM);
14570 
14571 alias extern(Windows) BOOL function (HWND, LPARAM) WNDENUMPROC;
14572 
14573 
14574 	enum {
14575 		TB_SETINDENT = WM_USER + 47,
14576 		TB_SETIMAGELIST,
14577 		TB_GETIMAGELIST,
14578 		TB_LOADIMAGES,
14579 		TB_GETRECT,
14580 		TB_SETHOTIMAGELIST,
14581 		TB_GETHOTIMAGELIST,
14582 		TB_SETDISABLEDIMAGELIST,
14583 		TB_GETDISABLEDIMAGELIST,
14584 		TB_SETSTYLE,
14585 		TB_GETSTYLE,
14586 		//TB_GETBUTTONSIZE,
14587 		//TB_SETBUTTONWIDTH,
14588 		TB_SETMAXTEXTROWS,
14589 		TB_GETTEXTROWS // = WM_USER + 61
14590 	}
14591 
14592 enum {
14593 	CCM_FIRST            = 0x2000,
14594 	CCM_LAST             = CCM_FIRST + 0x200,
14595 	CCM_SETBKCOLOR       = 8193,
14596 	CCM_SETCOLORSCHEME   = 8194,
14597 	CCM_GETCOLORSCHEME   = 8195,
14598 	CCM_GETDROPTARGET    = 8196,
14599 	CCM_SETUNICODEFORMAT = 8197,
14600 	CCM_GETUNICODEFORMAT = 8198,
14601 	CCM_SETVERSION       = 0x2007,
14602 	CCM_GETVERSION       = 0x2008,
14603 	CCM_SETNOTIFYWINDOW  = 0x2009
14604 }
14605 
14606 
14607 enum {
14608 	PBM_SETRANGE     = WM_USER + 1,
14609 	PBM_SETPOS,
14610 	PBM_DELTAPOS,
14611 	PBM_SETSTEP,
14612 	PBM_STEPIT,   // = WM_USER + 5
14613 	PBM_SETRANGE32   = 1030,
14614 	PBM_GETRANGE,
14615 	PBM_GETPOS,
14616 	PBM_SETBARCOLOR, // = 1033
14617 	PBM_SETBKCOLOR   = CCM_SETBKCOLOR
14618 }
14619 
14620 enum {
14621 	PBS_SMOOTH   = 1,
14622 	PBS_VERTICAL = 4
14623 }
14624 
14625 enum {
14626         ICC_LISTVIEW_CLASSES = 1,
14627         ICC_TREEVIEW_CLASSES = 2,
14628         ICC_BAR_CLASSES      = 4,
14629         ICC_TAB_CLASSES      = 8,
14630         ICC_UPDOWN_CLASS     = 16,
14631         ICC_PROGRESS_CLASS   = 32,
14632         ICC_HOTKEY_CLASS     = 64,
14633         ICC_ANIMATE_CLASS    = 128,
14634         ICC_WIN95_CLASSES    = 255,
14635         ICC_DATE_CLASSES     = 256,
14636         ICC_USEREX_CLASSES   = 512,
14637         ICC_COOL_CLASSES     = 1024,
14638 	ICC_STANDARD_CLASSES = 0x00004000,
14639 }
14640 
14641 	enum WM_USER = 1024;
14642 }
14643 
14644 version(win32_widgets)
14645 	pragma(lib, "comdlg32");
14646 
14647 
14648 ///
14649 enum GenericIcons : ushort {
14650 	None, ///
14651 	// these happen to match the win32 std icons numerically if you just subtract one from the value
14652 	Cut, ///
14653 	Copy, ///
14654 	Paste, ///
14655 	Undo, ///
14656 	Redo, ///
14657 	Delete, ///
14658 	New, ///
14659 	Open, ///
14660 	Save, ///
14661 	PrintPreview, ///
14662 	Properties, ///
14663 	Help, ///
14664 	Find, ///
14665 	Replace, ///
14666 	Print, ///
14667 }
14668 
14669 enum FileDialogType {
14670 	Automatic,
14671 	Open,
14672 	Save
14673 }
14674 string previousFileReferenced;
14675 
14676 /++
14677 	Used in automatic menu functions to indicate that the user should be able to browse for a file.
14678 
14679 	Params:
14680 		storage = an alias to a `static string` variable that stores the last file referenced. It will
14681 		use this to pre-fill the dialog with a suggestion.
14682 
14683 		Please note that it MUST be `static` or you will get compile errors.
14684 
14685 		filters = the filters param to [getFileName]
14686 
14687 		type = the type if dialog to show. If `FileDialogType.Automatic`, it the driver code will
14688 		guess based on the function name. If it has the word "Save" or "Export" in it, it will show
14689 		a save dialog box. Otherwise, it will show an open dialog box.
14690 +/
14691 struct FileName(alias storage = previousFileReferenced, string[] filters = null, FileDialogType type = FileDialogType.Automatic) {
14692 	string name;
14693 	alias name this;
14694 }
14695 
14696 /++
14697 	Gets a file name for an open or save operation, calling your `onOK` function when the user has selected one. This function may or may not block depending on the operating system, you MUST assume it will complete asynchronously.
14698 
14699 	History:
14700 		onCancel was added November 6, 2021.
14701 
14702 		The dialog itself on Linux was modified on December 2, 2021 to include
14703 		a directory picker in addition to the command line completion view.
14704 
14705 		The `initialDirectory` argument was added November 9, 2022 (dub v10.10)
14706 	Future_directions:
14707 		I want to add some kind of custom preview and maybe thumbnail thing in the future,
14708 		at least on Linux, maybe on Windows too.
14709 +/
14710 void getOpenFileName(
14711 	void delegate(string) onOK,
14712 	string prefilledName = null,
14713 	string[] filters = null,
14714 	void delegate() onCancel = null,
14715 	string initialDirectory = null,
14716 )
14717 {
14718 	return getFileName(true, onOK, prefilledName, filters, onCancel, initialDirectory);
14719 }
14720 
14721 /// ditto
14722 void getSaveFileName(
14723 	void delegate(string) onOK,
14724 	string prefilledName = null,
14725 	string[] filters = null,
14726 	void delegate() onCancel = null,
14727 	string initialDirectory = null,
14728 )
14729 {
14730 	return getFileName(false, onOK, prefilledName, filters, onCancel, initialDirectory);
14731 }
14732 
14733 void getFileName(
14734 	bool openOrSave,
14735 	void delegate(string) onOK,
14736 	string prefilledName = null,
14737 	string[] filters = null, // format here is like ["Text files\0*.txt;*.text", "Image files\0*.png;*.jpg"]
14738 	void delegate() onCancel = null,
14739 	string initialDirectory = null,
14740 )
14741 {
14742 
14743 	version(win32_widgets) {
14744 		import core.sys.windows.commdlg;
14745 	/*
14746 	Ofn.lStructSize = sizeof(OPENFILENAME);
14747 	Ofn.hwndOwner = hWnd;
14748 	Ofn.lpstrFilter = szFilter;
14749 	Ofn.lpstrFile= szFile;
14750 	Ofn.nMaxFile = sizeof(szFile)/ sizeof(*szFile);
14751 	Ofn.lpstrFileTitle = szFileTitle;
14752 	Ofn.nMaxFileTitle = sizeof(szFileTitle);
14753 	Ofn.lpstrInitialDir = (LPSTR)NULL;
14754 	Ofn.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT;
14755 	Ofn.lpstrTitle = szTitle;
14756 	 */
14757 
14758 
14759 		wchar[1024] file = 0;
14760 		wchar[1024] filterBuffer = 0;
14761 		makeWindowsString(prefilledName, file[]);
14762 		OPENFILENAME ofn;
14763 		ofn.lStructSize = ofn.sizeof;
14764 		if(filters.length) {
14765 			string filter;
14766 			foreach(i, f; filters) {
14767 				filter ~= f;
14768 				filter ~= "\0";
14769 			}
14770 			filter ~= "\0";
14771 			ofn.lpstrFilter = makeWindowsString(filter, filterBuffer[], 0 /* already terminated */).ptr;
14772 		}
14773 		ofn.lpstrFile = file.ptr;
14774 		ofn.nMaxFile = file.length;
14775 
14776 		wchar[1024] initialDir = 0;
14777 		if(initialDirectory !is null) {
14778 			makeWindowsString(initialDirectory, initialDir[]);
14779 			ofn.lpstrInitialDir = file.ptr;
14780 		}
14781 
14782 		if(openOrSave ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn))
14783 		{
14784 			string okString = makeUtf8StringFromWindowsString(ofn.lpstrFile);
14785 			if(okString.length && okString[$-1] == '\0')
14786 				okString = okString[0..$-1];
14787 			onOK(okString);
14788 		} else {
14789 			if(onCancel)
14790 				onCancel();
14791 		}
14792 	} else version(custom_widgets) {
14793 		if(filters.length == 0)
14794 			filters = ["All Files\0*.*"];
14795 		auto picker = new FilePicker(prefilledName, filters, initialDirectory);
14796 		picker.onOK = onOK;
14797 		picker.onCancel = onCancel;
14798 		picker.show();
14799 	}
14800 }
14801 
14802 version(custom_widgets)
14803 private
14804 class FilePicker : Dialog {
14805 	void delegate(string) onOK;
14806 	void delegate() onCancel;
14807 	LineEdit lineEdit;
14808 
14809 	// returns common prefix
14810 	string loadFiles(string cwd, string[] filters...) {
14811 		string[] files;
14812 		string[] dirs;
14813 
14814 		string commonPrefix;
14815 
14816 		getFiles(cwd, (string name, bool isDirectory) {
14817 			if(name == ".")
14818 				return; // skip this as unnecessary
14819 			if(isDirectory)
14820 				dirs ~= name;
14821 			else {
14822 				foreach(filter; filters)
14823 				if(
14824 					filter.length <= 1 ||
14825 					filter == "*.*" ||
14826 					(filter[0] == '*' && name.endsWith(filter[1 .. $])) ||
14827 					(filter[$-1] == '*' && name.startsWith(filter[0 .. $ - 1]))
14828 				)
14829 				{
14830 					files ~= name;
14831 
14832 					if(filter.length > 0 && filter[$-1] == '*') {
14833 						if(commonPrefix is null) {
14834 							commonPrefix = name;
14835 						} else {
14836 							foreach(idx, char i; name) {
14837 								if(idx >= commonPrefix.length || i != commonPrefix[idx]) {
14838 									commonPrefix = commonPrefix[0 .. idx];
14839 									break;
14840 								}
14841 							}
14842 						}
14843 					}
14844 
14845 					break;
14846 				}
14847 			}
14848 		});
14849 
14850 		extern(C) static int comparator(scope const void* a, scope const void* b) {
14851 			auto sa = *cast(string*) a;
14852 			auto sb = *cast(string*) b;
14853 
14854 			for(int i = 0; i < sa.length; i++) {
14855 				if(i == sb.length)
14856 					return 1;
14857 				return sa[i] - sb[i];
14858 			}
14859 
14860 			return 0;
14861 		}
14862 
14863 		nonPhobosSort(files, &comparator);
14864 		nonPhobosSort(dirs, &comparator);
14865 
14866 		listWidget.clear();
14867 		dirWidget.clear();
14868 		foreach(name; dirs)
14869 			dirWidget.addOption(name);
14870 		foreach(name; files)
14871 			listWidget.addOption(name);
14872 
14873 		return commonPrefix;
14874 	}
14875 
14876 	ListWidget listWidget;
14877 	ListWidget dirWidget;
14878 
14879 	string currentDirectory;
14880 	string[] processedFilters;
14881 
14882 	//string[] filters = null, // format here is like ["Text files\0*.txt;*.text", "Image files\n*.png;*.jpg"]
14883 	this(string prefilledName, string[] filters, string initialDirectory, Window owner = null) {
14884 		super(300, 200, "Choose File..."); // owner);
14885 
14886 		foreach(filter; filters) {
14887 			while(filter.length && filter[0] != 0) {
14888 				filter = filter[1 .. $];
14889 			}
14890 			if(filter.length)
14891 				filter = filter[1 .. $]; // trim off the 0
14892 
14893 			while(filter.length) {
14894 				int idx = 0;
14895 				while(idx < filter.length && filter[idx] != ';') {
14896 					idx++;
14897 				}
14898 
14899 				processedFilters ~= filter[0 .. idx];
14900 				if(idx < filter.length)
14901 					idx++; // skip the ;
14902 				filter = filter[idx .. $];
14903 			}
14904 		}
14905 
14906 		currentDirectory = initialDirectory is null ? "." : initialDirectory;
14907 
14908 		{
14909 			auto hl = new HorizontalLayout(this);
14910 			dirWidget = new ListWidget(hl);
14911 			listWidget = new ListWidget(hl);
14912 
14913 			// double click events normally trigger something else but
14914 			// here user might be clicking kinda fast and we'd rather just
14915 			// keep it
14916 			dirWidget.addEventListener((scope DoubleClickEvent dev) {
14917 				auto ce = new ChangeEvent!void(dirWidget, () {});
14918 				ce.dispatch();
14919 			});
14920 
14921 			dirWidget.addEventListener((scope ChangeEvent!void sce) {
14922 				string v;
14923 				foreach(o; dirWidget.options)
14924 					if(o.selected) {
14925 						v = o.label;
14926 						break;
14927 					}
14928 				if(v.length) {
14929 					currentDirectory ~= "/" ~ v;
14930 					loadFiles(currentDirectory, processedFilters);
14931 				}
14932 			});
14933 
14934 			// double click here, on the other hand, selects the file
14935 			// and moves on
14936 			listWidget.addEventListener((scope DoubleClickEvent dev) {
14937 				OK();
14938 			});
14939 		}
14940 
14941 		lineEdit = new LineEdit(this);
14942 		lineEdit.focus();
14943 		lineEdit.addEventListener(delegate(CharEvent event) {
14944 			if(event.character == '\t' || event.character == '\n')
14945 				event.preventDefault();
14946 		});
14947 
14948 		listWidget.addEventListener(EventType.change, () {
14949 			foreach(o; listWidget.options)
14950 				if(o.selected)
14951 					lineEdit.content = o.label;
14952 		});
14953 
14954 		loadFiles(currentDirectory, processedFilters);
14955 
14956 		lineEdit.addEventListener((KeyDownEvent event) {
14957 			if(event.key == Key.Tab) {
14958 
14959 				auto current = lineEdit.content;
14960 				if(current.length >= 2 && current[0 ..2] == "./")
14961 					current = current[2 .. $];
14962 
14963 				auto commonPrefix = loadFiles(".", current ~ "*");
14964 
14965 				if(commonPrefix.length)
14966 					lineEdit.content = commonPrefix;
14967 
14968 				// FIXME: if that is a directory, add the slash? or even go inside?
14969 
14970 				event.preventDefault();
14971 			}
14972 		});
14973 
14974 		lineEdit.content = prefilledName;
14975 
14976 		auto hl = new HorizontalLayout(60, this);
14977 		auto cancelButton = new Button("Cancel", hl);
14978 		auto okButton = new Button("OK", hl);
14979 
14980 		cancelButton.addEventListener(EventType.triggered, &Cancel);
14981 		okButton.addEventListener(EventType.triggered, &OK);
14982 
14983 		this.addEventListener((KeyDownEvent event) {
14984 			if(event.key == Key.Enter || event.key == Key.PadEnter) {
14985 				event.preventDefault();
14986 				OK();
14987 			}
14988 			if(event.key == Key.Escape)
14989 				Cancel();
14990 		});
14991 
14992 	}
14993 
14994 	override void OK() {
14995 		if(lineEdit.content.length) {
14996 			string accepted;
14997 			auto c = lineEdit.content;
14998 			if(c.length && c[0] == '/')
14999 				accepted = c;
15000 			else
15001 				accepted = currentDirectory ~ "/" ~ lineEdit.content;
15002 
15003 			if(isDir(accepted)) {
15004 				// FIXME: would be kinda nice to support ~ and collapse these paths too
15005 				// FIXME: would also be nice to actually show the "Looking in..." directory and maybe the filters but later.
15006 				currentDirectory = accepted;
15007 				loadFiles(currentDirectory, processedFilters);
15008 				lineEdit.content = "";
15009 				return;
15010 			}
15011 
15012 			if(onOK)
15013 				onOK(accepted);
15014 		}
15015 		close();
15016 	}
15017 
15018 	override void Cancel() {
15019 		if(onCancel)
15020 			onCancel();
15021 		close();
15022 	}
15023 }
15024 
15025 private bool isDir(string name) {
15026 	version(Windows) {
15027 		auto ws = WCharzBuffer(name);
15028 		auto ret = GetFileAttributesW(ws.ptr);
15029 		if(ret == INVALID_FILE_ATTRIBUTES)
15030 			return false;
15031 		return (ret & FILE_ATTRIBUTE_DIRECTORY) != 0;
15032 	} else version(Posix) {
15033 		import core.sys.posix.sys.stat;
15034 		stat_t buf;
15035 		auto ret = stat((name ~ '\0').ptr, &buf);
15036 		if(ret == -1)
15037 			return false; // I could probably check more specific errors tbh
15038 		return (buf.st_mode & S_IFMT) == S_IFDIR;
15039 	} else return false;
15040 }
15041 
15042 /*
15043 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775947%28v=vs.85%29.aspx#check_boxes
15044 http://msdn.microsoft.com/en-us/library/windows/desktop/ms633574%28v=vs.85%29.aspx
15045 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775943%28v=vs.85%29.aspx
15046 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775951%28v=vs.85%29.aspx
15047 http://msdn.microsoft.com/en-us/library/windows/desktop/ms632680%28v=vs.85%29.aspx
15048 http://msdn.microsoft.com/en-us/library/windows/desktop/ms644996%28v=vs.85%29.aspx#message_box
15049 http://www.sbin.org/doc/Xlib/chapt_03.html
15050 
15051 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760433%28v=vs.85%29.aspx
15052 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760446%28v=vs.85%29.aspx
15053 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760443%28v=vs.85%29.aspx
15054 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760476%28v=vs.85%29.aspx
15055 */
15056 
15057 
15058 // These are all for setMenuAndToolbarFromAnnotatedCode
15059 /// This item in the menu will be preceded by a separator line
15060 /// Group: generating_from_code
15061 struct separator {}
15062 deprecated("It was misspelled, use separator instead") alias seperator = separator;
15063 /// Program-wide keyboard shortcut to trigger the action
15064 /// Group: generating_from_code
15065 struct accelerator { string keyString; }
15066 /// tells which menu the action will be on
15067 /// Group: generating_from_code
15068 struct menu { string name; }
15069 /// Describes which toolbar section the action appears on
15070 /// Group: generating_from_code
15071 struct toolbar { string groupName; }
15072 ///
15073 /// Group: generating_from_code
15074 struct icon { ushort id; }
15075 ///
15076 /// Group: generating_from_code
15077 struct label { string label; }
15078 ///
15079 /// Group: generating_from_code
15080 struct hotkey { dchar ch; }
15081 ///
15082 /// Group: generating_from_code
15083 struct tip { string tip; }
15084 
15085 
15086 /++
15087 	Observes and allows inspection of an object via automatic gui
15088 +/
15089 /// Group: generating_from_code
15090 ObjectInspectionWindow objectInspectionWindow(T)(T t) if(is(T == class)) {
15091 	return new ObjectInspectionWindowImpl!(T)(t);
15092 }
15093 
15094 class ObjectInspectionWindow : Window {
15095 	this(int a, int b, string c) {
15096 		super(a, b, c);
15097 	}
15098 
15099 	abstract void readUpdatesFromObject();
15100 }
15101 
15102 class ObjectInspectionWindowImpl(T) : ObjectInspectionWindow {
15103 	T t;
15104 	this(T t) {
15105 		this.t = t;
15106 
15107 		super(300, 400, "ObjectInspectionWindow - " ~ T.stringof);
15108 
15109 		foreach(memberName; __traits(derivedMembers, T)) {{
15110 			alias member = I!(__traits(getMember, t, memberName))[0];
15111 			alias type = typeof(member);
15112 			static if(is(type == int)) {
15113 				auto le = new LabeledLineEdit(memberName ~ ": ", this);
15114 				//le.addEventListener("char", (Event ev) {
15115 					//if((ev.character < '0' || ev.character > '9') && ev.character != '-')
15116 						//ev.preventDefault();
15117 				//});
15118 				le.addEventListener(EventType.change, (Event ev) {
15119 					__traits(getMember, t, memberName) = cast(type) stringToLong(ev.stringValue);
15120 				});
15121 
15122 				updateMemberDelegates[memberName] = () {
15123 					le.content = toInternal!string(__traits(getMember, t, memberName));
15124 				};
15125 			}
15126 		}}
15127 	}
15128 
15129 	void delegate()[string] updateMemberDelegates;
15130 
15131 	override void readUpdatesFromObject() {
15132 		foreach(k, v; updateMemberDelegates)
15133 			v();
15134 	}
15135 }
15136 
15137 /++
15138 	Creates a dialog based on a data structure.
15139 
15140 	---
15141 	dialog((YourStructure value) {
15142 		// the user filled in the struct and clicked OK,
15143 		// you can check the members now
15144 	});
15145 	---
15146 
15147 	Params:
15148 		initialData = the initial value to show in the dialog. It will not modify this unless
15149 		it is a class then it might, no promises.
15150 
15151 	History:
15152 		The overload that lets you specify `initialData` was added on December 30, 2021 (dub v10.5)
15153 +/
15154 /// Group: generating_from_code
15155 void dialog(T)(void delegate(T) onOK, void delegate() onCancel = null, string title = T.stringof) {
15156 	dialog(T.init, onOK, onCancel, title);
15157 }
15158 /// ditto
15159 void dialog(T)(T initialData, void delegate(T) onOK, void delegate() onCancel = null, string title = T.stringof) {
15160 	auto dg = new AutomaticDialog!T(initialData, onOK, onCancel, title);
15161 	dg.show();
15162 }
15163 
15164 private static template I(T...) { alias I = T; }
15165 
15166 
15167 private string beautify(string name, char space = ' ', bool allLowerCase = false) {
15168 	if(name == "id")
15169 		return allLowerCase ? name : "ID";
15170 
15171 	char[160] buffer;
15172 	int bufferIndex = 0;
15173 	bool shouldCap = true;
15174 	bool shouldSpace;
15175 	bool lastWasCap;
15176 	foreach(idx, char ch; name) {
15177 		if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
15178 
15179 		if((ch >= 'A' && ch <= 'Z') || ch == '_') {
15180 			if(lastWasCap) {
15181 				// two caps in a row, don't change. Prolly acronym.
15182 			} else {
15183 				if(idx)
15184 					shouldSpace = true; // new word, add space
15185 			}
15186 
15187 			lastWasCap = true;
15188 		} else {
15189 			lastWasCap = false;
15190 		}
15191 
15192 		if(shouldSpace) {
15193 			buffer[bufferIndex++] = space;
15194 			if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
15195 			shouldSpace = false;
15196 		}
15197 		if(shouldCap) {
15198 			if(ch >= 'a' && ch <= 'z')
15199 				ch -= 32;
15200 			shouldCap = false;
15201 		}
15202 		if(allLowerCase && ch >= 'A' && ch <= 'Z')
15203 			ch += 32;
15204 		buffer[bufferIndex++] = ch;
15205 	}
15206 	return buffer[0 .. bufferIndex].idup;
15207 }
15208 
15209 /++
15210 	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.
15211 +/
15212 class AutomaticDialog(T) : Dialog {
15213 	T t;
15214 
15215 	void delegate(T) onOK;
15216 	void delegate() onCancel;
15217 
15218 	override int paddingTop() { return defaultLineHeight; }
15219 	override int paddingBottom() { return defaultLineHeight; }
15220 	override int paddingRight() { return defaultLineHeight; }
15221 	override int paddingLeft() { return defaultLineHeight; }
15222 
15223 	this(T initialData, void delegate(T) onOK, void delegate() onCancel, string title) {
15224 		assert(onOK !is null);
15225 
15226 		t = initialData;
15227 
15228 		static if(is(T == class)) {
15229 			if(t is null)
15230 				t = new T();
15231 		}
15232 		this.onOK = onOK;
15233 		this.onCancel = onCancel;
15234 		super(400, cast(int)(__traits(allMembers, T).length * 2) * (defaultLineHeight + scaleWithDpi(4 + 2)) + defaultLineHeight + scaleWithDpi(56), title);
15235 
15236 		static if(is(T == class))
15237 			this.addDataControllerWidget(t);
15238 		else
15239 			this.addDataControllerWidget(&t);
15240 
15241 		auto hl = new HorizontalLayout(this);
15242 		auto stretch = new HorizontalSpacer(hl); // to right align
15243 		auto ok = new CommandButton("OK", hl);
15244 		auto cancel = new CommandButton("Cancel", hl);
15245 		ok.addEventListener(EventType.triggered, &OK);
15246 		cancel.addEventListener(EventType.triggered, &Cancel);
15247 
15248 		this.addEventListener((KeyDownEvent ev) {
15249 			if(ev.key == Key.Enter || ev.key == Key.PadEnter) {
15250 				ok.focus();
15251 				OK();
15252 				ev.preventDefault();
15253 			}
15254 			if(ev.key == Key.Escape) {
15255 				Cancel();
15256 				ev.preventDefault();
15257 			}
15258 		});
15259 
15260 		this.addEventListener((scope ClosedEvent ce) {
15261 			if(onCancel)
15262 				onCancel();
15263 		});
15264 
15265 		//this.children[0].focus();
15266 	}
15267 
15268 	override void OK() {
15269 		onOK(t);
15270 		close();
15271 	}
15272 
15273 	override void Cancel() {
15274 		if(onCancel)
15275 			onCancel();
15276 		close();
15277 	}
15278 }
15279 
15280 private template baseClassCount(Class) {
15281 	private int helper() {
15282 		int count = 0;
15283 		static if(is(Class bases == super)) {
15284 			foreach(base; bases)
15285 				static if(is(base == class))
15286 					count += 1 + baseClassCount!base;
15287 		}
15288 		return count;
15289 	}
15290 
15291 	enum int baseClassCount = helper();
15292 }
15293 
15294 private long stringToLong(string s) {
15295 	long ret;
15296 	if(s.length == 0)
15297 		return ret;
15298 	bool negative = s[0] == '-';
15299 	if(negative)
15300 		s = s[1 .. $];
15301 	foreach(ch; s) {
15302 		if(ch >= '0' && ch <= '9') {
15303 			ret *= 10;
15304 			ret += ch - '0';
15305 		}
15306 	}
15307 	if(negative)
15308 		ret = -ret;
15309 	return ret;
15310 }
15311 
15312 
15313 interface ReflectableProperties {
15314 	/++
15315 		Iterates the event's properties as strings. Note that keys may be repeated and a get property request may
15316 		call your sink with `null`. It it does, it means the key either doesn't request or cannot be represented by
15317 		json in the current implementation.
15318 
15319 		This is auto-implemented for you if you mixin [RegisterGetters] in your child classes and only have
15320 		properties of type `bool`, `int`, `double`, or `string`. For other ones, you will need to do it yourself
15321 		as of the June 2, 2021 release.
15322 
15323 		History:
15324 			Added June 2, 2021.
15325 
15326 		See_Also: [getPropertyAsString], [setPropertyFromString]
15327 	+/
15328 	void getPropertiesList(scope void delegate(string name) sink) const;// @nogc pure nothrow;
15329 	/++
15330 		Requests a property to be delivered to you as a string, through your `sink` delegate.
15331 
15332 		If the `value` is null, it means the property could not be retreived. If `valueIsJson`, it should
15333 		be interpreted as json, otherwise, it is just a plain string.
15334 
15335 		The sink should always be called exactly once for each call (it is basically a return value, but it might
15336 		use a local buffer it maintains instead of allocating a return value).
15337 
15338 		History:
15339 			Added June 2, 2021.
15340 
15341 		See_Also: [getPropertiesList], [setPropertyFromString]
15342 	+/
15343 	void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink);
15344 	/++
15345 		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.
15346 
15347 		History:
15348 			Added June 2, 2021.
15349 
15350 		See_Also: [getPropertiesList], [getPropertyAsString], [SetPropertyResult]
15351 	+/
15352 	SetPropertyResult setPropertyFromString(string name, scope const(char)[] str, bool strIsJson);
15353 
15354 	/// [setPropertyFromString] possible return values
15355 	enum SetPropertyResult {
15356 		success = 0, /// the property has been successfully set to the request value
15357 		notPermitted = -1, /// the property exists but it cannot be changed at this time
15358 		notImplemented = -2, /// the set function is not implemented for the given property (which may or may not exist)
15359 		noSuchProperty = -3, /// there is no property by that name
15360 		wrongFormat = -4, /// the string was given in the wrong format, e.g. passing "two" for an int value
15361 		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)
15362 	}
15363 
15364 	/++
15365 		You can mix this in to get an implementation in child classes. This does [setPropertyFromString].
15366 
15367 		Your original base class, however, must implement its own methods. I recommend doing the initial ones by hand.
15368 
15369 		For [Widget] and [Event], the library provides [Widget.Register] and [Event.Register] that call these for you, so you should
15370 		rarely need to use these building blocks directly.
15371 	+/
15372 	mixin template RegisterSetters() {
15373 		override SetPropertyResult setPropertyFromString(string name, scope const(char)[] value, bool valueIsJson) {
15374 			switch(name) {
15375 				foreach(memberName; __traits(derivedMembers, typeof(this))) {
15376 					case memberName:
15377 						static if(is(typeof(__traits(getMember, this, memberName)) : const bool)) {
15378 							if(value != "true" && value != "false")
15379 								return SetPropertyResult.wrongFormat;
15380 							__traits(getMember, this, memberName) = value == "true" ? true : false;
15381 							return SetPropertyResult.success;
15382 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const long)) {
15383 							import core.stdc.stdlib;
15384 							char[128] zero = 0;
15385 							if(buffer.length + 1 >= zero.length)
15386 								return SetPropertyResult.wrongFormat;
15387 							zero[0 .. buffer.length] = buffer[];
15388 							__traits(getMember, this, memberName) = strtol(buffer.ptr, null, 10);
15389 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const double)) {
15390 							import core.stdc.stdlib;
15391 							char[128] zero = 0;
15392 							if(buffer.length + 1 >= zero.length)
15393 								return SetPropertyResult.wrongFormat;
15394 							zero[0 .. buffer.length] = buffer[];
15395 							__traits(getMember, this, memberName) = strtod(buffer.ptr, null, 10);
15396 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const string)) {
15397 							__traits(getMember, this, memberName) = value.idup;
15398 						} else {
15399 							return SetPropertyResult.notImplemented;
15400 						}
15401 
15402 				}
15403 				default:
15404 					return super.setPropertyFromString(name, value, valueIsJson);
15405 			}
15406 		}
15407 	}
15408 
15409 	/++
15410 		You can mix this in to get an implementation in child classes. This does [getPropertyAsString] and [getPropertiesList].
15411 
15412 		Your original base class, however, must implement its own methods. I recommend doing the initial ones by hand.
15413 
15414 		For [Widget] and [Event], the library provides [Widget.Register] and [Event.Register] that call these for you, so you should
15415 		rarely need to use these building blocks directly.
15416 	+/
15417 	mixin template RegisterGetters() {
15418 		override void getPropertiesList(scope void delegate(string name) sink) const {
15419 			super.getPropertiesList(sink);
15420 
15421 			foreach(memberName; __traits(derivedMembers, typeof(this))) {
15422 				sink(memberName);
15423 			}
15424 		}
15425 		override void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink) {
15426 			switch(name) {
15427 				foreach(memberName; __traits(derivedMembers, typeof(this))) {
15428 					case memberName:
15429 						static if(is(typeof(__traits(getMember, this, memberName)) : const bool)) {
15430 							sink(name, __traits(getMember, this, memberName) ? "true" : "false", true);
15431 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const long)) {
15432 							import core.stdc.stdio;
15433 							char[32] buffer;
15434 							auto len = snprintf(buffer.ptr, buffer.length, "%lld", cast(long) __traits(getMember, this, memberName));
15435 							sink(name, buffer[0 .. len], true);
15436 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const double)) {
15437 							import core.stdc.stdio;
15438 							char[32] buffer;
15439 							auto len = snprintf(buffer.ptr, buffer.length, "%f", cast(double) __traits(getMember, this, memberName));
15440 							sink(name, buffer[0 .. len], true);
15441 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const string)) {
15442 							sink(name, __traits(getMember, this, memberName), false);
15443 							//sinkJsonString(memberName, __traits(getMember, this, memberName), sink);
15444 						} else {
15445 							sink(name, null, true);
15446 						}
15447 
15448 					return;
15449 				}
15450 				default:
15451 					return super.getPropertyAsString(name, sink);
15452 			}
15453 		}
15454 	}
15455 }
15456 
15457 private struct Stack(T) {
15458 	this(int maxSize) {
15459 		internalLength = 0;
15460 		arr = initialBuffer[];
15461 	}
15462 
15463 	///.
15464 	void push(T t) {
15465 		if(internalLength >= arr.length) {
15466 			auto oldarr = arr;
15467 			if(arr.length < 4096)
15468 				arr = new T[arr.length * 2];
15469 			else
15470 				arr = new T[arr.length + 4096];
15471 			arr[0 .. oldarr.length] = oldarr[];
15472 		}
15473 
15474 		arr[internalLength] = t;
15475 		internalLength++;
15476 	}
15477 
15478 	///.
15479 	T pop() {
15480 		assert(internalLength);
15481 		internalLength--;
15482 		return arr[internalLength];
15483 	}
15484 
15485 	///.
15486 	T peek() {
15487 		assert(internalLength);
15488 		return arr[internalLength - 1];
15489 	}
15490 
15491 	///.
15492 	@property bool empty() {
15493 		return internalLength ? false : true;
15494 	}
15495 
15496 	///.
15497 	private T[] arr;
15498 	private size_t internalLength;
15499 	private T[64] initialBuffer;
15500 	// 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),
15501 	// using this saves us a bunch of trips to the GC. In my last profiling, I got about a 50x improvement in the push()
15502 	// function thanks to this, and push() was actually one of the slowest individual functions in the code!
15503 }
15504 
15505 /// 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.
15506 private struct WidgetStream {
15507 
15508 	///.
15509 	@property Widget front() {
15510 		return current.widget;
15511 	}
15512 
15513 	/// Use Widget.tree instead.
15514 	this(Widget start) {
15515 		current.widget = start;
15516 		current.childPosition = -1;
15517 		isEmpty = false;
15518 		stack = typeof(stack)(0);
15519 	}
15520 
15521 	/*
15522 		Handle it
15523 		handle its children
15524 
15525 	*/
15526 
15527 	///.
15528 	void popFront() {
15529 	    more:
15530 	    	if(isEmpty) return;
15531 
15532 		// FIXME: the profiler says this function is somewhat slow (noticeable because it can be called a lot of times)
15533 
15534 		current.childPosition++;
15535 		if(current.childPosition >= current.widget.children.length) {
15536 			if(stack.empty())
15537 				isEmpty = true;
15538 			else {
15539 				current = stack.pop();
15540 				goto more;
15541 			}
15542 		} else {
15543 			stack.push(current);
15544 			current.widget = current.widget.children[current.childPosition];
15545 			current.childPosition = -1;
15546 		}
15547 	}
15548 
15549 	///.
15550 	@property bool empty() {
15551 		return isEmpty;
15552 	}
15553 
15554 	private:
15555 
15556 	struct Current {
15557 		Widget widget;
15558 		int childPosition;
15559 	}
15560 
15561 	Current current;
15562 
15563 	Stack!(Current) stack;
15564 
15565 	bool isEmpty;
15566 }
15567 
15568 
15569 /+
15570 
15571 	I could fix up the hierarchy kinda like this
15572 
15573 	class Widget {
15574 		Widget[] children() { return null; }
15575 	}
15576 	interface WidgetContainer {
15577 		Widget asWidget();
15578 		void addChild(Widget w);
15579 
15580 		// alias asWidget this; // but meh
15581 	}
15582 
15583 	Widget can keep a (Widget parent) ctor, but it should prolly deprecate and tell people to instead change their ctors to take WidgetContainer instead.
15584 
15585 	class Layout : Widget, WidgetContainer {}
15586 
15587 	class Window : WidgetContainer {}
15588 
15589 
15590 	All constructors that previously took Widgets should now take WidgetContainers instead
15591 
15592 
15593 
15594 	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".
15595 +/
15596 
15597 /+
15598 	LAYOUTS 2.0
15599 
15600 	can just be assigned as a function. assigning a new one will cause it to be immediately called.
15601 
15602 	they simply are responsible for the recomputeChildLayout. If this pointer is null, it uses the default virtual one.
15603 
15604 	recomputeChildLayout only really needs a property accessor proxy... just the layout info too.
15605 
15606 	and even Paint can just use computedStyle...
15607 
15608 		background color
15609 		font
15610 		border color and style
15611 
15612 	And actually the style proxy can offer some helper routines to draw these like the draw 3d box
15613 		please note that many widgets and in some modes will completely ignore properties as they will.
15614 		they are just hints you set, not promises.
15615 
15616 
15617 
15618 
15619 
15620 	So generally the existing virtual functions are just the default for the class. But individual objects
15621 	or stylesheets can override this. The virtual ones count as tag-level specificity in css.
15622 +/
15623 
15624 /++
15625 	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.
15626 
15627 	History:
15628 		Added May 24, 2021.
15629 +/
15630 struct WidgetBackground {
15631 	/++
15632 		A background with the given solid color.
15633 	+/
15634 	this(Color color) {
15635 		this.color = color;
15636 	}
15637 
15638 	this(WidgetBackground bg) {
15639 		this = bg;
15640 	}
15641 
15642 	/++
15643 		Creates a widget from the string.
15644 
15645 		Currently, it only supports solid colors via [Color.fromString], but it will likely be expanded in the future to something more like css.
15646 	+/
15647 	static WidgetBackground fromString(string s) {
15648 		return WidgetBackground(Color.fromString(s));
15649 	}
15650 
15651 	/++
15652 		The background is not necessarily a solid color, but you can always specify a color as a fallback.
15653 
15654 		History:
15655 			Made `public` on December 18, 2022 (dub v10.10).
15656 	+/
15657 	Color color;
15658 }
15659 
15660 /++
15661 	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!)
15662 
15663 	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.
15664 
15665 	You should not inherit from this directly, but instead use [VisualTheme].
15666 
15667 	History:
15668 		Added May 8, 2021
15669 +/
15670 abstract class BaseVisualTheme {
15671 	/// Don't implement this, instead use [VisualTheme] and implement `paint` methods on specific subclasses you want to override.
15672 	abstract void doPaint(Widget widget, WidgetPainter painter);
15673 
15674 	/+
15675 	/// Don't implement this, instead use [VisualTheme] and implement `StyleOverride` aliases on specific subclasses you want to override.
15676 	abstract void useStyleProperties(Widget w, scope void delegate(scope Widget.Style props) dg);
15677 	+/
15678 
15679 	/++
15680 		Returns the property as a string, or null if it was not overridden in the style definition. The idea here is something like css,
15681 		where the interpretation of the string varies for each property and may include things like measurement units.
15682 	+/
15683 	abstract string getPropertyString(Widget widget, string propertyName);
15684 
15685 	/++
15686 		Default background color of the window. Widgets also use this to simulate transparency.
15687 
15688 		Probably some shade of grey.
15689 	+/
15690 	abstract Color windowBackgroundColor();
15691 	abstract Color widgetBackgroundColor();
15692 	abstract Color foregroundColor();
15693 	abstract Color lightAccentColor();
15694 	abstract Color darkAccentColor();
15695 
15696 	/++
15697 		Colors used to indicate active selections in lists and text boxes, etc.
15698 	+/
15699 	abstract Color selectionForegroundColor();
15700 	/// ditto
15701 	abstract Color selectionBackgroundColor();
15702 
15703 	deprecated("Use selectionForegroundColor and selectionBackgroundColor instead") Color selectionColor() { return selectionBackgroundColor(); }
15704 
15705 	/++
15706 		If you return `null` it will use simpledisplay's default. Otherwise, you return what font you want and it will cache it internally.
15707 	+/
15708 	abstract OperatingSystemFont defaultFont(int dpi);
15709 
15710 	private OperatingSystemFont[int] defaultFontCache_;
15711 	private OperatingSystemFont defaultFontCached(int dpi) {
15712 		if(dpi !in defaultFontCache_) {
15713 			// FIXME: set this to false if X disconnect or if visual theme changes
15714 			defaultFontCache_[dpi] = defaultFont(dpi);
15715 		}
15716 		return defaultFontCache_[dpi];
15717 	}
15718 }
15719 
15720 /+
15721 	A widget should have:
15722 		classList
15723 		dataset
15724 		attributes
15725 		computedStyles
15726 		state (persistent)
15727 		dynamic state (focused, hover, etc)
15728 +/
15729 
15730 // visualTheme.computedStyle(this).paddingLeft
15731 
15732 
15733 /++
15734 	This is your entry point to create your own visual theme for custom widgets.
15735 
15736 	You will want to inherit from this with a `final` class, passing your own class as the `CRTP` argument, then define the necessary methods.
15737 
15738 	Compatibility note: future versions of minigui may add new methods here. You will likely need to implement them when updating.
15739 +/
15740 abstract class VisualTheme(CRTP) : BaseVisualTheme {
15741 	override string getPropertyString(Widget widget, string propertyName) {
15742 		return null;
15743 	}
15744 
15745 	/+
15746 		mixin StyleOverride!Widget
15747 	final override void useStyleProperties(Widget w, scope void delegate(scope Widget.Style props) dg) {
15748 		w.useStyleProperties(dg);
15749 	}
15750 	+/
15751 
15752 	final override void doPaint(Widget widget, WidgetPainter painter) {
15753 		auto derived = cast(CRTP) cast(void*) this;
15754 
15755 		scope void delegate(Widget, WidgetPainter) bestMatch;
15756 		int bestMatchScore;
15757 
15758 		static if(__traits(hasMember, CRTP, "paint"))
15759 		foreach(overload; __traits(getOverloads, CRTP, "paint")) {
15760 			static if(is(typeof(overload) Params == __parameters)) {
15761 				static assert(Params.length == 2);
15762 				static assert(is(Params[0] : Widget));
15763 				static assert(is(Params[1] == WidgetPainter));
15764 				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);
15765 
15766 				alias type = Params[0];
15767 				if(cast(type) widget) {
15768 					auto score = baseClassCount!type;
15769 
15770 					if(score > bestMatchScore) {
15771 						bestMatch = cast(typeof(bestMatch)) &__traits(child, derived, overload);
15772 						bestMatchScore = score;
15773 					}
15774 				}
15775 			} else static assert(0, "paint should be a method.");
15776 		}
15777 
15778 		if(bestMatch)
15779 			bestMatch(widget, painter);
15780 		else
15781 			widget.paint(painter);
15782 	}
15783 
15784 	deprecated("Add an `int dpi` argument to your override now.") OperatingSystemFont defaultFont() { return null; }
15785 
15786 	// 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
15787 	// mixin Beautiful95Theme;
15788 	mixin DefaultLightTheme;
15789 
15790 	private static struct Cached {
15791 		// i prolly want to do this
15792 	}
15793 }
15794 
15795 /// ditto
15796 mixin template Beautiful95Theme() {
15797 	override Color windowBackgroundColor() { return Color(212, 212, 212); }
15798 	override Color widgetBackgroundColor() { return Color.white; }
15799 	override Color foregroundColor() { return Color.black; }
15800 	override Color darkAccentColor() { return Color(172, 172, 172); }
15801 	override Color lightAccentColor() { return Color(223, 223, 223); }
15802 	override Color selectionForegroundColor() { return Color.white; }
15803 	override Color selectionBackgroundColor() { return Color(0, 0, 128); }
15804 	override OperatingSystemFont defaultFont(int dpi) { return null; } // will just use the default out of simpledisplay's xfontstr
15805 }
15806 
15807 /// ditto
15808 mixin template DefaultLightTheme() {
15809 	override Color windowBackgroundColor() { return Color(232, 232, 232); }
15810 	override Color widgetBackgroundColor() { return Color.white; }
15811 	override Color foregroundColor() { return Color.black; }
15812 	override Color darkAccentColor() { return Color(172, 172, 172); }
15813 	override Color lightAccentColor() { return Color(223, 223, 223); }
15814 	override Color selectionForegroundColor() { return Color.white; }
15815 	override Color selectionBackgroundColor() { return Color(0, 0, 128); }
15816 	override OperatingSystemFont defaultFont(int dpi) {
15817 		version(Windows)
15818 			return new OperatingSystemFont("Segoe UI");
15819 		else {
15820 			// FIXME: undo xft's scaling so we don't end up double scaled
15821 			return new OperatingSystemFont("DejaVu Sans", 9 * dpi / 96);
15822 		}
15823 	}
15824 }
15825 
15826 /// ditto
15827 mixin template DefaultDarkTheme() {
15828 	override Color windowBackgroundColor() { return Color(64, 64, 64); }
15829 	override Color widgetBackgroundColor() { return Color.black; }
15830 	override Color foregroundColor() { return Color.white; }
15831 	override Color darkAccentColor() { return Color(20, 20, 20); }
15832 	override Color lightAccentColor() { return Color(80, 80, 80); }
15833 	override Color selectionForegroundColor() { return Color.white; }
15834 	override Color selectionBackgroundColor() { return Color(128, 0, 128); }
15835 	override OperatingSystemFont defaultFont(int dpi) {
15836 		version(Windows)
15837 			return new OperatingSystemFont("Segoe UI", 12);
15838 		else
15839 			return new OperatingSystemFont("DejaVu Sans", 9 * dpi / 96);
15840 	}
15841 }
15842 
15843 /// ditto
15844 alias DefaultTheme = DefaultLightTheme;
15845 
15846 final class DefaultVisualTheme : VisualTheme!DefaultVisualTheme {
15847 	/+
15848 	OperatingSystemFont defaultFont() { return new OperatingSystemFont("Times New Roman", 8, FontWeight.medium); }
15849 	Color windowBackgroundColor() { return Color(242, 242, 242); }
15850 	Color darkAccentColor() { return windowBackgroundColor; }
15851 	Color lightAccentColor() { return windowBackgroundColor; }
15852 	+/
15853 }
15854 
15855 /++
15856 	Event fired when an [Observeable] variable changes. You will want to add an event listener referencing
15857 	the field like `widget.addEventListener((scope StateChanged!(Whatever.field) ev) { });`
15858 
15859 	History:
15860 		Moved from minigui_addons.webview to main minigui on November 27, 2021 (dub v10.4)
15861 +/
15862 class StateChanged(alias field) : Event {
15863 	enum EventString = __traits(identifier, __traits(parent, field)) ~ "." ~ __traits(identifier, field) ~ ":change";
15864 	override bool cancelable() const { return false; }
15865 	this(Widget target, typeof(field) newValue) {
15866 		this.newValue = newValue;
15867 		super(EventString, target);
15868 	}
15869 
15870 	typeof(field) newValue;
15871 }
15872 
15873 /++
15874 	Convenience function to add a `triggered` event listener.
15875 
15876 	Its implementation is simply `w.addEventListener("triggered", dg);`
15877 
15878 	History:
15879 		Added November 27, 2021 (dub v10.4)
15880 +/
15881 void addWhenTriggered(Widget w, void delegate() dg) {
15882 	w.addEventListener("triggered", dg);
15883 }
15884 
15885 /++
15886 	Observable varables can be added to widgets and when they are changed, it fires
15887 	off a [StateChanged] event so you can react to it.
15888 
15889 	It is implemented as a getter and setter property, along with another helper you
15890 	can use to subscribe whith is `name_changed`. You can also subscribe to the [StateChanged]
15891 	event through the usual means. Just give the name of the variable. See [StateChanged] for an
15892 	example.
15893 
15894 	History:
15895 		Moved from minigui_addons.webview to main minigui on November 27, 2021 (dub v10.4)
15896 +/
15897 mixin template Observable(T, string name) {
15898 	private T backing;
15899 
15900 	mixin(q{
15901 		void } ~ name ~ q{_changed (void delegate(T) dg) {
15902 			this.addEventListener((StateChanged!this_thing ev) {
15903 				dg(ev.newValue);
15904 			});
15905 		}
15906 
15907 		@property T } ~ name ~ q{ () {
15908 			return backing;
15909 		}
15910 
15911 		@property void } ~ name ~ q{ (T t) {
15912 			backing = t;
15913 			auto event = new StateChanged!this_thing(this, t);
15914 			event.dispatch();
15915 		}
15916 	});
15917 
15918 	mixin("private alias this_thing = " ~ name ~ ";");
15919 }
15920 
15921 
15922 private bool startsWith(string test, string thing) {
15923 	if(test.length < thing.length)
15924 		return false;
15925 	return test[0 .. thing.length] == thing;
15926 }
15927 
15928 private bool endsWith(string test, string thing) {
15929 	if(test.length < thing.length)
15930 		return false;
15931 	return test[$ - thing.length .. $] == thing;
15932 }
15933 
15934 // still do layout delegation
15935 // and... split off Window from Widget.
15936 
15937 version(minigui_screenshots)
15938 struct Screenshot {
15939 	string name;
15940 }
15941 
15942 version(minigui_screenshots)
15943 static if(__VERSION__ > 2092)
15944 mixin(q{
15945 shared static this() {
15946 	import core.runtime;
15947 
15948 	static UnitTestResult screenshotMagic() {
15949 		string name;
15950 
15951 		import arsd.png;
15952 
15953 		auto results = new Window();
15954 		auto button = new Button("do it", results);
15955 
15956 		Window.newWindowCreated = delegate(Window w) {
15957 			Timer timer;
15958 			timer = new Timer(250, {
15959 				auto img = w.win.takeScreenshot();
15960 				timer.destroy();
15961 
15962 				version(Windows)
15963 					writePng("/var/www/htdocs/minigui-screenshots/windows/" ~ name ~ ".png", img);
15964 				else
15965 					writePng("/var/www/htdocs/minigui-screenshots/linux/" ~ name ~ ".png", img);
15966 
15967 				w.close();
15968 			});
15969 		};
15970 
15971 		button.addWhenTriggered( {
15972 
15973 		foreach(test; __traits(getUnitTests, mixin(__MODULE__))) {
15974 			name = null;
15975 			static foreach(attr; __traits(getAttributes, test)) {
15976 				static if(is(typeof(attr) == Screenshot))
15977 					name = attr.name;
15978 			}
15979 			if(name.length) {
15980 				test();
15981 			}
15982 		}
15983 
15984 		});
15985 
15986 		results.loop();
15987 
15988 		return UnitTestResult(0, 0, false, false);
15989 	}
15990 
15991 
15992 	Runtime.extendedModuleUnitTester = &screenshotMagic;
15993 }
15994 });
15995 version(minigui_screenshots) {
15996 	version(unittest)
15997 		void main() {}
15998 	else static assert(0, "dont forget the -unittest flag to dmd");
15999 }
16000 
16001 // FIXME: i called hotkey accelerator in some places. hotkey = key when menu is active like E&xit. accelerator = global shortcut.
16002 // FIXME: make multiple accelerators disambiguate based ona rgs
16003 // FIXME: MainWindow ctor should have same arg order as Window
16004 // FIXME: mainwindow ctor w/ client area size instead of total size.
16005 // 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.
16006 // FIXME: tri-state checkbox
16007 // FIXME: subordinate controls grouping...