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 /++
225 	This hello world sample will have an oversized button, but that's ok, you see your first window!
226 +/
227 version(Demo)
228 unittest {
229 	import arsd.minigui;
230 
231 	void main() {
232 		auto window = new MainWindow();
233 
234 		// note the parent widget is almost always passed as the last argument to a constructor
235 		auto hello = new TextLabel("Hello, world!", TextAlignment.Center, window);
236 		auto button = new Button("Close", window);
237 		button.addWhenTriggered({
238 			window.close();
239 		});
240 
241 		window.loop();
242 	}
243 
244 	main(); // exclude from docs
245 }
246 
247 /++
248 	This example shows one way you can partition your window into a header
249 	and sidebar. Here, the header and sidebar have a fixed width, while the
250 	rest of the content sizes with the window.
251 
252 	It might be a new way of thinking about window layout to do things this
253 	way - perhaps [GridLayout] more matches your style of thought - but the
254 	concept here is to partition the window into sub-boxes with a particular
255 	size, then partition those boxes into further boxes.
256 
257 	$(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.)
258 
259 	So to make the header, start with a child layout that has a max height.
260 	It will use that space from the top, then the remaining children will
261 	split the remaining area, meaning you can think of is as just being another
262 	box you can split again. Keep splitting until you have the look you desire.
263 +/
264 // https://github.com/adamdruppe/arsd/issues/310
265 version(minigui_screenshots)
266 @Screenshot("layout")
267 unittest {
268 	import arsd.minigui;
269 
270 	// This helper class is just to help make the layout boxes visible.
271 	// think of it like a <div style="background-color: whatever;"></div> in HTML.
272 	class ColorWidget : Widget {
273 		this(Color color, Widget parent) {
274 			this.color = color;
275 			super(parent);
276 		}
277 		Color color;
278 		class Style : Widget.Style {
279 			override WidgetBackground background() { return WidgetBackground(color); }
280 		}
281 		mixin OverrideStyle!Style;
282 	}
283 
284 	void main() {
285 		auto window = new Window;
286 
287 		// the key is to give it a max height. This is one way to do it:
288 		auto header = new class HorizontalLayout {
289 			this() { super(window); }
290 			override int maxHeight() { return 50; }
291 		};
292 		// this next line is a shortcut way of doing it too, but it only works
293 		// for HorizontalLayout and VerticalLayout, and is less explicit, so it
294 		// is good to know how to make a new class like above anyway.
295 		// auto header = new HorizontalLayout(50, window);
296 
297 		auto bar = new HorizontalLayout(window);
298 
299 		// or since this is so common, VerticalLayout and HorizontalLayout both
300 		// can just take an argument in their constructor for max width/height respectively
301 
302 		// (could have tone this above too, but I wanted to demo both techniques)
303 		auto left = new VerticalLayout(100, bar);
304 
305 		// and this is the main section's container. A plain Widget instance is good enough here.
306 		auto container = new Widget(bar);
307 
308 		// and these just add color to the containers we made above for the screenshot.
309 		// in a real application, you can just add your actual controls instead of these.
310 		auto headerColorBox = new ColorWidget(Color.teal, header);
311 		auto leftColorBox = new ColorWidget(Color.green, left);
312 		auto rightColorBox = new ColorWidget(Color.purple, container);
313 
314 		window.loop();
315 	}
316 
317 	main(); // exclude from docs
318 }
319 
320 
321 import arsd.core;
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 version(Windows) {
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 		} else {
1477 			featureNotImplemented();
1478 		}
1479 
1480 		return Point(x, y);
1481 	}
1482 
1483 	version(win32_widgets)
1484 	int handleWmDrawItem(DRAWITEMSTRUCT* dis) { return 0; }
1485 
1486 	version(win32_widgets)
1487 	/// Called when a WM_COMMAND is sent to the associated hwnd.
1488 	void handleWmCommand(ushort cmd, ushort id) {}
1489 
1490 	version(win32_widgets)
1491 	/++
1492 		Called when a WM_NOTIFY is sent to the associated hwnd.
1493 
1494 		History:
1495 	+/
1496 	int handleWmNotify(NMHDR* hdr, int code, out int mustReturn) { return 0; }
1497 
1498 	version(win32_widgets)
1499 	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); }
1500 
1501 	/++
1502 		This tip is displayed in the status bar (if there is one in the containing window) when the mouse moves over this widget.
1503 
1504 		Updates to this variable will only be made visible on the next mouse enter event.
1505 	+/
1506 	@scriptable string statusTip;
1507 	// string toolTip;
1508 	// string helpText;
1509 
1510 	/++
1511 		If true, this widget can be focused via keyboard control with the tab key.
1512 
1513 		If false, it is assumed the widget itself does will never receive the keyboard focus (though its childen are free to).
1514 	+/
1515 	bool tabStop = true;
1516 	/++
1517 		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.)
1518 	+/
1519 	int tabOrder;
1520 
1521 	version(win32_widgets) {
1522 		static Widget[HWND] nativeMapping;
1523 		/// The native handle, if there is one.
1524 		HWND hwnd;
1525 		WNDPROC originalWindowProcedure;
1526 
1527 		SimpleWindow simpleWindowWrappingHwnd;
1528 
1529 		// please note it IGNORES your return value and does NOT forward it to Windows!
1530 		int hookedWndProc(UINT iMessage, WPARAM wParam, LPARAM lParam) {
1531 			return 0;
1532 		}
1533 	}
1534 	private bool implicitlyCreated;
1535 
1536 	/// 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.
1537 	int x;
1538 	/// ditto
1539 	int y;
1540 	private int _width;
1541 	private int _height;
1542 	private Widget[] _children;
1543 	private Widget _parent;
1544 	private Window _parentWindow;
1545 
1546 	/++
1547 		Returns the window to which this widget is attached.
1548 
1549 		History:
1550 			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.
1551 	+/
1552 	final @property inout(Window) parentWindow() inout @nogc nothrow pure { return _parentWindow; }
1553 	private @property void parentWindow(Window parent) {
1554 		_parentWindow = parent;
1555 		foreach(child; children)
1556 			child.parentWindow = parent; // please note that this is recursive
1557 	}
1558 
1559 	/++
1560 		Returns the list of the widget's children.
1561 
1562 		History:
1563 			Prior to May 11, 2021, the `Widget[] children` was directly available. Now, only this property getter is available and the actual store is private.
1564 
1565 			Children should be added by the constructor most the time, but if that's impossible, use [addChild] and [removeWidget] to manage the list.
1566 	+/
1567 	final @property inout(Widget)[] children() inout @nogc nothrow pure { return _children; }
1568 
1569 	/++
1570 		Returns the widget's parent.
1571 
1572 		History:
1573 			Prior to May 11, 2021, the `Widget parent` variable was directly available. Now, only this property getter is permitted.
1574 
1575 			The parent should only be managed by the [addChild] and [removeWidget] method.
1576 	+/
1577 	final @property inout(Widget) parent() inout nothrow @nogc pure @safe return { return _parent; }
1578 
1579 	/// The widget's current size.
1580 	final @scriptable public @property int width() const nothrow @nogc pure @safe { return _width; }
1581 	/// ditto
1582 	final @scriptable public @property int height() const nothrow @nogc pure @safe { return _height; }
1583 
1584 	/// Only the layout manager should be calling these.
1585 	final protected @property int width(int a) @safe { return _width = a; }
1586 	/// ditto
1587 	final protected @property int height(int a) @safe { return _height = a; }
1588 
1589 	/++
1590 		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.
1591 
1592 		It is also responsible for calling [sendResizeEvent] to notify other listeners that the widget has changed size.
1593 	+/
1594 	protected void registerMovement() {
1595 		version(win32_widgets) {
1596 			if(hwnd) {
1597 				auto pos = getChildPositionRelativeToParentHwnd(this);
1598 				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
1599 			}
1600 		}
1601 		sendResizeEvent();
1602 	}
1603 
1604 	/// Creates the widget and adds it to the parent.
1605 	this(Widget parent) {
1606 		if(parent !is null)
1607 			parent.addChild(this);
1608 		setupDefaultEventHandlers();
1609 	}
1610 
1611 	/// 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.
1612 	@scriptable
1613 	bool isFocused() {
1614 		return parentWindow && parentWindow.focusedWidget is this;
1615 	}
1616 
1617 	private bool showing_ = true;
1618 	///
1619 	bool showing() { return showing_; }
1620 	///
1621 	bool hidden() { return !showing_; }
1622 	/++
1623 		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.
1624 	+/
1625 	void showing(bool s, bool recalculate = true) {
1626 		auto so = showing_;
1627 		showing_ = s;
1628 		if(s != so) {
1629 			version(win32_widgets)
1630 			if(hwnd)
1631 				ShowWindow(hwnd, s ? SW_SHOW : SW_HIDE);
1632 
1633 			if(parent && recalculate) {
1634 				parent.queueRecomputeChildLayout();
1635 				parent.redraw();
1636 			}
1637 
1638 			foreach(child; children)
1639 				child.showing(s, false);
1640 
1641 		}
1642 		queueRecomputeChildLayout();
1643 		redraw();
1644 	}
1645 	/// Convenience method for `showing = true`
1646 	@scriptable
1647 	void show() {
1648 		showing = true;
1649 	}
1650 	/// Convenience method for `showing = false`
1651 	@scriptable
1652 	void hide() {
1653 		showing = false;
1654 	}
1655 
1656 	///
1657 	@scriptable
1658 	void focus() {
1659 		assert(parentWindow !is null);
1660 		if(isFocused())
1661 			return;
1662 
1663 		if(parentWindow.focusedWidget) {
1664 			// FIXME: more details here? like from and to
1665 			auto from = parentWindow.focusedWidget;
1666 			parentWindow.focusedWidget.setDynamicState(DynamicState.focus, false);
1667 			parentWindow.focusedWidget = null;
1668 			from.emit!BlurEvent();
1669 			this.emit!FocusOutEvent();
1670 		}
1671 
1672 
1673 		version(win32_widgets) {
1674 			if(this.hwnd !is null)
1675 				SetFocus(this.hwnd);
1676 		}
1677 		//else static if(UsingSimpledisplayX11)
1678 			//this.parentWindow.win.focus();
1679 
1680 		parentWindow.focusedWidget = this;
1681 		parentWindow.focusedWidget.setDynamicState(DynamicState.focus, true);
1682 		this.emit!FocusEvent();
1683 		this.emit!FocusInEvent();
1684 	}
1685 
1686 	/+
1687 	/++
1688 		Unfocuses the widget. This may reset
1689 	+/
1690 	@scriptable
1691 	void blur() {
1692 
1693 	}
1694 	+/
1695 
1696 
1697 	/++
1698 		This is called when the widget is added to a window. It gives you a chance to set up event hooks.
1699 
1700 		Update on May 11, 2021: I'm considering removing this method. You can usually achieve these things through looser-coupled methods.
1701 	+/
1702 	void attachedToWindow(Window w) {}
1703 	/++
1704 		Callback when the widget is added to another widget.
1705 
1706 		Update on May 11, 2021: I'm considering removing this method since I've never actually found it useful.
1707 	+/
1708 	void addedTo(Widget w) {}
1709 
1710 	/++
1711 		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.
1712 
1713 		This is available primarily to be overridden. For example, [MainWindow] overrides it to redirect its children into a central widget.
1714 	+/
1715 	protected void addChild(Widget w, int position = int.max) {
1716 		assert(w._parent !is this, "Child cannot be added twice to the same parent");
1717 		assert(w !is this, "Child cannot be its own parent!");
1718 		w._parent = this;
1719 		if(position == int.max || position == children.length) {
1720 			_children ~= w;
1721 		} else {
1722 			assert(position < _children.length);
1723 			_children.length = _children.length + 1;
1724 			for(int i = cast(int) _children.length - 1; i > position; i--)
1725 				_children[i] = _children[i - 1];
1726 			_children[position] = w;
1727 		}
1728 
1729 		this.parentWindow = this._parentWindow;
1730 
1731 		w.addedTo(this);
1732 
1733 		if(this.hidden)
1734 			w.showing = false;
1735 
1736 		if(parentWindow !is null) {
1737 			w.attachedToWindow(parentWindow);
1738 			parentWindow.queueRecomputeChildLayout();
1739 			parentWindow.redraw();
1740 		}
1741 	}
1742 
1743 	/++
1744 		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.
1745 	+/
1746 	Widget getChildAtPosition(int x, int y) {
1747 		// it goes backward so the last one to show gets picked first
1748 		// might use z-index later
1749 		foreach_reverse(child; children) {
1750 			if(child.hidden)
1751 				continue;
1752 			if(child.x <= x && child.y <= y
1753 				&& ((x - child.x) < child.width)
1754 				&& ((y - child.y) < child.height))
1755 			{
1756 				return child;
1757 			}
1758 		}
1759 
1760 		return null;
1761 	}
1762 
1763 	/++
1764 		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.
1765 
1766 		History:
1767 			Added July 2, 2021 (v10.2)
1768 	+/
1769 	protected void addScrollPosition(ref int x, ref int y) {};
1770 
1771 	/++
1772 		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.
1773 
1774 		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.
1775 
1776 		[paint] is not called for system widgets as the OS library draws them instead.
1777 
1778 
1779 		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.
1780 
1781 		You should also look at [WidgetPainter.visualTheme] to be theme aware.
1782 
1783 		History:
1784 			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.
1785 	+/
1786 	void paint(WidgetPainter painter) {
1787 		version(win32_widgets)
1788 			if(hwnd) {
1789 				return;
1790 			}
1791 		painter.drawThemed(&paintContent); // note this refers to the following overload
1792 	}
1793 
1794 	/++
1795 		Responsible for drawing the content as the theme engine is responsible for other elements.
1796 
1797 		$(WARNING If you override [paint], this method may never be used as it is only called from inside the default implementation of `paint`.)
1798 
1799 		Params:
1800 			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.
1801 
1802 			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.
1803 
1804 			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.
1805 
1806 		Returns:
1807 			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.
1808 
1809 		History:
1810 			Added May 15, 2021
1811 	+/
1812 	Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
1813 		return bounds;
1814 	}
1815 
1816 	deprecated("Change ScreenPainter to WidgetPainter")
1817 	final void paint(ScreenPainter) { assert(0, "Change ScreenPainter to WidgetPainter and recompile your code"); }
1818 
1819 	/// I don't actually like the name of this
1820 	/// this draws a background on it
1821 	void erase(WidgetPainter painter) {
1822 		version(win32_widgets)
1823 			if(hwnd) return; // Windows will do it. I think.
1824 
1825 		auto c = getComputedStyle().background.color;
1826 		painter.fillColor = c;
1827 		painter.outlineColor = c;
1828 
1829 		version(win32_widgets) {
1830 			HANDLE b, p;
1831 			if(c.a == 0 && parent is parentWindow) {
1832 				// I don't remember why I had this really...
1833 				b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
1834 				p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
1835 			}
1836 		}
1837 		painter.drawRectangle(Point(0, 0), width, height);
1838 		version(win32_widgets) {
1839 			if(c.a == 0 && parent is parentWindow) {
1840 				SelectObject(painter.impl.hdc, p);
1841 				SelectObject(painter.impl.hdc, b);
1842 			}
1843 		}
1844 	}
1845 
1846 	///
1847 	WidgetPainter draw() {
1848 		int x = this.x, y = this.y;
1849 		auto parent = this.parent;
1850 		while(parent) {
1851 			x += parent.x;
1852 			y += parent.y;
1853 			parent = parent.parent;
1854 		}
1855 
1856 		auto painter = parentWindow.win.draw(true);
1857 		painter.originX = x;
1858 		painter.originY = y;
1859 		painter.setClipRectangle(Point(0, 0), width, height);
1860 		return WidgetPainter(painter, this);
1861 	}
1862 
1863 	/// 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.
1864 	protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force, bool invalidate) {
1865 		if(hidden)
1866 			return;
1867 
1868 		int paintX = x;
1869 		int paintY = y;
1870 		if(this.useNativeDrawing()) {
1871 			paintX = 0;
1872 			paintY = 0;
1873 			lox = 0;
1874 			loy = 0;
1875 			containment = Rectangle(0, 0, int.max, int.max);
1876 		}
1877 
1878 		painter.originX = lox + paintX;
1879 		painter.originY = loy + paintY;
1880 
1881 		bool actuallyPainted = false;
1882 
1883 		const clip = containment.intersectionOf(Rectangle(Point(lox + paintX, loy + paintY), Size(width, height)));
1884 		if(clip == Rectangle.init) {
1885 			// writeln(this, " clipped out");
1886 			return;
1887 		}
1888 
1889 		bool invalidateChildren = invalidate;
1890 
1891 		if(redrawRequested || force) {
1892 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
1893 
1894 			painter.drawingUpon = this;
1895 
1896 			erase(painter);
1897 			if(painter.visualTheme)
1898 				painter.visualTheme.doPaint(this, painter);
1899 			else
1900 				paint(painter);
1901 
1902 			if(invalidate) {
1903 				// sdpyPrintDebugString("invalidate " ~ typeid(this).name);
1904 				auto region = Rectangle(Point(clip.upperLeft.x - painter.originX, clip.upperRight.y - painter.originY), Size(clip.width, clip.height));
1905 				painter.invalidateRect(region);
1906 				// children are contained inside this, so no need to do extra work
1907 				invalidateChildren = false;
1908 			}
1909 
1910 			redrawRequested = false;
1911 			actuallyPainted = true;
1912 		}
1913 
1914 		foreach(child; children) {
1915 			version(win32_widgets)
1916 				if(child.useNativeDrawing()) continue;
1917 			child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted, invalidateChildren);
1918 		}
1919 
1920 		version(win32_widgets)
1921 		foreach(child; children) {
1922 			if(child.useNativeDrawing) {
1923 				painter = WidgetPainter(child.simpleWindowWrappingHwnd.draw(true), child);
1924 				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
1925 			}
1926 		}
1927 	}
1928 
1929 	protected bool useNativeDrawing() nothrow {
1930 		version(win32_widgets)
1931 			return hwnd !is null;
1932 		else
1933 			return false;
1934 	}
1935 
1936 	private static class RedrawEvent {}
1937 	private __gshared re = new RedrawEvent();
1938 
1939 	private bool redrawRequested;
1940 	///
1941 	final void redraw(string file = __FILE__, size_t line = __LINE__) {
1942 		redrawRequested = true;
1943 
1944 		if(this.parentWindow) {
1945 			auto sw = this.parentWindow.win;
1946 			assert(sw !is null);
1947 			if(!sw.eventQueued!RedrawEvent) {
1948 				sw.postEvent(re);
1949 				//  writeln("redraw requested from ", file,":",line," ", this.parentWindow.win.impl.window);
1950 			}
1951 		}
1952 	}
1953 
1954 	private SimpleWindow drawableWindow;
1955 
1956 	/++
1957 		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.
1958 
1959 		Returns:
1960 			`true` if you should do your default behavior.
1961 
1962 		History:
1963 			Added May 5, 2021
1964 
1965 		Bugs:
1966 			It does not do the static checks on gdc right now.
1967 	+/
1968 	final protected bool emit(EventType, this This, Args...)(Args args) {
1969 		version(GNU) {} else
1970 		static assert(classStaticallyEmits!(This, EventType), "The " ~ This.stringof ~ " class is not declared to emit " ~ EventType.stringof);
1971 		auto e = new EventType(this, args);
1972 		e.dispatch();
1973 		return !e.defaultPrevented;
1974 	}
1975 	/// ditto
1976 	final protected bool emit(string eventString, this This)() {
1977 		auto e = new Event(eventString, this);
1978 		e.dispatch();
1979 		return !e.defaultPrevented;
1980 	}
1981 
1982 	/++
1983 		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.
1984 
1985 		History:
1986 			Added May 5, 2021
1987 	+/
1988 	final public EventListener subscribe(EventType, this This)(void delegate(EventType) handler) {
1989 		static assert(classStaticallyEmits!(This, EventType), "The " ~ This.stringof ~ " class is not declared to emit " ~ EventType.stringof);
1990 		return addEventListener(handler);
1991 	}
1992 
1993 	/++
1994 		Gets the computed style properties from the visual theme.
1995 
1996 		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].)
1997 
1998 		History:
1999 			Added May 8, 2021
2000 	+/
2001 	final StyleInformation getComputedStyle() {
2002 		return StyleInformation(this);
2003 	}
2004 
2005 	int focusableWidgets(scope int delegate(Widget) dg) {
2006 		foreach(widget; WidgetStream(this)) {
2007 			if(widget.tabStop && !widget.hidden) {
2008 				int result = dg(widget);
2009 				if (result)
2010 					return result;
2011 			}
2012 		}
2013 		return 0;
2014 	}
2015 
2016 	/++
2017 		Calculates the border box (that is, the full width/height of the widget, from border edge to border edge)
2018 		for the given content box (the area between the padding)
2019 
2020 		History:
2021 			Added January 4, 2023 (dub v11.0)
2022 	+/
2023 	Rectangle borderBoxForContentBox(Rectangle contentBox) {
2024 		auto cs = getComputedStyle();
2025 
2026 		auto borderWidth = getBorderWidth(cs.borderStyle);
2027 
2028 		auto rect = contentBox;
2029 
2030 		rect.left -= borderWidth;
2031 		rect.right += borderWidth;
2032 		rect.top -= borderWidth;
2033 		rect.bottom += borderWidth;
2034 
2035 		auto insideBorderRect = rect;
2036 
2037 		rect.left -= cs.paddingLeft;
2038 		rect.right += cs.paddingRight;
2039 		rect.top -= cs.paddingTop;
2040 		rect.bottom += cs.paddingBottom;
2041 
2042 		return rect;
2043 	}
2044 
2045 
2046 	// FIXME: I kinda want to hide events from implementation widgets
2047 	// so it just catches them all and stops propagation...
2048 	// i guess i can do it with a event listener on star.
2049 
2050 	mixin Emits!KeyDownEvent; ///
2051 	mixin Emits!KeyUpEvent; ///
2052 	mixin Emits!CharEvent; ///
2053 
2054 	mixin Emits!MouseDownEvent; ///
2055 	mixin Emits!MouseUpEvent; ///
2056 	mixin Emits!ClickEvent; ///
2057 	mixin Emits!DoubleClickEvent; ///
2058 	mixin Emits!MouseMoveEvent; ///
2059 	mixin Emits!MouseOverEvent; ///
2060 	mixin Emits!MouseOutEvent; ///
2061 	mixin Emits!MouseEnterEvent; ///
2062 	mixin Emits!MouseLeaveEvent; ///
2063 
2064 	mixin Emits!ResizeEvent; ///
2065 
2066 	mixin Emits!BlurEvent; ///
2067 	mixin Emits!FocusEvent; ///
2068 
2069 	mixin Emits!FocusInEvent; ///
2070 	mixin Emits!FocusOutEvent; ///
2071 }
2072 
2073 /+
2074 /++
2075 	Interface to indicate that the widget has a simple value property.
2076 
2077 	History:
2078 		Added August 26, 2021
2079 +/
2080 interface HasValue!T {
2081 	/// Getter
2082 	@property T value();
2083 	/// Setter
2084 	@property void value(T);
2085 }
2086 
2087 /++
2088 	Interface to indicate that the widget has a range of possible values for its simple value property.
2089 	This would be present on something like a slider or possibly a number picker.
2090 
2091 	History:
2092 		Added September 11, 2021
2093 +/
2094 interface HasRangeOfValues!T : HasValue!T {
2095 	/// The minimum and maximum values in the range, inclusive.
2096 	@property T minValue();
2097 	@property void minValue(T); /// ditto
2098 	@property T maxValue(); /// ditto
2099 	@property void maxValue(T); /// ditto
2100 
2101 	/// The smallest step the user interface allows. User may still type in values without this limitation.
2102 	@property void step(T);
2103 	@property T step(); /// ditto
2104 }
2105 
2106 /++
2107 	Interface to indicate that the widget has a list of possible values the user can choose from.
2108 	This would be present on something like a drop-down selector.
2109 
2110 	The value is NOT necessarily one of the items on the list. Consider the case of a free-entry
2111 	combobox.
2112 
2113 	History:
2114 		Added September 11, 2021
2115 +/
2116 interface HasListOfValues!T : HasValue!T {
2117 	@property T[] values;
2118 	@property void values(T[]);
2119 
2120 	@property int selectedIndex(); // note it may return -1!
2121 	@property void selectedIndex(int);
2122 }
2123 +/
2124 
2125 /++
2126 	History:
2127 		Added September 2021 (dub v10.4)
2128 +/
2129 class GridLayout : Layout {
2130 
2131 	// 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.
2132 
2133 	/++
2134 		If a widget is too small to fill a grid cell, the graviy tells where it "sticks" to.
2135 	+/
2136 	enum Gravity {
2137 		Center    = 0,
2138 		NorthWest = North | West,
2139 		North     = 0b10_00,
2140 		NorthEast = North | East,
2141 		West      = 0b00_10,
2142 		East      = 0b00_01,
2143 		SouthWest = South | West,
2144 		South     = 0b01_00,
2145 		SouthEast = South | East,
2146 	}
2147 
2148 	/++
2149 		The width and height are in some proportional units and can often just be 12.
2150 	+/
2151 	this(int width, int height, Widget parent) {
2152 		this.gridWidth = width;
2153 		this.gridHeight = height;
2154 		super(parent);
2155 	}
2156 
2157 	/++
2158 		Sets the position of the given child.
2159 
2160 		The units of these arguments are in the proportional grid units you set in the constructor.
2161 	+/
2162 	Widget setChildPosition(return Widget child, int x, int y, int width, int height, Gravity gravity = Gravity.Center) {
2163 		// ensure it is in bounds
2164 		// then ensure no overlaps
2165 
2166 		ChildPosition p = ChildPosition(child, x, y, width, height, gravity);
2167 
2168 		foreach(ref position; positions) {
2169 			if(position.widget is child) {
2170 				position = p;
2171 				goto set;
2172 			}
2173 		}
2174 
2175 		positions ~= p;
2176 
2177 		set:
2178 
2179 		// FIXME: should this batch?
2180 		queueRecomputeChildLayout();
2181 
2182 		return child;
2183 	}
2184 
2185 	override void addChild(Widget w, int position = int.max) {
2186 		super.addChild(w, position);
2187 		//positions ~= ChildPosition(w);
2188 		if(position != int.max) {
2189 			// FIXME: align it so they actually match.
2190 		}
2191 	}
2192 
2193 	override void widgetRemoved(size_t idx, Widget w) {
2194 		// FIXME: keep the positions array aligned
2195 		// positions[idx].widget = null;
2196 	}
2197 
2198 	override void recomputeChildLayout() {
2199 		registerMovement();
2200 		int onGrid = cast(int) positions.length;
2201 		c: foreach(child; children) {
2202 			// just snap it to the grid
2203 			if(onGrid)
2204 			foreach(position; positions)
2205 				if(position.widget is child) {
2206 					child.x = this.width * position.x / this.gridWidth;
2207 					child.y = this.height * position.y / this.gridHeight;
2208 					child.width = this.width * position.width / this.gridWidth;
2209 					child.height = this.height * position.height / this.gridHeight;
2210 
2211 					auto diff = child.width - child.maxWidth();
2212 					// FIXME: gravity?
2213 					if(diff > 0) {
2214 						child.width = child.width - diff;
2215 
2216 						if(position.gravity & Gravity.West) {
2217 							// nothing needed, already aligned
2218 						} else if(position.gravity & Gravity.East) {
2219 							child.x += diff;
2220 						} else {
2221 							child.x += diff / 2;
2222 						}
2223 					}
2224 
2225 					diff = child.height - child.maxHeight();
2226 					// FIXME: gravity?
2227 					if(diff > 0) {
2228 						child.height = child.height - diff;
2229 
2230 						if(position.gravity & Gravity.North) {
2231 							// nothing needed, already aligned
2232 						} else if(position.gravity & Gravity.South) {
2233 							child.y += diff;
2234 						} else {
2235 							child.y += diff / 2;
2236 						}
2237 					}
2238 
2239 
2240 					child.recomputeChildLayout();
2241 					onGrid--;
2242 					continue c;
2243 				}
2244 			// the position isn't given on the grid array, we'll just fill in from where the explicit ones left off.
2245 		}
2246 	}
2247 
2248 	private struct ChildPosition {
2249 		Widget widget;
2250 		int x;
2251 		int y;
2252 		int width;
2253 		int height;
2254 		Gravity gravity;
2255 	}
2256 	private ChildPosition[] positions;
2257 
2258 	int gridWidth = 12;
2259 	int gridHeight = 12;
2260 }
2261 
2262 ///
2263 abstract class ComboboxBase : Widget {
2264 	// if the user can enter arbitrary data, we want to use  2 == CBS_DROPDOWN
2265 	// or to always show the list, we want CBS_SIMPLE == 1
2266 	version(win32_widgets)
2267 		this(uint style, Widget parent) {
2268 			super(parent);
2269 			createWin32Window(this, "ComboBox"w, null, style);
2270 		}
2271 	else version(custom_widgets)
2272 		this(Widget parent) {
2273 			super(parent);
2274 
2275 			addEventListener((KeyDownEvent event) {
2276 				if(event.key == Key.Up) {
2277 					if(selection_ > -1) { // -1 means select blank
2278 						selection_--;
2279 						fireChangeEvent();
2280 					}
2281 					event.preventDefault();
2282 				}
2283 				if(event.key == Key.Down) {
2284 					if(selection_ + 1 < options.length) {
2285 						selection_++;
2286 						fireChangeEvent();
2287 					}
2288 					event.preventDefault();
2289 				}
2290 
2291 			});
2292 
2293 		}
2294 	else static assert(false);
2295 
2296 	/++
2297 		Returns the current list of options in the selection.
2298 
2299 		History:
2300 			Property accessor added March 1, 2022 (dub v10.7). Prior to that, it was private.
2301 	+/
2302 	final @property string[] options() const {
2303 		return cast(string[]) options_;
2304 	}
2305 
2306 	private string[] options_;
2307 	private int selection_ = -1;
2308 
2309 	/++
2310 		Adds an option to the end of options array.
2311 	+/
2312 	void addOption(string s) {
2313 		options_ ~= s;
2314 		version(win32_widgets)
2315 		SendMessageW(hwnd, 323 /*CB_ADDSTRING*/, 0, cast(LPARAM) toWstringzInternal(s));
2316 	}
2317 
2318 	/++
2319 		Gets the current selection as an index into the [options] array. Returns -1 if nothing is selected.
2320 	+/
2321 	int getSelection() {
2322 		return selection_;
2323 	}
2324 
2325 	/++
2326 		Returns the current selection as a string.
2327 
2328 		History:
2329 			Added November 17, 2021
2330 	+/
2331 	string getSelectionString() {
2332 		return selection_ == -1 ? null : options[selection_];
2333 	}
2334 
2335 	/++
2336 		Sets the current selection to an index in the options array, or to the given option if present.
2337 		Please note that the string version may do a linear lookup.
2338 
2339 		Returns:
2340 			the index you passed in
2341 
2342 		History:
2343 			The `string` based overload was added on March 1, 2022 (dub v10.7).
2344 
2345 			The return value was `void` prior to March 1, 2022.
2346 	+/
2347 	int setSelection(int idx) {
2348 		selection_ = idx;
2349 		version(win32_widgets)
2350 		SendMessageW(hwnd, 334 /*CB_SETCURSEL*/, idx, 0);
2351 
2352 		auto t = new SelectionChangedEvent(this, selection_, selection_ == -1 ? null : options[selection_]);
2353 		t.dispatch();
2354 
2355 		return idx;
2356 	}
2357 
2358 	/// ditto
2359 	int setSelection(string s) {
2360 		if(s !is null)
2361 		foreach(idx, item; options)
2362 			if(item == s) {
2363 				return setSelection(cast(int) idx);
2364 			}
2365 		return setSelection(-1);
2366 	}
2367 
2368 	/++
2369 		This event is fired when the selection changes. Note it inherits
2370 		from ChangeEvent!string, meaning you can use that as well, and it also
2371 		fills in [Event.intValue].
2372 	+/
2373 	static class SelectionChangedEvent : ChangeEvent!string {
2374 		this(Widget target, int iv, string sv) {
2375 			super(target, &stringValue);
2376 			this.iv = iv;
2377 			this.sv = sv;
2378 		}
2379 		immutable int iv;
2380 		immutable string sv;
2381 
2382 		override @property string stringValue() { return sv; }
2383 		override @property int intValue() { return iv; }
2384 	}
2385 
2386 	version(win32_widgets)
2387 	override void handleWmCommand(ushort cmd, ushort id) {
2388 		if(cmd == CBN_SELCHANGE) {
2389 			selection_ = cast(int) SendMessageW(hwnd, 327 /* CB_GETCURSEL */, 0, 0);
2390 			fireChangeEvent();
2391 		}
2392 	}
2393 
2394 	private void fireChangeEvent() {
2395 		if(selection_ >= options.length)
2396 			selection_ = -1;
2397 
2398 		auto t = new SelectionChangedEvent(this, selection_, selection_ == -1 ? null : options[selection_]);
2399 		t.dispatch();
2400 	}
2401 
2402 	version(win32_widgets) {
2403 		override int minHeight() { return defaultLineHeight + 6; }
2404 		override int maxHeight() { return defaultLineHeight + 6; }
2405 	} else {
2406 		override int minHeight() { return defaultLineHeight + 4; }
2407 		override int maxHeight() { return defaultLineHeight + 4; }
2408 	}
2409 
2410 	version(custom_widgets) {
2411 
2412 		// FIXME: this should scroll if there's too many elements to reasonably fit on screen
2413 
2414 		SimpleWindow dropDown;
2415 		void popup() {
2416 			auto w = width;
2417 			// FIXME: suggestedDropdownHeight see below
2418 			auto h = cast(int) this.options.length * defaultLineHeight + 8;
2419 
2420 			auto coord = this.globalCoordinates();
2421 			auto dropDown = new SimpleWindow(
2422 				w, h,
2423 				null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.dropdownMenu, WindowFlags.dontAutoShow, parentWindow ? parentWindow.win : null);
2424 
2425 			dropDown.move(coord.x, coord.y + this.height);
2426 
2427 			{
2428 				auto cs = getComputedStyle();
2429 				auto painter = dropDown.draw();
2430 				draw3dFrame(0, 0, w, h, painter, FrameStyle.risen, getComputedStyle().background.color);
2431 				auto p = Point(4, 4);
2432 				painter.outlineColor = cs.foregroundColor;
2433 				foreach(option; options) {
2434 					painter.drawText(p, option);
2435 					p.y += defaultLineHeight;
2436 				}
2437 			}
2438 
2439 			dropDown.setEventHandlers(
2440 				(MouseEvent event) {
2441 					if(event.type == MouseEventType.buttonReleased) {
2442 						dropDown.close();
2443 						auto element = (event.y - 4) / defaultLineHeight;
2444 						if(element >= 0 && element <= options.length) {
2445 							selection_ = element;
2446 
2447 							fireChangeEvent();
2448 						}
2449 					}
2450 				}
2451 			);
2452 
2453 			dropDown.visibilityChanged = (bool visible) {
2454 				if(visible) {
2455 					this.redraw();
2456 					dropDown.grabInput();
2457 				} else {
2458 					dropDown.releaseInputGrab();
2459 				}
2460 			};
2461 
2462 			dropDown.show();
2463 		}
2464 
2465 	}
2466 }
2467 
2468 /++
2469 	A drop-down list where the user must select one of the
2470 	given options. Like `<select>` in HTML.
2471 +/
2472 class DropDownSelection : ComboboxBase {
2473 	this(Widget parent) {
2474 		version(win32_widgets)
2475 			super(3 /* CBS_DROPDOWNLIST */ | WS_VSCROLL, parent);
2476 		else version(custom_widgets) {
2477 			super(parent);
2478 
2479 			addEventListener("focus", () { this.redraw; });
2480 			addEventListener("blur", () { this.redraw; });
2481 			addEventListener(EventType.change, () { this.redraw; });
2482 			addEventListener("mousedown", () { this.focus(); this.popup(); });
2483 			addEventListener((KeyDownEvent event) {
2484 				if(event.key == Key.Space)
2485 					popup();
2486 			});
2487 		} else static assert(false);
2488 	}
2489 
2490 	mixin Padding!q{2};
2491 	static class Style : Widget.Style {
2492 		override FrameStyle borderStyle() { return FrameStyle.risen; }
2493 	}
2494 	mixin OverrideStyle!Style;
2495 
2496 	version(custom_widgets)
2497 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
2498 		auto cs = getComputedStyle();
2499 
2500 		painter.drawText(bounds.upperLeft, selection_ == -1 ? "" : options[selection_]);
2501 
2502 		painter.outlineColor = cs.foregroundColor;
2503 		painter.fillColor = cs.foregroundColor;
2504 
2505 		/+
2506 		Point[4] triangle;
2507 		enum padding = 6;
2508 		enum paddingV = 7;
2509 		enum triangleWidth = 10;
2510 		triangle[0] = Point(width - padding - triangleWidth, paddingV);
2511 		triangle[1] = Point(width - padding - triangleWidth / 2, height - paddingV);
2512 		triangle[2] = Point(width - padding - 0, paddingV);
2513 		triangle[3] = triangle[0];
2514 		painter.drawPolygon(triangle[]);
2515 		+/
2516 
2517 		auto offset = Point((this.width - scaleWithDpi(16)), (this.height - scaleWithDpi(16)) / 2);
2518 
2519 		painter.drawPolygon(
2520 			scaleWithDpi(Point(2, 6) + offset),
2521 			scaleWithDpi(Point(7, 11) + offset),
2522 			scaleWithDpi(Point(12, 6) + offset),
2523 			scaleWithDpi(Point(2, 6) + offset)
2524 		);
2525 
2526 
2527 		return bounds;
2528 	}
2529 
2530 	version(win32_widgets)
2531 	override void registerMovement() {
2532 		version(win32_widgets) {
2533 			if(hwnd) {
2534 				auto pos = getChildPositionRelativeToParentHwnd(this);
2535 				// the height given to this from Windows' perspective is supposed
2536 				// to include the drop down's height. so I add to it to give some
2537 				// room for that.
2538 				// FIXME: maybe make the subclass provide a suggestedDropdownHeight thing
2539 				MoveWindow(hwnd, pos[0], pos[1], width, height + 200, true);
2540 			}
2541 		}
2542 		sendResizeEvent();
2543 	}
2544 }
2545 
2546 /++
2547 	A text box with a drop down arrow listing selections.
2548 	The user can choose from the list, or type their own.
2549 +/
2550 class FreeEntrySelection : ComboboxBase {
2551 	this(Widget parent) {
2552 		version(win32_widgets)
2553 			super(2 /* CBS_DROPDOWN */, parent);
2554 		else version(custom_widgets) {
2555 			super(parent);
2556 			auto hl = new HorizontalLayout(this);
2557 			lineEdit = new LineEdit(hl);
2558 
2559 			tabStop = false;
2560 
2561 			lineEdit.addEventListener("focus", &lineEdit.selectAll);
2562 
2563 			auto btn = new class ArrowButton {
2564 				this() {
2565 					super(ArrowDirection.down, hl);
2566 				}
2567 				override int maxHeight() {
2568 					return lineEdit.maxHeight;
2569 				}
2570 			};
2571 			//btn.addDirectEventListener("focus", &lineEdit.focus);
2572 			btn.addEventListener("triggered", &this.popup);
2573 			addEventListener(EventType.change, (Event event) {
2574 				lineEdit.content = event.stringValue;
2575 				lineEdit.focus();
2576 				redraw();
2577 			});
2578 		}
2579 		else static assert(false);
2580 	}
2581 
2582 	version(custom_widgets) {
2583 		LineEdit lineEdit;
2584 	}
2585 }
2586 
2587 /++
2588 	A combination of free entry with a list below it.
2589 +/
2590 class ComboBox : ComboboxBase {
2591 	this(Widget parent) {
2592 		version(win32_widgets)
2593 			super(1 /* CBS_SIMPLE */ | CBS_NOINTEGRALHEIGHT, parent);
2594 		else version(custom_widgets) {
2595 			super(parent);
2596 			lineEdit = new LineEdit(this);
2597 			listWidget = new ListWidget(this);
2598 			listWidget.multiSelect = false;
2599 			listWidget.addEventListener(EventType.change, delegate(Widget, Event) {
2600 				string c = null;
2601 				foreach(option; listWidget.options)
2602 					if(option.selected) {
2603 						c = option.label;
2604 						break;
2605 					}
2606 				lineEdit.content = c;
2607 			});
2608 
2609 			listWidget.tabStop = false;
2610 			this.tabStop = false;
2611 			listWidget.addEventListener("focus", &lineEdit.focus);
2612 			this.addEventListener("focus", &lineEdit.focus);
2613 
2614 			addDirectEventListener(EventType.change, {
2615 				listWidget.setSelection(selection_);
2616 				if(selection_ != -1)
2617 					lineEdit.content = options[selection_];
2618 				lineEdit.focus();
2619 				redraw();
2620 			});
2621 
2622 			lineEdit.addEventListener("focus", &lineEdit.selectAll);
2623 
2624 			listWidget.addDirectEventListener(EventType.change, {
2625 				int set = -1;
2626 				foreach(idx, opt; listWidget.options)
2627 					if(opt.selected) {
2628 						set = cast(int) idx;
2629 						break;
2630 					}
2631 				if(set != selection_)
2632 					this.setSelection(set);
2633 			});
2634 		} else static assert(false);
2635 	}
2636 
2637 	override int minHeight() { return defaultLineHeight * 3; }
2638 	override int maxHeight() { return cast(int) options.length * defaultLineHeight + defaultLineHeight; }
2639 	override int heightStretchiness() { return 5; }
2640 
2641 	version(custom_widgets) {
2642 		LineEdit lineEdit;
2643 		ListWidget listWidget;
2644 
2645 		override void addOption(string s) {
2646 			listWidget.options ~= ListWidget.Option(s);
2647 			ComboboxBase.addOption(s);
2648 		}
2649 	}
2650 }
2651 
2652 /+
2653 class Spinner : Widget {
2654 	version(win32_widgets)
2655 	this(Widget parent) {
2656 		super(parent);
2657 		parentWindow = parent.parentWindow;
2658 		auto hlayout = new HorizontalLayout(this);
2659 		lineEdit = new LineEdit(hlayout);
2660 		upDownControl = new UpDownControl(hlayout);
2661 	}
2662 
2663 	LineEdit lineEdit;
2664 	UpDownControl upDownControl;
2665 }
2666 
2667 class UpDownControl : Widget {
2668 	version(win32_widgets)
2669 	this(Widget parent) {
2670 		super(parent);
2671 		parentWindow = parent.parentWindow;
2672 		createWin32Window(this, "msctls_updown32"w, null, 4/*UDS_ALIGNRIGHT*/| 2 /* UDS_SETBUDDYINT */ | 16 /* UDS_AUTOBUDDY */ | 32 /* UDS_ARROWKEYS */);
2673 	}
2674 
2675 	override int minHeight() { return defaultLineHeight; }
2676 	override int maxHeight() { return defaultLineHeight * 3/2; }
2677 
2678 	override int minWidth() { return defaultLineHeight * 3/2; }
2679 	override int maxWidth() { return defaultLineHeight * 3/2; }
2680 }
2681 +/
2682 
2683 /+
2684 class DataView : Widget {
2685 	// this is the omnibus data viewer
2686 	// the internal data layout is something like:
2687 	// string[string][] but also each node can have parents
2688 }
2689 +/
2690 
2691 
2692 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb775491(v=vs.85).aspx#PROGRESS_CLASS
2693 
2694 // http://svn.dsource.org/projects/bindings/trunk/win32/commctrl.d
2695 
2696 // FIXME: menus should prolly capture the mouse. ugh i kno.
2697 /*
2698 	TextEdit needs:
2699 
2700 	* caret manipulation
2701 	* selection control
2702 	* convenience functions for appendText, insertText, insertTextAtCaret, etc.
2703 
2704 	For example:
2705 
2706 	connect(paste, &textEdit.insertTextAtCaret);
2707 
2708 	would be nice.
2709 
2710 
2711 
2712 	I kinda want an omnibus dataview that combines list, tree,
2713 	and table - it can be switched dynamically between them.
2714 
2715 	Flattening policy: only show top level, show recursive, show grouped
2716 	List styles: plain list (e.g. <ul>), tiles (some details next to it), icons (like Windows explorer)
2717 
2718 	Single select, multi select, organization, drag+drop
2719 */
2720 
2721 //static if(UsingSimpledisplayX11)
2722 version(win32_widgets) {}
2723 else version(custom_widgets) {
2724 	enum scrollClickRepeatInterval = 50;
2725 
2726 deprecated("Get these properties off `Widget.getComputedStyle` instead. The defaults are now set in the `WidgetPainter.visualTheme`.") {
2727 	enum windowBackgroundColor = Color(212, 212, 212); // used to be 192
2728 	enum activeTabColor = lightAccentColor;
2729 	enum hoveringColor = Color(228, 228, 228);
2730 	enum buttonColor = windowBackgroundColor;
2731 	enum depressedButtonColor = darkAccentColor;
2732 	enum activeListXorColor = Color(255, 255, 127);
2733 	enum progressBarColor = Color(0, 0, 128);
2734 	enum activeMenuItemColor = Color(0, 0, 128);
2735 
2736 }}
2737 else static assert(false);
2738 deprecated("Get these properties off the `visualTheme` instead.") {
2739 	// these are used by horizontal rule so not just custom_widgets. for now at least.
2740 	enum darkAccentColor = Color(172, 172, 172);
2741 	enum lightAccentColor = Color(223, 223, 223); // used to be 223
2742 }
2743 
2744 private const(wchar)* toWstringzInternal(in char[] s) {
2745 	wchar[] str;
2746 	str.reserve(s.length + 1);
2747 	foreach(dchar ch; s)
2748 		str ~= ch;
2749 	str ~= '\0';
2750 	return str.ptr;
2751 }
2752 
2753 static if(SimpledisplayTimerAvailable)
2754 void setClickRepeat(Widget w, int interval, int delay = 250) {
2755 	Timer timer;
2756 	int delayRemaining = delay / interval;
2757 	if(delayRemaining <= 1)
2758 		delayRemaining = 2;
2759 
2760 	immutable originalDelayRemaining = delayRemaining;
2761 
2762 	w.addDirectEventListener((scope MouseDownEvent ev) {
2763 		if(ev.srcElement !is w)
2764 			return;
2765 		if(timer !is null) {
2766 			timer.destroy();
2767 			timer = null;
2768 		}
2769 		delayRemaining = originalDelayRemaining;
2770 		timer = new Timer(interval, () {
2771 			if(delayRemaining > 0)
2772 				delayRemaining--;
2773 			else {
2774 				auto ev = new Event("triggered", w);
2775 				ev.sendDirectly();
2776 			}
2777 		});
2778 	});
2779 
2780 	w.addDirectEventListener((scope MouseUpEvent ev) {
2781 		if(ev.srcElement !is w)
2782 			return;
2783 		if(timer !is null) {
2784 			timer.destroy();
2785 			timer = null;
2786 		}
2787 	});
2788 
2789 	w.addDirectEventListener((scope MouseLeaveEvent ev) {
2790 		if(ev.srcElement !is w)
2791 			return;
2792 		if(timer !is null) {
2793 			timer.destroy();
2794 			timer = null;
2795 		}
2796 	});
2797 
2798 }
2799 else
2800 void setClickRepeat(Widget w, int interval, int delay = 250) {}
2801 
2802 enum FrameStyle {
2803 	none, ///
2804 	risen, /// a 3d pop-out effect (think Windows 95 button)
2805 	sunk, /// a 3d sunken effect (think Windows 95 button as you click on it)
2806 	solid, ///
2807 	dotted, ///
2808 	fantasy, /// a style based on a popular fantasy video game
2809 }
2810 
2811 version(custom_widgets)
2812 deprecated
2813 void draw3dFrame(Widget widget, ScreenPainter painter, FrameStyle style) {
2814 	draw3dFrame(0, 0, widget.width, widget.height, painter, style, WidgetPainter.visualTheme.windowBackgroundColor);
2815 }
2816 
2817 version(custom_widgets)
2818 void draw3dFrame(Widget widget, ScreenPainter painter, FrameStyle style, Color background) {
2819 	draw3dFrame(0, 0, widget.width, widget.height, painter, style, background);
2820 }
2821 
2822 version(custom_widgets)
2823 deprecated
2824 void draw3dFrame(int x, int y, int width, int height, ScreenPainter painter, FrameStyle style) {
2825 	draw3dFrame(x, y, width, height, painter, style, WidgetPainter.visualTheme.windowBackgroundColor);
2826 }
2827 
2828 int getBorderWidth(FrameStyle style) {
2829 	final switch(style) {
2830 		case FrameStyle.sunk, FrameStyle.risen:
2831 			return 2;
2832 		case FrameStyle.none:
2833 			return 0;
2834 		case FrameStyle.solid:
2835 			return 1;
2836 		case FrameStyle.dotted:
2837 			return 1;
2838 		case FrameStyle.fantasy:
2839 			return 3;
2840 	}
2841 }
2842 
2843 int draw3dFrame(int x, int y, int width, int height, ScreenPainter painter, FrameStyle style, Color background, Color border = Color.transparent) {
2844 	int borderWidth = getBorderWidth(style);
2845 	final switch(style) {
2846 		case FrameStyle.sunk, FrameStyle.risen:
2847 			// outer layer
2848 			painter.outlineColor = style == FrameStyle.sunk ? Color.white : Color.black;
2849 		break;
2850 		case FrameStyle.none:
2851 			painter.outlineColor = background;
2852 		break;
2853 		case FrameStyle.solid:
2854 			painter.pen = Pen(border, 1);
2855 		break;
2856 		case FrameStyle.dotted:
2857 			painter.pen = Pen(border, 1, Pen.Style.Dotted);
2858 		break;
2859 		case FrameStyle.fantasy:
2860 			painter.pen = Pen(border, 3);
2861 		break;
2862 	}
2863 
2864 	painter.fillColor = background;
2865 	painter.drawRectangle(Point(x + 0, y + 0), width, height);
2866 
2867 
2868 	if(style == FrameStyle.sunk || style == FrameStyle.risen) {
2869 		// 3d effect
2870 		auto vt = WidgetPainter.visualTheme;
2871 
2872 		painter.outlineColor = (style == FrameStyle.sunk) ? vt.darkAccentColor : vt.lightAccentColor;
2873 		painter.drawLine(Point(x + 0, y + 0), Point(x + width, y + 0));
2874 		painter.drawLine(Point(x + 0, y + 0), Point(x + 0, y + height - 1));
2875 
2876 		// inner layer
2877 		//right, bottom
2878 		painter.outlineColor = (style == FrameStyle.sunk) ? vt.lightAccentColor : vt.darkAccentColor;
2879 		painter.drawLine(Point(x + width - 2, y + 2), Point(x + width - 2, y + height - 2));
2880 		painter.drawLine(Point(x + 2, y + height - 2), Point(x + width - 2, y + height - 2));
2881 		// left, top
2882 		painter.outlineColor = (style == FrameStyle.sunk) ? Color.black : Color.white;
2883 		painter.drawLine(Point(x + 1, y + 1), Point(x + width, y + 1));
2884 		painter.drawLine(Point(x + 1, y + 1), Point(x + 1, y + height - 2));
2885 	} else if(style == FrameStyle.fantasy) {
2886 		painter.pen = Pen(Color.white, 1, Pen.Style.Solid);
2887 		painter.fillColor = Color.transparent;
2888 		painter.drawRectangle(Point(x + 1, y + 1), Point(x + width - 1, y + height - 1));
2889 	}
2890 
2891 	return borderWidth;
2892 }
2893 
2894 /++
2895 	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.
2896 
2897 	See_Also:
2898 		[MenuItem]
2899 		[ToolButton]
2900 		[Menu.addItem]
2901 +/
2902 class Action {
2903 	version(win32_widgets) {
2904 		private int id;
2905 		private static int lastId = 9000;
2906 		private static Action[int] mapping;
2907 	}
2908 
2909 	KeyEvent accelerator;
2910 
2911 	// FIXME: disable message
2912 	// and toggle thing?
2913 	// ??? and trigger arguments too ???
2914 
2915 	/++
2916 		Params:
2917 			label = the textual label
2918 			icon = icon ID. See [GenericIcons]. There is currently no way to do custom icons.
2919 			triggered = initial handler, more can be added via the [triggered] member.
2920 	+/
2921 	this(string label, ushort icon = 0, void delegate() triggered = null) {
2922 		this.label = label;
2923 		this.iconId = icon;
2924 		if(triggered !is null)
2925 			this.triggered ~= triggered;
2926 		version(win32_widgets) {
2927 			id = ++lastId;
2928 			mapping[id] = this;
2929 		}
2930 	}
2931 
2932 	private string label;
2933 	private ushort iconId;
2934 	// icon
2935 
2936 	// when it is triggered, the triggered event is fired on the window
2937 	/// The list of handlers when it is triggered.
2938 	void delegate()[] triggered;
2939 }
2940 
2941 /*
2942 	plan:
2943 		keyboard accelerators
2944 
2945 		* menus (and popups and tooltips)
2946 		* status bar
2947 		* toolbars and buttons
2948 
2949 		sortable table view
2950 
2951 		maybe notification area icons
2952 		basic clipboard
2953 
2954 		* radio box
2955 		splitter
2956 		toggle buttons (optionally mutually exclusive, like in Paint)
2957 		label, rich text display, multi line plain text (selectable)
2958 		* fieldset
2959 		* nestable grid layout
2960 		single line text input
2961 		* multi line text input
2962 		slider
2963 		spinner
2964 		list box
2965 		drop down
2966 		combo box
2967 		auto complete box
2968 		* progress bar
2969 
2970 		terminal window/widget (on unix it might even be a pty but really idk)
2971 
2972 		ok button
2973 		cancel button
2974 
2975 		keyboard hotkeys
2976 
2977 		scroll widget
2978 
2979 		event redirections and network transparency
2980 		script integration
2981 */
2982 
2983 
2984 /*
2985 	MENUS
2986 
2987 	auto bar = new MenuBar(window);
2988 	window.menuBar = bar;
2989 
2990 	auto fileMenu = bar.addItem(new Menu("&File"));
2991 	fileMenu.addItem(new MenuItem("&Exit"));
2992 
2993 
2994 	EVENTS
2995 
2996 	For controls, you should usually use "triggered" rather than "click", etc., because
2997 	triggered handles both keyboard (focus and press as well as hotkeys) and mouse activation.
2998 	This is the case on menus and pushbuttons.
2999 
3000 	"click", on the other hand, currently only fires when it is literally clicked by the mouse.
3001 */
3002 
3003 
3004 /*
3005 enum LinePreference {
3006 	AlwaysOnOwnLine, // always on its own line
3007 	PreferOwnLine, // it will always start a new line, and if max width <= line width, it will expand all the way
3008 	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
3009 }
3010 */
3011 
3012 /++
3013 	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.
3014 
3015 	---
3016 	class MyWidget : Widget {
3017 		this(Widget parent) { super(parent); }
3018 
3019 		// set paddingLeft, paddingRight, paddingTop, and paddingBottom all to `return 4;` in one go:
3020 		mixin Padding!q{4};
3021 
3022 		// set marginLeft, marginRight, marginTop, and marginBottom all to `return 8;` in one go:
3023 		mixin Margin!q{8};
3024 
3025 		// but if I specify one outside, it overrides the override, so now marginLeft is 2,
3026 		// while Top/Bottom/Right remain 8 from the mixin above.
3027 		override int marginLeft() { return 2; }
3028 	}
3029 	---
3030 
3031 
3032 	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]).
3033 
3034 	Padding is the area inside a widget where its background is drawn, but the content avoids.
3035 
3036 	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!).
3037 
3038 	* 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.
3039 +/
3040 mixin template Padding(string code) {
3041 	override int paddingLeft() { return mixin(code);}
3042 	override int paddingRight() { return mixin(code);}
3043 	override int paddingTop() { return mixin(code);}
3044 	override int paddingBottom() { return mixin(code);}
3045 }
3046 
3047 /// ditto
3048 mixin template Margin(string code) {
3049 	override int marginLeft() { return mixin(code);}
3050 	override int marginRight() { return mixin(code);}
3051 	override int marginTop() { return mixin(code);}
3052 	override int marginBottom() { return mixin(code);}
3053 }
3054 
3055 private
3056 void recomputeChildLayout(string relevantMeasure)(Widget parent) {
3057 	enum calcingV = relevantMeasure == "height";
3058 
3059 	parent.registerMovement();
3060 
3061 	if(parent.children.length == 0)
3062 		return;
3063 
3064 	auto parentStyle = parent.getComputedStyle();
3065 
3066 	enum firstThingy = relevantMeasure == "height" ? "Top" : "Left";
3067 	enum secondThingy = relevantMeasure == "height" ? "Bottom" : "Right";
3068 
3069 	enum otherFirstThingy = relevantMeasure == "height" ? "Left" : "Top";
3070 	enum otherSecondThingy = relevantMeasure == "height" ? "Right" : "Bottom";
3071 
3072 	// my own width and height should already be set by the caller of this function...
3073 	int spaceRemaining = mixin("parent." ~ relevantMeasure) -
3074 		mixin("parentStyle.padding"~firstThingy~"()") -
3075 		mixin("parentStyle.padding"~secondThingy~"()");
3076 
3077 	int stretchinessSum;
3078 	int stretchyChildSum;
3079 	int lastMargin = 0;
3080 
3081 	int shrinkinessSum;
3082 	int shrinkyChildSum;
3083 
3084 	// set initial size
3085 	foreach(child; parent.children) {
3086 
3087 		auto childStyle = child.getComputedStyle();
3088 
3089 		if(cast(StaticPosition) child)
3090 			continue;
3091 		if(child.hidden)
3092 			continue;
3093 
3094 		const iw = child.flexBasisWidth();
3095 		const ih = child.flexBasisHeight();
3096 
3097 		static if(calcingV) {
3098 			child.width = parent.width -
3099 				mixin("childStyle.margin"~otherFirstThingy~"()") -
3100 				mixin("childStyle.margin"~otherSecondThingy~"()") -
3101 				mixin("parentStyle.padding"~otherFirstThingy~"()") -
3102 				mixin("parentStyle.padding"~otherSecondThingy~"()");
3103 
3104 			if(child.width < 0)
3105 				child.width = 0;
3106 			if(child.width > childStyle.maxWidth())
3107 				child.width = childStyle.maxWidth();
3108 
3109 			if(iw > 0) {
3110 				auto totalPossible = child.width;
3111 				if(child.width > iw && child.widthStretchiness() == 0)
3112 					child.width = iw;
3113 			}
3114 
3115 			child.height = mymax(childStyle.minHeight(), ih);
3116 		} else {
3117 			// set to take all the space
3118 			child.height = parent.height -
3119 				mixin("childStyle.margin"~firstThingy~"()") -
3120 				mixin("childStyle.margin"~secondThingy~"()") -
3121 				mixin("parentStyle.padding"~firstThingy~"()") -
3122 				mixin("parentStyle.padding"~secondThingy~"()");
3123 
3124 			// then clamp it
3125 			if(child.height < 0)
3126 				child.height = 0;
3127 			if(child.height > childStyle.maxHeight())
3128 				child.height = childStyle.maxHeight();
3129 
3130 			// and if possible, respect the ideal target
3131 			if(ih > 0) {
3132 				auto totalPossible = child.height;
3133 				if(child.height > ih && child.heightStretchiness() == 0)
3134 					child.height = ih;
3135 			}
3136 
3137 			// if we have an ideal, try to respect it, otehrwise, just use the minimum
3138 			child.width = mymax(childStyle.minWidth(), iw);
3139 		}
3140 
3141 		spaceRemaining -= mixin("child." ~ relevantMeasure);
3142 
3143 		int thisMargin = mymax(lastMargin, mixin("childStyle.margin"~firstThingy~"()"));
3144 		auto margin = mixin("childStyle.margin" ~ secondThingy ~ "()");
3145 		lastMargin = margin;
3146 		spaceRemaining -= thisMargin + margin;
3147 
3148 		auto s = mixin("child." ~ relevantMeasure ~ "Stretchiness()");
3149 		stretchinessSum += s;
3150 		if(s > 0)
3151 			stretchyChildSum++;
3152 
3153 		auto s2 = mixin("child." ~ relevantMeasure ~ "Shrinkiness()");
3154 		shrinkinessSum += s2;
3155 		if(s2 > 0)
3156 			shrinkyChildSum++;
3157 	}
3158 
3159 	if(spaceRemaining < 0 && shrinkyChildSum) {
3160 		// shrink to get into the space if it is possible
3161 		auto toRemove = -spaceRemaining;
3162 		auto removalPerItem  = toRemove * shrinkinessSum / shrinkyChildSum;
3163 		auto remainder = toRemove * shrinkinessSum % shrinkyChildSum;
3164 
3165 		// FIXME: wtf why am i shrinking things with no shrinkiness?
3166 
3167 		foreach(child; parent.children) {
3168 			auto childStyle = child.getComputedStyle();
3169 			if(cast(StaticPosition) child)
3170 				continue;
3171 			if(child.hidden)
3172 				continue;
3173 			static if(calcingV) {
3174 				auto maximum = childStyle.maxHeight();
3175 			} else {
3176 				auto maximum = childStyle.maxWidth();
3177 			}
3178 
3179 			if(mixin("child._" ~ relevantMeasure) >= maximum)
3180 				continue;
3181 
3182 			mixin("child._" ~ relevantMeasure) -= removalPerItem + remainder; // this is removing more than needed to trigger the next thing. ugh.
3183 
3184 			spaceRemaining += removalPerItem + remainder;
3185 		}
3186 	}
3187 
3188 	// stretch to fill space
3189 	while(spaceRemaining > 0 && stretchinessSum && stretchyChildSum) {
3190 		auto spacePerChild = spaceRemaining / stretchinessSum;
3191 		bool spreadEvenly;
3192 		bool giveToBiggest;
3193 		if(spacePerChild <= 0) {
3194 			spacePerChild = spaceRemaining / stretchyChildSum;
3195 			spreadEvenly = true;
3196 		}
3197 		if(spacePerChild <= 0) {
3198 			giveToBiggest = true;
3199 		}
3200 		int previousSpaceRemaining = spaceRemaining;
3201 		stretchinessSum = 0;
3202 		Widget mostStretchy;
3203 		int mostStretchyS;
3204 		foreach(child; parent.children) {
3205 			auto childStyle = child.getComputedStyle();
3206 			if(cast(StaticPosition) child)
3207 				continue;
3208 			if(child.hidden)
3209 				continue;
3210 			static if(calcingV) {
3211 				auto maximum = childStyle.maxHeight();
3212 			} else {
3213 				auto maximum = childStyle.maxWidth();
3214 			}
3215 
3216 			if(mixin("child." ~ relevantMeasure) >= maximum) {
3217 				auto adj = mixin("child." ~ relevantMeasure) - maximum;
3218 				mixin("child._" ~ relevantMeasure) -= adj;
3219 				spaceRemaining += adj;
3220 				continue;
3221 			}
3222 			auto s = mixin("child." ~ relevantMeasure ~ "Stretchiness()");
3223 			if(s <= 0)
3224 				continue;
3225 			auto spaceAdjustment = spacePerChild * (spreadEvenly ? 1 : s);
3226 			mixin("child._" ~ relevantMeasure) += spaceAdjustment;
3227 			spaceRemaining -= spaceAdjustment;
3228 			if(mixin("child." ~ relevantMeasure) > maximum) {
3229 				auto diff = mixin("child." ~ relevantMeasure) - maximum;
3230 				mixin("child._" ~ relevantMeasure) -= diff;
3231 				spaceRemaining += diff;
3232 			} else if(mixin("child._" ~ relevantMeasure) < maximum) {
3233 				stretchinessSum += mixin("child." ~ relevantMeasure ~ "Stretchiness()");
3234 				if(mostStretchy is null || s >= mostStretchyS) {
3235 					mostStretchy = child;
3236 					mostStretchyS = s;
3237 				}
3238 			}
3239 		}
3240 
3241 		if(giveToBiggest && mostStretchy !is null) {
3242 			auto child = mostStretchy;
3243 			auto childStyle = child.getComputedStyle();
3244 			int spaceAdjustment = spaceRemaining;
3245 
3246 			static if(calcingV)
3247 				auto maximum = childStyle.maxHeight();
3248 			else
3249 				auto maximum = childStyle.maxWidth();
3250 
3251 			mixin("child._" ~ relevantMeasure) += spaceAdjustment;
3252 			spaceRemaining -= spaceAdjustment;
3253 			if(mixin("child._" ~ relevantMeasure) > maximum) {
3254 				auto diff = mixin("child." ~ relevantMeasure) - maximum;
3255 				mixin("child._" ~ relevantMeasure) -= diff;
3256 				spaceRemaining += diff;
3257 			}
3258 		}
3259 
3260 		if(spaceRemaining == previousSpaceRemaining) {
3261 			if(mostStretchy !is null) {
3262 				static if(calcingV)
3263 					auto maximum = mostStretchy.maxHeight();
3264 				else
3265 					auto maximum = mostStretchy.maxWidth();
3266 
3267 				mixin("mostStretchy._" ~ relevantMeasure) += spaceRemaining;
3268 				if(mixin("mostStretchy._" ~ relevantMeasure) > maximum)
3269 					mixin("mostStretchy._" ~ relevantMeasure) = maximum;
3270 			}
3271 			break; // apparently nothing more we can do
3272 		}
3273 	}
3274 
3275 	foreach(child; parent.children) {
3276 		auto childStyle = child.getComputedStyle();
3277 		if(cast(StaticPosition) child)
3278 			continue;
3279 		if(child.hidden)
3280 			continue;
3281 
3282 		static if(calcingV)
3283 			auto maximum = childStyle.maxHeight();
3284 		else
3285 			auto maximum = childStyle.maxWidth();
3286 		if(mixin("child._" ~ relevantMeasure) > maximum)
3287 			mixin("child._" ~ relevantMeasure) = maximum;
3288 	}
3289 
3290 	// position
3291 	lastMargin = 0;
3292 	int currentPos = mixin("parent.padding"~firstThingy~"()");
3293 	foreach(child; parent.children) {
3294 		auto childStyle = child.getComputedStyle();
3295 		if(cast(StaticPosition) child) {
3296 			child.recomputeChildLayout();
3297 			continue;
3298 		}
3299 		if(child.hidden)
3300 			continue;
3301 		auto margin = mixin("childStyle.margin" ~ secondThingy ~ "()");
3302 		int thisMargin = mymax(lastMargin, mixin("childStyle.margin"~firstThingy~"()"));
3303 		currentPos += thisMargin;
3304 		static if(calcingV) {
3305 			child.x = parentStyle.paddingLeft() + childStyle.marginLeft();
3306 			child.y = currentPos;
3307 		} else {
3308 			child.x = currentPos;
3309 			child.y = parentStyle.paddingTop() + childStyle.marginTop();
3310 
3311 		}
3312 		currentPos += mixin("child." ~ relevantMeasure);
3313 		currentPos += margin;
3314 		lastMargin = margin;
3315 
3316 		child.recomputeChildLayout();
3317 	}
3318 }
3319 
3320 int mymax(int a, int b) { return a > b ? a : b; }
3321 int mymax(int a, int b, int c) {
3322 	auto d = mymax(a, b);
3323 	return c > d ? c : d;
3324 }
3325 
3326 // OK so we need to make getting at the native window stuff possible in simpledisplay.d
3327 // and here, it must be integrable with the layout, the event system, and not be painted over.
3328 version(win32_widgets) {
3329 
3330 	// this function just does stuff that a parent window needs for redirection
3331 	int WindowProcedureHelper(Widget this_, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
3332 		this_.hookedWndProc(msg, wParam, lParam);
3333 
3334 		switch(msg) {
3335 
3336 			case WM_VSCROLL, WM_HSCROLL:
3337 				auto pos = HIWORD(wParam);
3338 				auto m = LOWORD(wParam);
3339 
3340 				auto scrollbarHwnd = cast(HWND) lParam;
3341 
3342 				if(auto widgetp = scrollbarHwnd in Widget.nativeMapping) {
3343 
3344 					//auto smw = cast(ScrollMessageWidget) widgetp.parent;
3345 
3346 					switch(m) {
3347 						/+
3348 						// I don't think those messages are ever actually sent normally by the widget itself,
3349 						// they are more used for the keyboard interface. methinks.
3350 						case SB_BOTTOM:
3351 							// writeln("end");
3352 							auto event = new Event("scrolltoend", *widgetp);
3353 							event.dispatch();
3354 							//if(!event.defaultPrevented)
3355 						break;
3356 						case SB_TOP:
3357 							// writeln("top");
3358 							auto event = new Event("scrolltobeginning", *widgetp);
3359 							event.dispatch();
3360 						break;
3361 						case SB_ENDSCROLL:
3362 							// idk
3363 						break;
3364 						+/
3365 						case SB_LINEDOWN:
3366 							(*widgetp).emitCommand!"scrolltonextline"();
3367 						return 0;
3368 						case SB_LINEUP:
3369 							(*widgetp).emitCommand!"scrolltopreviousline"();
3370 						return 0;
3371 						case SB_PAGEDOWN:
3372 							(*widgetp).emitCommand!"scrolltonextpage"();
3373 						return 0;
3374 						case SB_PAGEUP:
3375 							(*widgetp).emitCommand!"scrolltopreviouspage"();
3376 						return 0;
3377 						case SB_THUMBPOSITION:
3378 							auto ev = new ScrollToPositionEvent(*widgetp, pos);
3379 							ev.dispatch();
3380 						return 0;
3381 						case SB_THUMBTRACK:
3382 							// eh kinda lying but i like the real time update display
3383 							auto ev = new ScrollToPositionEvent(*widgetp, pos);
3384 							ev.dispatch();
3385 
3386 							// the event loop doesn't seem to carry on with a requested redraw..
3387 							// so we request it to get our dirty bit set...
3388 							// then we need to immediately actually redraw it too for instant feedback to user
3389 							SimpleWindow.processAllCustomEvents();
3390 							SimpleWindow.processAllCustomEvents();
3391 							//if(this_.parentWindow)
3392 								//this_.parentWindow.actualRedraw();
3393 
3394 							// and this ensures the WM_PAINT message is sent fairly quickly
3395 							// still seems to lag a little in large windows but meh it basically works.
3396 							if(this_.parentWindow) {
3397 								// FIXME: if painting is slow, this does still lag
3398 								// we probably will want to expose some user hook to ScrollWindowEx
3399 								// or something.
3400 								UpdateWindow(this_.parentWindow.hwnd);
3401 							}
3402 						return 0;
3403 						default:
3404 					}
3405 				}
3406 			break;
3407 
3408 			case WM_CONTEXTMENU:
3409 				auto hwndFrom = cast(HWND) wParam;
3410 
3411 				auto xPos = cast(short) LOWORD(lParam);
3412 				auto yPos = cast(short) HIWORD(lParam);
3413 
3414 				if(auto widgetp = hwndFrom in Widget.nativeMapping) {
3415 					POINT p;
3416 					p.x = xPos;
3417 					p.y = yPos;
3418 					ScreenToClient(hwnd, &p);
3419 					auto clientX = cast(ushort) p.x;
3420 					auto clientY = cast(ushort) p.y;
3421 
3422 					auto wap = widgetAtPoint(*widgetp, clientX, clientY);
3423 
3424 					if(wap.widget.showContextMenu(wap.x, wap.y, xPos, yPos)) {
3425 						return 0;
3426 					}
3427 				}
3428 			break;
3429 
3430 			case WM_DRAWITEM:
3431 				auto dis = cast(DRAWITEMSTRUCT*) lParam;
3432 				if(auto widgetp = dis.hwndItem in Widget.nativeMapping) {
3433 					return (*widgetp).handleWmDrawItem(dis);
3434 				}
3435 			break;
3436 
3437 			case WM_NOTIFY:
3438 				auto hdr = cast(NMHDR*) lParam;
3439 				auto hwndFrom = hdr.hwndFrom;
3440 				auto code = hdr.code;
3441 
3442 				if(auto widgetp = hwndFrom in Widget.nativeMapping) {
3443 					return (*widgetp).handleWmNotify(hdr, code, mustReturn);
3444 				}
3445 			break;
3446 			case WM_COMMAND:
3447 				auto handle = cast(HWND) lParam;
3448 				auto cmd = HIWORD(wParam);
3449 				return processWmCommand(hwnd, handle, cmd, LOWORD(wParam));
3450 
3451 			default:
3452 				// pass it on
3453 		}
3454 		return 0;
3455 	}
3456 
3457 
3458 
3459 	extern(Windows)
3460 	private
3461 	// this is called by native child windows, whereas the other hook is done by simpledisplay windows
3462 	// but can i merge them?!
3463 	LRESULT HookedWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
3464 		// try { writeln(iMessage); } catch(Exception e) {};
3465 
3466 		if(auto te = hWnd in Widget.nativeMapping) {
3467 			try {
3468 
3469 				te.hookedWndProc(iMessage, wParam, lParam);
3470 
3471 				int mustReturn;
3472 				auto ret = WindowProcedureHelper(*te, hWnd, iMessage, wParam, lParam, mustReturn);
3473 				if(mustReturn)
3474 					return ret;
3475 
3476 				if(iMessage == WM_SETFOCUS) {
3477 					auto lol = *te;
3478 					while(lol !is null && lol.implicitlyCreated)
3479 						lol = lol.parent;
3480 					lol.focus();
3481 					//(*te).parentWindow.focusedWidget = lol;
3482 				}
3483 
3484 
3485 				if(iMessage == WM_CTLCOLORBTN || iMessage == WM_CTLCOLORSTATIC) {
3486 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
3487 					return cast(typeof(return)) GetSysColorBrush(COLOR_3DFACE); // this is the window background color...
3488 						//GetStockObject(NULL_BRUSH);
3489 				}
3490 
3491 				auto pos = getChildPositionRelativeToParentOrigin(*te);
3492 				lastDefaultPrevented = false;
3493 				// try { writeln(typeid(*te)); } catch(Exception e) {}
3494 				if(SimpleWindow.triggerEvents(hWnd, iMessage, wParam, lParam, pos[0], pos[1], (*te).parentWindow.win) || !lastDefaultPrevented)
3495 					return CallWindowProcW((*te).originalWindowProcedure, hWnd, iMessage, wParam, lParam);
3496 				else {
3497 					// it was something we recognized, should only call the window procedure if the default was not prevented
3498 				}
3499 			} catch(Exception e) {
3500 				assert(0, e.toString());
3501 			}
3502 			return 0;
3503 		}
3504 		assert(0, "shouldn't be receiving messages for this window....");
3505 		//assert(0, to!string(hWnd) ~ " :: " ~ to!string(TextEdit.nativeMapping)); // not supposed to happen
3506 	}
3507 
3508 	extern(Windows)
3509 	private
3510 	// see for info https://jeffpar.github.io/kbarchive/kb/079/Q79982/
3511 	LRESULT HookedWndProcBSGROUPBOX_HACK(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
3512 		if(iMessage == WM_ERASEBKGND) {
3513 			auto dc = GetDC(hWnd);
3514 			auto b = SelectObject(dc, GetSysColorBrush(COLOR_3DFACE));
3515 			auto p = SelectObject(dc, GetStockObject(NULL_PEN));
3516 			RECT r;
3517 			GetWindowRect(hWnd, &r);
3518 			// since the pen is null, to fill the whole space, we need the +1 on both.
3519 			gdi.Rectangle(dc, 0, 0, r.right - r.left + 1, r.bottom - r.top + 1);
3520 			SelectObject(dc, p);
3521 			SelectObject(dc, b);
3522 			ReleaseDC(hWnd, dc);
3523 			InvalidateRect(hWnd, null, false); // redraw the border
3524 			return 1;
3525 		}
3526 		return HookedWndProc(hWnd, iMessage, wParam, lParam);
3527 	}
3528 
3529 	/++
3530 		Calls MS Windows' CreateWindowExW function to create a native backing for the given widget. It will create
3531 		needed mappings, window procedure hooks, and other private member variables needed to tie it into the rest
3532 		of minigui's expectations.
3533 
3534 		This should be called in your widget's constructor AFTER you call `super(parent);`. The parent window
3535 		member MUST already be initialized for this function to succeed, which is done by [Widget]'s base constructor.
3536 
3537 		It assumes `className` is zero-terminated. It should come from a `"wide string literal"w`.
3538 
3539 		To check if you can use this, use `static if(UsingWin32Widgets)`.
3540 	+/
3541 	void createWin32Window(Widget p, const(wchar)[] className, string windowText, DWORD style, DWORD extStyle = 0) {
3542 		assert(p.parentWindow !is null);
3543 		assert(p.parentWindow.win.impl.hwnd !is null);
3544 
3545 		auto bsgroupbox = style == BS_GROUPBOX;
3546 
3547 		HWND phwnd;
3548 
3549 		auto wtf = p.parent;
3550 		while(wtf) {
3551 			if(wtf.hwnd !is null) {
3552 				phwnd = wtf.hwnd;
3553 				break;
3554 			}
3555 			wtf = wtf.parent;
3556 		}
3557 
3558 		if(phwnd is null)
3559 			phwnd = p.parentWindow.win.impl.hwnd;
3560 
3561 		assert(phwnd !is null);
3562 
3563 		WCharzBuffer wt = WCharzBuffer(windowText);
3564 
3565 		style |= WS_VISIBLE | WS_CHILD;
3566 		//if(className != WC_TABCONTROL)
3567 			style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
3568 		p.hwnd = CreateWindowExW(extStyle, className.ptr, wt.ptr, style,
3569 				CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
3570 				phwnd, null, cast(HINSTANCE) GetModuleHandle(null), null);
3571 
3572 		assert(p.hwnd !is null);
3573 
3574 
3575 		static HFONT font;
3576 		if(font is null) {
3577 			NONCLIENTMETRICS params;
3578 			params.cbSize = params.sizeof;
3579 			if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
3580 				font = CreateFontIndirect(&params.lfMessageFont);
3581 			}
3582 		}
3583 
3584 		if(font)
3585 			SendMessage(p.hwnd, WM_SETFONT, cast(uint) font, true);
3586 
3587 		p.simpleWindowWrappingHwnd = new SimpleWindow(p.hwnd);
3588 		p.simpleWindowWrappingHwnd.beingOpenKeepsAppOpen = false;
3589 		Widget.nativeMapping[p.hwnd] = p;
3590 
3591 		if(bsgroupbox)
3592 		p.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(p.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProcBSGROUPBOX_HACK);
3593 		else
3594 		p.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(p.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc);
3595 
3596 		EnumChildWindows(p.hwnd, &childHandler, cast(LPARAM) cast(void*) p);
3597 
3598 		p.registerMovement();
3599 	}
3600 }
3601 
3602 version(win32_widgets)
3603 private
3604 extern(Windows) BOOL childHandler(HWND hwnd, LPARAM lparam) {
3605 	if(hwnd is null || hwnd in Widget.nativeMapping)
3606 		return true;
3607 	auto parent = cast(Widget) cast(void*) lparam;
3608 	Widget p = new Widget(null);
3609 	p._parent = parent;
3610 	p.parentWindow = parent.parentWindow;
3611 	p.hwnd = hwnd;
3612 	p.implicitlyCreated = true;
3613 	Widget.nativeMapping[p.hwnd] = p;
3614 	p.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(p.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc);
3615 	return true;
3616 }
3617 
3618 /++
3619 	Encapsulates the simpledisplay [ScreenPainter] for use on a [Widget], with [VisualTheme] and invalidated area awareness.
3620 +/
3621 struct WidgetPainter {
3622 	this(ScreenPainter screenPainter, Widget drawingUpon) {
3623 		this.drawingUpon = drawingUpon;
3624 		this.screenPainter = screenPainter;
3625 		if(auto font = visualTheme.defaultFontCached(drawingUpon.currentDpi))
3626 			this.screenPainter.setFont(font);
3627 	}
3628 
3629 	/++
3630 		EXPERIMENTAL. subject to change.
3631 
3632 		When you draw a cursor, you can draw this to notify your window of where it is,
3633 		for IME systems to use.
3634 	+/
3635 	void notifyCursorPosition(int x, int y, int width, int height) {
3636 		if(auto a = drawingUpon.parentWindow)
3637 		if(auto w = a.inputProxy) {
3638 			w.setIMEPopupLocation(x + screenPainter.originX + width, y + screenPainter.originY + height);
3639 		}
3640 	}
3641 
3642 
3643 	///
3644 	ScreenPainter screenPainter;
3645 	/// Forward to the screen painter for other methods
3646 	alias screenPainter this;
3647 
3648 	private Widget drawingUpon;
3649 
3650 	/++
3651 		This is the list of rectangles that actually need to be redrawn.
3652 
3653 		Not actually implemented yet.
3654 	+/
3655 	Rectangle[] invalidatedRectangles;
3656 
3657 	private static BaseVisualTheme _visualTheme;
3658 
3659 	/++
3660 		Functions to access the visual theme and helpers to easily use it.
3661 
3662 		These are aware of the current widget's computed style out of the theme.
3663 	+/
3664 	static @property BaseVisualTheme visualTheme() {
3665 		if(_visualTheme is null)
3666 			_visualTheme = new DefaultVisualTheme();
3667 		return _visualTheme;
3668 	}
3669 
3670 	/// ditto
3671 	static @property void visualTheme(BaseVisualTheme theme) {
3672 		_visualTheme = theme;
3673 
3674 		// FIXME: notify all windows about the new theme
3675 	}
3676 
3677 	/// ditto
3678 	Color themeForeground() {
3679 		return drawingUpon.getComputedStyle().foregroundColor();
3680 	}
3681 
3682 	/// ditto
3683 	Color themeBackground() {
3684 		return drawingUpon.getComputedStyle().background.color;
3685 	}
3686 
3687 	int isDarkTheme() {
3688 		return 0; // unspecified, yes, no as enum. FIXME
3689 	}
3690 
3691 	/++
3692 		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.
3693 
3694 		It gives your draw delegate a [Rectangle] representing the coordinates inside your border and padding.
3695 
3696 		If you change teh clip rectangle, you should change it back before you return.
3697 
3698 
3699 		The sequence it uses is:
3700 			background
3701 			content (delegated to you)
3702 			border
3703 			focused outline
3704 			selected overlay
3705 
3706 		Example code:
3707 
3708 		---
3709 		void paint(WidgetPainter painter) {
3710 			painter.drawThemed((bounds) {
3711 				return bounds; // if the selection overlay should be contained, you can return it here.
3712 			});
3713 		}
3714 		---
3715 	+/
3716 	void drawThemed(scope Rectangle delegate(const Rectangle bounds) drawBody) {
3717 		drawThemed((WidgetPainter painter, const Rectangle bounds) {
3718 			return drawBody(bounds);
3719 		});
3720 	}
3721 	// this overload is actually mroe for setting the delegate to a virtual function
3722 	void drawThemed(scope Rectangle delegate(WidgetPainter painter, const Rectangle bounds) drawBody) {
3723 		Rectangle rect = Rectangle(0, 0, drawingUpon.width, drawingUpon.height);
3724 
3725 		auto cs = drawingUpon.getComputedStyle();
3726 
3727 		auto bg = cs.background.color;
3728 
3729 		auto borderWidth = draw3dFrame(0, 0, drawingUpon.width, drawingUpon.height, this, cs.borderStyle, bg, cs.borderColor);
3730 
3731 		rect.left += borderWidth;
3732 		rect.right -= borderWidth;
3733 		rect.top += borderWidth;
3734 		rect.bottom -= borderWidth;
3735 
3736 		auto insideBorderRect = rect;
3737 
3738 		rect.left += cs.paddingLeft;
3739 		rect.right -= cs.paddingRight;
3740 		rect.top += cs.paddingTop;
3741 		rect.bottom -= cs.paddingBottom;
3742 
3743 		this.outlineColor = this.themeForeground;
3744 		this.fillColor = bg;
3745 
3746 		auto widgetFont = cs.fontCached;
3747 		if(widgetFont !is null)
3748 			this.setFont(widgetFont);
3749 
3750 		rect = drawBody(this, rect);
3751 
3752 		if(widgetFont !is null) {
3753 			if(auto vtFont = visualTheme.defaultFontCached(drawingUpon.currentDpi))
3754 				this.setFont(vtFont);
3755 			else
3756 				this.setFont(null);
3757 		}
3758 
3759 		if(auto os = cs.outlineStyle()) {
3760 			this.pen = Pen(cs.outlineColor(), 1, os == FrameStyle.dotted ? Pen.Style.Dotted : Pen.Style.Solid);
3761 			this.fillColor = Color.transparent;
3762 			this.drawRectangle(insideBorderRect);
3763 		}
3764 	}
3765 
3766 	/++
3767 		First, draw the background.
3768 		Then draw your content.
3769 		Next, draw the border.
3770 		And the focused indicator.
3771 		And the is-selected box.
3772 
3773 		If it is focused i can draw the outline too...
3774 
3775 		If selected i can even do the xor action but that's at the end.
3776 	+/
3777 	void drawThemeBackground() {
3778 
3779 	}
3780 
3781 	void drawThemeBorder() {
3782 
3783 	}
3784 
3785 	// all this stuff is a dangerous experiment....
3786 	static class ScriptableVersion {
3787 		ScreenPainterImplementation* p;
3788 		int originX, originY;
3789 
3790 		@scriptable:
3791 		void drawRectangle(int x, int y, int width, int height) {
3792 			p.drawRectangle(x + originX, y + originY, width, height);
3793 		}
3794 		void drawLine(int x1, int y1, int x2, int y2) {
3795 			p.drawLine(x1 + originX, y1 + originY, x2 + originX, y2 + originY);
3796 		}
3797 		void drawText(int x, int y, string text) {
3798 			p.drawText(x + originX, y + originY, 100000, 100000, text, 0);
3799 		}
3800 		void setOutlineColor(int r, int g, int b) {
3801 			p.pen = Pen(Color(r,g,b), 1);
3802 		}
3803 		void setFillColor(int r, int g, int b) {
3804 			p.fillColor = Color(r,g,b);
3805 		}
3806 	}
3807 
3808 	ScriptableVersion toArsdJsvar() {
3809 		auto sv = new ScriptableVersion;
3810 		sv.p = this.screenPainter.impl;
3811 		sv.originX = this.screenPainter.originX;
3812 		sv.originY = this.screenPainter.originY;
3813 		return sv;
3814 	}
3815 
3816 	static WidgetPainter fromJsVar(T)(T t) {
3817 		return WidgetPainter.init;
3818 	}
3819 	// done..........
3820 }
3821 
3822 
3823 struct Style {
3824 	static struct helper(string m, T) {
3825 		enum method = m;
3826 		T v;
3827 
3828 		mixin template MethodOverride(typeof(this) v) {
3829 			mixin("override typeof(v.v) "~v.method~"() { return v.v; }");
3830 		}
3831 	}
3832 
3833 	static auto opDispatch(string method, T)(T value) {
3834 		return helper!(method, T)(value);
3835 	}
3836 }
3837 
3838 /++
3839 	Implementation detail of the [ControlledBy] UDA.
3840 
3841 	History:
3842 		Added Oct 28, 2020
3843 +/
3844 struct ControlledBy_(T, Args...) {
3845 	Args args;
3846 
3847 	static if(Args.length)
3848 	this(Args args) {
3849 		this.args = args;
3850 	}
3851 
3852 	private T construct(Widget parent) {
3853 		return new T(args, parent);
3854 	}
3855 }
3856 
3857 /++
3858 	User-defined attribute you can add to struct members contrlled by [addDataControllerWidget] or [dialog] to tell which widget you want created for them.
3859 
3860 	History:
3861 		Added Oct 28, 2020
3862 +/
3863 auto ControlledBy(T, Args...)(Args args) {
3864 	return ControlledBy_!(T, Args)(args);
3865 }
3866 
3867 struct ContainerMeta {
3868 	string name;
3869 	ContainerMeta[] children;
3870 	Widget function(Widget parent) factory;
3871 
3872 	Widget instantiate(Widget parent) {
3873 		auto n = factory(parent);
3874 		n.name = name;
3875 		foreach(child; children)
3876 			child.instantiate(n);
3877 		return n;
3878 	}
3879 }
3880 
3881 /++
3882 	This is a helper for [addDataControllerWidget]. You can use it as a UDA on the type. See
3883 	http://dpldocs.info/this-week-in-d/Blog.Posted_2020_11_02.html for more information.
3884 
3885 	Please note that as of May 28, 2021, a dmd bug prevents this from compiling on module-level
3886 	structures. It works fine on structs declared inside functions though.
3887 
3888 	See: https://issues.dlang.org/show_bug.cgi?id=21984
3889 +/
3890 template Container(CArgs...) {
3891 	static if(CArgs.length && is(CArgs[0] : Widget)) {
3892 		private alias Super = CArgs[0];
3893 		private alias CArgs2 = CArgs[1 .. $];
3894 	} else {
3895 		private alias Super = Layout;
3896 		private alias CArgs2 = CArgs;
3897 	}
3898 
3899 	class Container : Super {
3900 		this(Widget parent) { super(parent); }
3901 
3902 		// just to partially support old gdc versions
3903 		version(GNU) {
3904 			static if(CArgs2.length >= 1) { enum tmp0 = CArgs2[0]; mixin typeof(tmp0).MethodOverride!(CArgs2[0]); }
3905 			static if(CArgs2.length >= 2) { enum tmp1 = CArgs2[1]; mixin typeof(tmp1).MethodOverride!(CArgs2[1]); }
3906 			static if(CArgs2.length >= 3) { enum tmp2 = CArgs2[2]; mixin typeof(tmp2).MethodOverride!(CArgs2[2]); }
3907 			static if(CArgs2.length > 3) static assert(0, "only a few overrides like this supported on your compiler version at this time");
3908 		} else mixin(q{
3909 			static foreach(Arg; CArgs2) {
3910 				mixin Arg.MethodOverride!(Arg);
3911 			}
3912 		});
3913 
3914 		static ContainerMeta opCall(string name, ContainerMeta[] children...) {
3915 			return ContainerMeta(
3916 				name,
3917 				children.dup,
3918 				function (Widget parent) { return new typeof(this)(parent); }
3919 			);
3920 		}
3921 
3922 		static ContainerMeta opCall(ContainerMeta[] children...) {
3923 			return opCall(null, children);
3924 		}
3925 	}
3926 }
3927 
3928 /++
3929 	The data controller widget is created by reflecting over the given
3930 	data type. You can use [ControlledBy] as a UDA on a struct or
3931 	just let it create things automatically.
3932 
3933 	Unlike [dialog], this uses real-time updating of the data and
3934 	you add it to another window yourself.
3935 
3936 	---
3937 		struct Test {
3938 			int x;
3939 			int y;
3940 		}
3941 
3942 		auto window = new Window();
3943 		auto dcw = new DataControllerWidget!Test(new Test, window);
3944 	---
3945 
3946 	The way it works is any public members are given a widget based
3947 	on their data type, and public methods trigger an action button
3948 	if no relevant parameters or a dialog action if it does have
3949 	parameters, similar to the [menu] facility.
3950 
3951 	If you change data programmatically, without going through the
3952 	DataControllerWidget methods, you will have to tell it something
3953 	has changed and it needs to redraw. This is done with the `invalidate`
3954 	method.
3955 
3956 	History:
3957 		Added Oct 28, 2020
3958 +/
3959 /// Group: generating_from_code
3960 class DataControllerWidget(T) : WidgetContainer {
3961 	static if(is(T == class) || is(T == interface) || is(T : const E[], E))
3962 		private alias Tref = T;
3963 	else
3964 		private alias Tref = T*;
3965 
3966 	Tref datum;
3967 
3968 	/++
3969 		See_also: [addDataControllerWidget]
3970 	+/
3971 	this(Tref datum, Widget parent) {
3972 		this.datum = datum;
3973 
3974 		Widget cp = this;
3975 
3976 		super(parent);
3977 
3978 		foreach(attr; __traits(getAttributes, T))
3979 			static if(is(typeof(attr) == ContainerMeta)) {
3980 				cp = attr.instantiate(this);
3981 			}
3982 
3983 		auto def = this.getByName("default");
3984 		if(def !is null)
3985 			cp = def;
3986 
3987 		Widget helper(string name) {
3988 			auto maybe = this.getByName(name);
3989 			if(maybe is null)
3990 				return cp;
3991 			return maybe;
3992 
3993 		}
3994 
3995 		foreach(member; __traits(allMembers, T))
3996 		static if(member != "this") // wtf https://issues.dlang.org/show_bug.cgi?id=22011
3997 		static if(is(typeof(__traits(getMember, this.datum, member))))
3998 		static if(__traits(getProtection, __traits(getMember, this.datum, member)) == "public") {
3999 			void delegate() update;
4000 
4001 			auto w = widgetFor!(__traits(getMember, T, member))(&__traits(getMember, this.datum, member), helper(member), update);
4002 
4003 			if(update)
4004 				updaters ~= update;
4005 
4006 			static if(is(typeof(__traits(getMember, this.datum, member)) == function)) {
4007 				w.addEventListener("triggered", delegate() {
4008 					makeAutomaticHandler!(__traits(getMember, this.datum, member))(&__traits(getMember, this.datum, member))();
4009 					notifyDataUpdated();
4010 				});
4011 			} else static if(is(typeof(w.isChecked) == bool)) {
4012 				w.addEventListener(EventType.change, (Event ev) {
4013 					__traits(getMember, this.datum, member) = w.isChecked;
4014 				});
4015 			} else static if(is(typeof(w.value) == string) || is(typeof(w.content) == string)) {
4016 				w.addEventListener("change", (Event e) { genericSetValue(&__traits(getMember, this.datum, member), e.stringValue); } );
4017 			} else static if(is(typeof(w.value) == int)) {
4018 				w.addEventListener("change", (Event e) { genericSetValue(&__traits(getMember, this.datum, member), e.intValue); } );
4019 			} else static if(is(typeof(w) == DropDownSelection)) {
4020 				// special case for this to kinda support enums and such. coudl be better though
4021 				w.addEventListener("change", (Event e) { genericSetValue(&__traits(getMember, this.datum, member), e.intValue); } );
4022 			} else {
4023 				//static assert(0, "unsupported type " ~ typeof(__traits(getMember, this.datum, member)).stringof ~ " " ~ typeof(w).stringof);
4024 			}
4025 		}
4026 	}
4027 
4028 	/++
4029 		If you modify the data in the structure directly, you need to call this to update the UI and propagate any change messages.
4030 
4031 		History:
4032 			Added May 28, 2021
4033 	+/
4034 	void notifyDataUpdated() {
4035 		foreach(updater; updaters)
4036 			updater();
4037 
4038 		this.emit!(ChangeEvent!void)(delegate{});
4039 	}
4040 
4041 	private Widget[string] memberWidgets;
4042 	private void delegate()[] updaters;
4043 
4044 	mixin Emits!(ChangeEvent!void);
4045 }
4046 
4047 private int saturatedSum(int[] values...) {
4048 	int sum;
4049 	foreach(value; values) {
4050 		if(value == int.max)
4051 			return int.max;
4052 		sum += value;
4053 	}
4054 	return sum;
4055 }
4056 
4057 void genericSetValue(T, W)(T* where, W what) {
4058 	import std.conv;
4059 	*where = to!T(what);
4060 	//*where = cast(T) stringToLong(what);
4061 }
4062 
4063 /++
4064 	Creates a widget for the value `tt`, which is pointed to at runtime by `valptr`, with the given parent.
4065 
4066 	The `update` delegate can be called if you change `*valptr` to reflect those changes in the widget.
4067 
4068 	Note that this creates the widget but does not attach any event handlers to it.
4069 +/
4070 private static auto widgetFor(alias tt, P)(P valptr, Widget parent, out void delegate() update) {
4071 
4072 	string displayName = __traits(identifier, tt).beautify;
4073 
4074 	static if(controlledByCount!tt == 1) {
4075 		foreach(i, attr; __traits(getAttributes, tt)) {
4076 			static if(is(typeof(attr) == ControlledBy_!(T, Args), T, Args...)) {
4077 				auto w = attr.construct(parent);
4078 				static if(__traits(compiles, w.setPosition(*valptr)))
4079 					update = () { w.setPosition(*valptr); };
4080 				else static if(__traits(compiles, w.setValue(*valptr)))
4081 					update = () { w.setValue(*valptr); };
4082 
4083 				if(update)
4084 					update();
4085 				return w;
4086 			}
4087 		}
4088 	} else static if(controlledByCount!tt == 0) {
4089 		static if(is(typeof(tt) == enum)) {
4090 			// FIXME: update
4091 			auto dds = new DropDownSelection(parent);
4092 			foreach(idx, option; __traits(allMembers, typeof(tt))) {
4093 				dds.addOption(option);
4094 				if(__traits(getMember, typeof(tt), option) == *valptr)
4095 					dds.setSelection(cast(int) idx);
4096 			}
4097 			return dds;
4098 		} else static if(is(typeof(tt) == bool)) {
4099 			auto box = new Checkbox(displayName, parent);
4100 			update = () { box.isChecked = *valptr; };
4101 			update();
4102 			return box;
4103 		} else static if(is(typeof(tt) : const long)) {
4104 			auto le = new LabeledLineEdit(displayName, parent);
4105 			update = () { le.content = toInternal!string(*valptr); };
4106 			update();
4107 			return le;
4108 		} else static if(is(typeof(tt) : const double)) {
4109 			auto le = new LabeledLineEdit(displayName, parent);
4110 			import std.conv;
4111 			update = () { le.content = to!string(*valptr); };
4112 			update();
4113 			return le;
4114 		} else static if(is(typeof(tt) : const string)) {
4115 			auto le = new LabeledLineEdit(displayName, parent);
4116 			update = () { le.content = *valptr; };
4117 			update();
4118 			return le;
4119 		} else static if(is(typeof(tt) == function)) {
4120 			auto w = new Button(displayName, parent);
4121 			return w;
4122 		} else static if(is(typeof(tt) == class) || is(typeof(tt) == interface)) {
4123 			return parent.addDataControllerWidget(tt);
4124 		} else static assert(0, typeof(tt).stringof);
4125 	} else static assert(0, "multiple controllers not yet supported");
4126 }
4127 
4128 private template controlledByCount(alias tt) {
4129 	static int helper() {
4130 		int count;
4131 		foreach(i, attr; __traits(getAttributes, tt))
4132 			static if(is(typeof(attr) == ControlledBy_!(T, Args), T, Args...))
4133 				count++;
4134 		return count;
4135 	}
4136 
4137 	enum controlledByCount = helper;
4138 }
4139 
4140 /++
4141 	Intended for UFCS action like `window.addDataControllerWidget(new MyObject());`
4142 
4143 	If you provide a `redrawOnChange` widget, it will automatically register a change event handler that calls that widget's redraw method.
4144 
4145 	History:
4146 		The `redrawOnChange` parameter was added on May 28, 2021.
4147 +/
4148 DataControllerWidget!T addDataControllerWidget(T)(Widget parent, T t, Widget redrawOnChange = null) if(is(T == class) || is(T == interface)) {
4149 	auto dcw = new DataControllerWidget!T(t, parent);
4150 	initializeDataControllerWidget(dcw, redrawOnChange);
4151 	return dcw;
4152 }
4153 
4154 /// ditto
4155 DataControllerWidget!T addDataControllerWidget(T)(Widget parent, T* t, Widget redrawOnChange = null) if(is(T == struct)) {
4156 	auto dcw = new DataControllerWidget!T(t, parent);
4157 	initializeDataControllerWidget(dcw, redrawOnChange);
4158 	return dcw;
4159 }
4160 
4161 private void initializeDataControllerWidget(Widget w, Widget redrawOnChange) {
4162 	if(redrawOnChange !is null)
4163 		w.addEventListener("change", delegate() { redrawOnChange.redraw(); });
4164 }
4165 
4166 /++
4167 	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.
4168 
4169 	History:
4170 		Finalized on June 3, 2021 for the dub v10.0 release
4171 +/
4172 struct StyleInformation {
4173 	private Widget w;
4174 	private BaseVisualTheme visualTheme;
4175 
4176 	private this(Widget w) {
4177 		this.w = w;
4178 		this.visualTheme = WidgetPainter.visualTheme;
4179 	}
4180 
4181 	/++
4182 		Forwards to [Widget.Style]
4183 
4184 		Bugs:
4185 			It is supposed to fall back to the [VisualTheme] if
4186 			the style doesn't override the default, but that is
4187 			not generally implemented. Many of them may end up
4188 			being explicit overloads instead of the generic
4189 			opDispatch fallback, like [font] is now.
4190 	+/
4191 	public @property opDispatch(string name)() {
4192 		typeof(__traits(getMember, Widget.Style.init, name)()) prop;
4193 		w.useStyleProperties((scope Widget.Style props) {
4194 		//visualTheme.useStyleProperties(w, (props) {
4195 			prop = __traits(getMember, props, name);
4196 		});
4197 		return prop;
4198 	}
4199 
4200 	/++
4201 		Returns the cached font object associated with the widget,
4202 		if overridden by the [Widget.Style|Style], or the [VisualTheme] if not.
4203 
4204 		History:
4205 			Prior to March 21, 2022 (dub v10.7), `font` went through
4206 			[opDispatch], which did not use the cache. You can now call it
4207 			repeatedly without guilt.
4208 	+/
4209 	public @property OperatingSystemFont font() {
4210 		OperatingSystemFont prop;
4211 		w.useStyleProperties((scope Widget.Style props) {
4212 			prop = props.fontCached;
4213 		});
4214 		if(prop is null) {
4215 			prop = visualTheme.defaultFontCached(w.currentDpi);
4216 		}
4217 		return prop;
4218 	}
4219 
4220 	@property {
4221 		// Layout helpers. Currently just forwarding since I haven't made up my mind on a better way.
4222 		/** */ int paddingLeft() { return w.paddingLeft(); }
4223 		/** */ int paddingRight() { return w.paddingRight(); }
4224 		/** */ int paddingTop() { return w.paddingTop(); }
4225 		/** */ int paddingBottom() { return w.paddingBottom(); }
4226 
4227 		/** */ int marginLeft() { return w.marginLeft(); }
4228 		/** */ int marginRight() { return w.marginRight(); }
4229 		/** */ int marginTop() { return w.marginTop(); }
4230 		/** */ int marginBottom() { return w.marginBottom(); }
4231 
4232 		/** */ int maxHeight() { return w.maxHeight(); }
4233 		/** */ int minHeight() { return w.minHeight(); }
4234 
4235 		/** */ int maxWidth() { return w.maxWidth(); }
4236 		/** */ int minWidth() { return w.minWidth(); }
4237 
4238 		/** */ int flexBasisWidth() { return w.flexBasisWidth(); }
4239 		/** */ int flexBasisHeight() { return w.flexBasisHeight(); }
4240 
4241 		/** */ int heightStretchiness() { return w.heightStretchiness(); }
4242 		/** */ int widthStretchiness() { return w.widthStretchiness(); }
4243 
4244 		/** */ int heightShrinkiness() { return w.heightShrinkiness(); }
4245 		/** */ int widthShrinkiness() { return w.widthShrinkiness(); }
4246 
4247 		// Global helpers some of these are unstable.
4248 		static:
4249 		/** */ Color windowBackgroundColor() { return WidgetPainter.visualTheme.windowBackgroundColor(); }
4250 		/** */ Color widgetBackgroundColor() { return WidgetPainter.visualTheme.widgetBackgroundColor(); }
4251 		/** */ Color lightAccentColor() { return WidgetPainter.visualTheme.lightAccentColor(); }
4252 		/** */ Color darkAccentColor() { return WidgetPainter.visualTheme.darkAccentColor(); }
4253 		/** */ Color selectionForegroundColor() { return WidgetPainter.visualTheme.selectionForegroundColor(); }
4254 		/** */ Color selectionBackgroundColor() { return WidgetPainter.visualTheme.selectionBackgroundColor(); }
4255 
4256 		/** */ Color activeTabColor() { return lightAccentColor; }
4257 		/** */ Color buttonColor() { return windowBackgroundColor; }
4258 		/** */ Color depressedButtonColor() { return darkAccentColor; }
4259 		/** */ Color hoveringColor() { return lightAccentColor; }
4260 		deprecated("Use selectionForegroundColor and selectionBackgroundColor instead") Color activeListXorColor() {
4261 			auto c = WidgetPainter.visualTheme.selectionColor();
4262 			return Color(c.r ^ 255, c.g ^ 255, c.b ^ 255, c.a);
4263 		}
4264 		/** */ Color progressBarColor() { return WidgetPainter.visualTheme.selectionBackgroundColor(); }
4265 		/** */ Color activeMenuItemColor() { return WidgetPainter.visualTheme.selectionBackgroundColor(); }
4266 	}
4267 
4268 
4269 
4270 	/+
4271 
4272 	private static auto extractStyleProperty(string name)(Widget w) {
4273 		typeof(__traits(getMember, Widget.Style.init, name)()) prop;
4274 		w.useStyleProperties((props) {
4275 			prop = __traits(getMember, props, name);
4276 		});
4277 		return prop;
4278 	}
4279 
4280 	// FIXME: clear this upon a X server disconnect
4281 	private static OperatingSystemFont[string] fontCache;
4282 
4283 	T getProperty(T)(string name, lazy T default_) {
4284 		if(visualTheme !is null) {
4285 			auto str = visualTheme.getPropertyString(w, name);
4286 			if(str is null)
4287 				return default_;
4288 			static if(is(T == Color))
4289 				return Color.fromString(str);
4290 			else static if(is(T == Measurement))
4291 				return Measurement(cast(int) toInternal!int(str));
4292 			else static if(is(T == WidgetBackground))
4293 				return WidgetBackground.fromString(str);
4294 			else static if(is(T == OperatingSystemFont)) {
4295 				if(auto f = str in fontCache)
4296 					return *f;
4297 				else
4298 					return fontCache[str] = new OperatingSystemFont(str);
4299 			} else static if(is(T == FrameStyle)) {
4300 				switch(str) {
4301 					default:
4302 						return FrameStyle.none;
4303 					foreach(style; __traits(allMembers, FrameStyle))
4304 					case style:
4305 						return __traits(getMember, FrameStyle, style);
4306 				}
4307 			} else static assert(0);
4308 		} else
4309 			return default_;
4310 	}
4311 
4312 	static struct Measurement {
4313 		int value;
4314 		alias value this;
4315 	}
4316 
4317 	@property:
4318 
4319 	int paddingLeft() { return getProperty("padding-left", Measurement(w.paddingLeft())); }
4320 	int paddingRight() { return getProperty("padding-right", Measurement(w.paddingRight())); }
4321 	int paddingTop() { return getProperty("padding-top", Measurement(w.paddingTop())); }
4322 	int paddingBottom() { return getProperty("padding-bottom", Measurement(w.paddingBottom())); }
4323 
4324 	int marginLeft() { return getProperty("margin-left", Measurement(w.marginLeft())); }
4325 	int marginRight() { return getProperty("margin-right", Measurement(w.marginRight())); }
4326 	int marginTop() { return getProperty("margin-top", Measurement(w.marginTop())); }
4327 	int marginBottom() { return getProperty("margin-bottom", Measurement(w.marginBottom())); }
4328 
4329 	int maxHeight() { return getProperty("max-height", Measurement(w.maxHeight())); }
4330 	int minHeight() { return getProperty("min-height", Measurement(w.minHeight())); }
4331 
4332 	int maxWidth() { return getProperty("max-width", Measurement(w.maxWidth())); }
4333 	int minWidth() { return getProperty("min-width", Measurement(w.minWidth())); }
4334 
4335 
4336 	WidgetBackground background() { return getProperty("background", extractStyleProperty!"background"(w)); }
4337 	Color foregroundColor() { return getProperty("foreground-color", extractStyleProperty!"foregroundColor"(w)); }
4338 
4339 	OperatingSystemFont font() { return getProperty("font", extractStyleProperty!"fontCached"(w)); }
4340 
4341 	FrameStyle borderStyle() { return getProperty("border-style", extractStyleProperty!"borderStyle"(w)); }
4342 	Color borderColor() { return getProperty("border-color", extractStyleProperty!"borderColor"(w)); }
4343 
4344 	FrameStyle outlineStyle() { return getProperty("outline-style", extractStyleProperty!"outlineStyle"(w)); }
4345 	Color outlineColor() { return getProperty("outline-color", extractStyleProperty!"outlineColor"(w)); }
4346 
4347 
4348 	Color windowBackgroundColor() { return WidgetPainter.visualTheme.windowBackgroundColor(); }
4349 	Color widgetBackgroundColor() { return WidgetPainter.visualTheme.widgetBackgroundColor(); }
4350 	Color lightAccentColor() { return WidgetPainter.visualTheme.lightAccentColor(); }
4351 	Color darkAccentColor() { return WidgetPainter.visualTheme.darkAccentColor(); }
4352 
4353 	Color activeTabColor() { return lightAccentColor; }
4354 	Color buttonColor() { return windowBackgroundColor; }
4355 	Color depressedButtonColor() { return darkAccentColor; }
4356 	Color hoveringColor() { return Color(228, 228, 228); }
4357 	Color activeListXorColor() {
4358 		auto c = WidgetPainter.visualTheme.selectionColor();
4359 		return Color(c.r ^ 255, c.g ^ 255, c.b ^ 255, c.a);
4360 	}
4361 	Color progressBarColor() { return WidgetPainter.visualTheme.selectionColor(); }
4362 	Color activeMenuItemColor() { return WidgetPainter.visualTheme.selectionColor(); }
4363 	+/
4364 }
4365 
4366 
4367 
4368 // pragma(msg, __traits(classInstanceSize, Widget));
4369 
4370 /*private*/ template EventString(E) {
4371 	static if(is(typeof(E.EventString)))
4372 		enum EventString = E.EventString;
4373 	else
4374 		enum EventString = E.mangleof; // FIXME fqn? or something more user friendly
4375 }
4376 
4377 /*private*/ template EventStringIdentifier(E) {
4378 	string helper() {
4379 		auto es = EventString!E;
4380 		char[] id = new char[](es.length * 2);
4381 		size_t idx;
4382 		foreach(char ch; es) {
4383 			id[idx++] = cast(char)('a' + (ch >> 4));
4384 			id[idx++] = cast(char)('a' + (ch & 0x0f));
4385 		}
4386 		return cast(string) id;
4387 	}
4388 
4389 	enum EventStringIdentifier = helper();
4390 }
4391 
4392 
4393 template classStaticallyEmits(This, EventType) {
4394 	static if(is(This Base == super))
4395 		static if(is(Base : Widget))
4396 			enum baseEmits = classStaticallyEmits!(Base, EventType);
4397 		else
4398 			enum baseEmits = false;
4399 	else
4400 		enum baseEmits = false;
4401 
4402 	enum thisEmits = is(typeof(__traits(getMember, This, "emits_" ~ EventStringIdentifier!EventType)) == EventType[0]);
4403 
4404 	enum classStaticallyEmits = thisEmits || baseEmits;
4405 }
4406 
4407 /++
4408 	A helper to make widgets out of other native windows.
4409 
4410 	History:
4411 		Factored out of OpenGlWidget on November 5, 2021
4412 +/
4413 class NestedChildWindowWidget : Widget {
4414 	SimpleWindow win;
4415 
4416 	/++
4417 		Used on X to send focus to the appropriate child window when requested by the window manager.
4418 
4419 		Normally returns its own nested window. Can also return another child or null to revert to the parent
4420 		if you override it in a child class.
4421 
4422 		History:
4423 			Added April 2, 2022 (dub v10.8)
4424 	+/
4425 	SimpleWindow focusableWindow() {
4426 		return win;
4427 	}
4428 
4429 	///
4430 	// win = new SimpleWindow(640, 480, null, OpenGlOptions.yes, Resizability.automaticallyScaleIfPossible, WindowTypes.nestedChild, WindowFlags.normal, getParentWindow(parent));
4431 	this(SimpleWindow win, Widget parent) {
4432 		this.parentWindow = parent.parentWindow;
4433 		this.win = win;
4434 
4435 		super(parent);
4436 		windowsetup(win);
4437 	}
4438 
4439 	static protected SimpleWindow getParentWindow(Widget parent) {
4440 		assert(parent !is null);
4441 		SimpleWindow pwin = parent.parentWindow.win;
4442 
4443 		version(win32_widgets) {
4444 			HWND phwnd;
4445 			auto wtf = parent;
4446 			while(wtf) {
4447 				if(wtf.hwnd) {
4448 					phwnd = wtf.hwnd;
4449 					break;
4450 				}
4451 				wtf = wtf.parent;
4452 			}
4453 			// kinda a hack here just because the ctor below just needs a SimpleWindow wrapper....
4454 			if(phwnd)
4455 				pwin = new SimpleWindow(phwnd);
4456 		}
4457 
4458 		return pwin;
4459 	}
4460 
4461 	/++
4462 		Called upon the nested window being destroyed.
4463 		Remember the window has already been destroyed at
4464 		this point, so don't use the native handle for anything.
4465 
4466 		History:
4467 			Added April 3, 2022 (dub v10.8)
4468 	+/
4469 	protected void dispose() {
4470 
4471 	}
4472 
4473 	protected void windowsetup(SimpleWindow w) {
4474 		/*
4475 		win.onFocusChange = (bool getting) {
4476 			if(getting)
4477 				this.focus();
4478 		};
4479 		*/
4480 
4481 		/+
4482 		win.onFocusChange = (bool getting) {
4483 			if(getting) {
4484 				this.parentWindow.focusedWidget = this;
4485 				this.emit!FocusEvent();
4486 				this.emit!FocusInEvent();
4487 			} else {
4488 				this.emit!BlurEvent();
4489 				this.emit!FocusOutEvent();
4490 			}
4491 		};
4492 		+/
4493 
4494 		win.onDestroyed = () {
4495 			this.dispose();
4496 		};
4497 
4498 		version(win32_widgets) {
4499 			Widget.nativeMapping[win.hwnd] = this;
4500 			this.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(win.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc);
4501 		} else {
4502 			win.setEventHandlers(
4503 				(MouseEvent e) {
4504 					Widget p = this;
4505 					while(p ! is parentWindow) {
4506 						e.x += p.x;
4507 						e.y += p.y;
4508 						p = p.parent;
4509 					}
4510 					parentWindow.dispatchMouseEvent(e);
4511 				},
4512 				(KeyEvent e) {
4513 					//writefln("%s %x   %s", cast(void*) win, cast(uint) e.key, e.key);
4514 					parentWindow.dispatchKeyEvent(e);
4515 				},
4516 				(dchar e) {
4517 					parentWindow.dispatchCharEvent(e);
4518 				},
4519 			);
4520 		}
4521 
4522 	}
4523 
4524 	override void showing(bool s, bool recalc) {
4525 		auto cur = hidden;
4526 		win.hidden = !s;
4527 		if(cur != s && s)
4528 			redraw();
4529 	}
4530 
4531 	/// OpenGL widgets cannot have child widgets. Do not call this.
4532 	/* @disable */ final override void addChild(Widget, int) {
4533 		throw new Error("cannot add children to OpenGL widgets");
4534 	}
4535 
4536 	/// When an opengl widget is laid out, it will adjust the glViewport for you automatically.
4537 	/// Keep in mind that events like mouse coordinates are still relative to your size.
4538 	override void registerMovement() {
4539 		// writefln("%d %d %d %d", x,y,width,height);
4540 		version(win32_widgets)
4541 			auto pos = getChildPositionRelativeToParentHwnd(this);
4542 		else
4543 			auto pos = getChildPositionRelativeToParentOrigin(this);
4544 		win.moveResize(pos[0], pos[1], width, height);
4545 
4546 		registerMovementAdditionalWork();
4547 		sendResizeEvent();
4548 	}
4549 
4550 	abstract void registerMovementAdditionalWork();
4551 }
4552 
4553 /++
4554 	Nests an opengl capable window inside this window as a widget.
4555 
4556 	You may also just want to create an additional [SimpleWindow] with
4557 	[OpenGlOptions.yes] yourself.
4558 
4559 	An OpenGL widget cannot have child widgets. It will throw if you try.
4560 +/
4561 static if(OpenGlEnabled)
4562 class OpenGlWidget : NestedChildWindowWidget {
4563 
4564 	override void registerMovementAdditionalWork() {
4565 		win.setAsCurrentOpenGlContext();
4566 	}
4567 
4568 	///
4569 	this(Widget parent) {
4570 		auto win = new SimpleWindow(640, 480, null, OpenGlOptions.yes, Resizability.automaticallyScaleIfPossible, WindowTypes.nestedChild, WindowFlags.normal, getParentWindow(parent));
4571 		super(win, parent);
4572 	}
4573 
4574 	override void paint(WidgetPainter painter) {
4575 		win.setAsCurrentOpenGlContext();
4576 		glViewport(0, 0, this.width, this.height);
4577 		win.redrawOpenGlSceneNow();
4578 	}
4579 
4580 	void redrawOpenGlScene(void delegate() dg) {
4581 		win.redrawOpenGlScene = dg;
4582 	}
4583 }
4584 
4585 /++
4586 	This demo shows how to draw text in an opengl scene.
4587 +/
4588 unittest {
4589 	import arsd.minigui;
4590 	import arsd.ttf;
4591 
4592 	void main() {
4593 		auto window = new Window();
4594 
4595 		auto widget = new OpenGlWidget(window);
4596 
4597 		// old means non-shader code so compatible with glBegin etc.
4598 		// tbh I haven't implemented new one in font yet...
4599 		// anyway, declaring here, will construct soon.
4600 		OpenGlLimitedFont!(OpenGlFontGLVersion.old) glfont;
4601 
4602 		// this is a little bit awkward, calling some methods through
4603 		// the underlying SimpleWindow `win` method, and you can't do this
4604 		// on a nanovega widget due to conflicts so I should probably fix
4605 		// the api to be a bit easier. But here it will work.
4606 		//
4607 		// Alternatively, you could load the font on the first draw, inside
4608 		// the redrawOpenGlScene, and keep a flag so you don't do it every
4609 		// time. That'd be a bit easier since the lib sets up the context
4610 		// by then guaranteed.
4611 		//
4612 		// But still, I wanna show this.
4613 		widget.win.visibleForTheFirstTime = delegate {
4614 			// must set the opengl context
4615 			widget.win.setAsCurrentOpenGlContext();
4616 
4617 			// if you were doing a OpenGL 3+ shader, this
4618 			// gets especially important to do in order. With
4619 			// old-style opengl, I think you can even do it
4620 			// in main(), but meh, let's show it more correctly.
4621 
4622 			// Anyway, now it is time to load the font from the
4623 			// OS (you can alternatively load one from a .ttf file
4624 			// you bundle with the application), then load the
4625 			// font into texture for drawing.
4626 
4627 			auto osfont = new OperatingSystemFont("DejaVu Sans", 18);
4628 
4629 			assert(!osfont.isNull()); // make sure it actually loaded
4630 
4631 			// using typeof to avoid repeating the long name lol
4632 			glfont = new typeof(glfont)(
4633 				// get the raw data from the font for loading in here
4634 				// since it doesn't use the OS function to draw the
4635 				// text, we gotta treat it more as a file than as
4636 				// a drawing api.
4637 				osfont.getTtfBytes(),
4638 				18, // need to respecify size since opengl world is different coordinate system
4639 
4640 				// these last two numbers are why it is called
4641 				// "Limited" font. It only loads the characters
4642 				// in the given range, since the texture atlas
4643 				// it references is all a big image generated ahead
4644 				// of time. You could maybe do the whole thing but
4645 				// idk how much memory that is.
4646 				//
4647 				// But here, 0-128 represents the ASCII range, so
4648 				// good enough for most English things, numeric labels,
4649 				// etc.
4650 				0,
4651 				128
4652 			);
4653 		};
4654 
4655 		widget.redrawOpenGlScene = () {
4656 			// now we can use the glfont's drawString function
4657 
4658 			// first some opengl setup. You can do this in one place
4659 			// on window first visible too in many cases, just showing
4660 			// here cuz it is easier for me.
4661 
4662 			// gonna need some alpha blending or it just looks awful
4663 			glEnable(GL_BLEND);
4664 			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
4665 			glClearColor(0,0,0,0);
4666 			glDepthFunc(GL_LEQUAL);
4667 
4668 			// Also need to enable 2d textures, since it draws the
4669 			// font characters as images baked in
4670 			glMatrixMode(GL_MODELVIEW);
4671 			glLoadIdentity();
4672 			glDisable(GL_DEPTH_TEST);
4673 			glEnable(GL_TEXTURE_2D);
4674 
4675 			// the orthographic matrix is best for 2d things like text
4676 			// so let's set that up. This matrix makes the coordinates
4677 			// in the opengl scene be one-to-one with the actual pixels
4678 			// on screen. (Not necessarily best, you may wish to scale
4679 			// things, but it does help keep fonts looking normal.)
4680 			glMatrixMode(GL_PROJECTION);
4681 			glLoadIdentity();
4682 			glOrtho(0, widget.width, widget.height, 0, 0, 1);
4683 
4684 			// you can do other glScale, glRotate, glTranslate, etc
4685 			// to the matrix here of course if you want.
4686 
4687 			// note the x,y coordinates here are for the text baseline
4688 			// NOT the upper-left corner. The baseline is like the line
4689 			// in the notebook you write on. Most the letters are actually
4690 			// above it, but some, like p and q, dip a bit below it.
4691 			//
4692 			// So if you're used to the upper left coordinate like the
4693 			// rest of simpledisplay/minigui usually do, do the
4694 			// y + glfont.ascent to bring it down a little. So this
4695 			// example puts the string in the upper left of the window.
4696 			glfont.drawString(0, 0 + glfont.ascent, "Hello!!", Color.green);
4697 
4698 			// re color btw: the function sets a solid color internally,
4699 			// but you actually COULD do your own thing for rainbow effects
4700 			// and the sort if you wanted too, by pulling its guts out.
4701 			// Just view its source for an idea of how it actually draws:
4702 			// http://arsd-official.dpldocs.info/source/arsd.ttf.d.html#L332
4703 
4704 			// it gets a bit complicated with the character positioning,
4705 			// but the opengl parts are fairly simple: bind a texture,
4706 			// set the color, draw a quad for each letter.
4707 
4708 
4709 			// the last optional argument there btw is a bounding box
4710 			// it will/ use to word wrap and return an object you can
4711 			// use to implement scrolling or pagination; it tells how
4712 			// much of the string didn't fit in the box. But for simple
4713 			// labels we can just ignore that.
4714 
4715 
4716 			// I'd suggest drawing text as the last step, after you
4717 			// do your other drawing. You might use the push/pop matrix
4718 			// stuff to keep your place. You, in theory, should be able
4719 			// to do text in a 3d space but I've never actually tried
4720 			// that....
4721 		};
4722 
4723 		window.loop();
4724 	}
4725 }
4726 
4727 version(custom_widgets)
4728 	private alias ListWidgetBase = ScrollableWidget;
4729 else
4730 	private alias ListWidgetBase = Widget;
4731 
4732 /++
4733 	A list widget contains a list of strings that the user can examine and select.
4734 
4735 
4736 	In the future, items in the list may be possible to be more than just strings.
4737 
4738 	See_Also:
4739 		[TableView]
4740 +/
4741 class ListWidget : ListWidgetBase {
4742 	/// 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.
4743 	mixin Emits!(ChangeEvent!void);
4744 
4745 	static struct Option {
4746 		string label;
4747 		bool selected;
4748 		void* tag;
4749 	}
4750 
4751 	/++
4752 		Sets the current selection to the `y`th item in the list. Will emit [ChangeEvent] when complete.
4753 	+/
4754 	void setSelection(int y) {
4755 		if(!multiSelect)
4756 			foreach(ref opt; options)
4757 				opt.selected = false;
4758 		if(y >= 0 && y < options.length)
4759 			options[y].selected = !options[y].selected;
4760 
4761 		this.emit!(ChangeEvent!void)(delegate {});
4762 
4763 		version(custom_widgets)
4764 			redraw();
4765 	}
4766 
4767 	/++
4768 		Gets the index of the selected item. In case of multi select, the index of the first selected item is returned.
4769 		Returns -1 if nothing is selected.
4770 	+/
4771 	int getSelection()
4772 	{
4773 		foreach(i, opt; options) {
4774 			if (opt.selected)
4775 				return cast(int) i;
4776 		}
4777 		return -1;
4778 	}
4779 
4780 	version(custom_widgets)
4781 	override void defaultEventHandler_click(ClickEvent event) {
4782 		this.focus();
4783 		if(event.button == MouseButton.left) {
4784 			auto y = (event.clientY - 4) / defaultLineHeight;
4785 			if(y >= 0 && y < options.length) {
4786 				setSelection(y);
4787 			}
4788 		}
4789 		super.defaultEventHandler_click(event);
4790 	}
4791 
4792 	this(Widget parent) {
4793 		tabStop = false;
4794 		super(parent);
4795 		version(win32_widgets)
4796 			createWin32Window(this, WC_LISTBOX, "",
4797 				0|WS_CHILD|WS_VISIBLE|LBS_NOTIFY, 0);
4798 	}
4799 
4800 	version(win32_widgets)
4801 	override void handleWmCommand(ushort code, ushort id) {
4802 		switch(code) {
4803 			case LBN_SELCHANGE:
4804 				auto sel = SendMessageW(hwnd, LB_GETCURSEL, 0, 0);
4805 				setSelection(cast(int) sel);
4806 			break;
4807 			default:
4808 		}
4809 	}
4810 
4811 
4812 	version(custom_widgets)
4813 	override void paintFrameAndBackground(WidgetPainter painter) {
4814 		draw3dFrame(this, painter, FrameStyle.sunk, painter.visualTheme.widgetBackgroundColor);
4815 	}
4816 
4817 	version(custom_widgets)
4818 	override void paint(WidgetPainter painter) {
4819 		auto cs = getComputedStyle();
4820 		auto pos = Point(4, 4);
4821 		foreach(idx, option; options) {
4822 			painter.fillColor = painter.visualTheme.widgetBackgroundColor;
4823 			painter.outlineColor = painter.visualTheme.widgetBackgroundColor;
4824 			painter.drawRectangle(pos, width - 8, defaultLineHeight);
4825 			if(option.selected) {
4826 				//painter.rasterOp = RasterOp.xor;
4827 				painter.outlineColor = cs.selectionForegroundColor;
4828 				painter.fillColor = cs.selectionBackgroundColor;
4829 				painter.drawRectangle(pos, width - 8, defaultLineHeight);
4830 				//painter.rasterOp = RasterOp.normal;
4831 			}
4832 			painter.outlineColor = option.selected ? cs.selectionForegroundColor : cs.foregroundColor;
4833 			painter.drawText(pos, option.label);
4834 			pos.y += defaultLineHeight;
4835 		}
4836 	}
4837 
4838 	static class Style : Widget.Style {
4839 		override WidgetBackground background() {
4840 			return WidgetBackground(WidgetPainter.visualTheme.widgetBackgroundColor);
4841 		}
4842 	}
4843 	mixin OverrideStyle!Style;
4844 	//mixin Padding!q{2};
4845 
4846 	void addOption(string text, void* tag = null) {
4847 		options ~= Option(text, false, tag);
4848 		version(win32_widgets) {
4849 			WCharzBuffer buffer = WCharzBuffer(text);
4850 			SendMessageW(hwnd, LB_ADDSTRING, 0, cast(LPARAM) buffer.ptr);
4851 		}
4852 		version(custom_widgets) {
4853 			setContentSize(width, cast(int) (options.length * defaultLineHeight));
4854 			redraw();
4855 		}
4856 	}
4857 
4858 	void clear() {
4859 		options = null;
4860 		version(win32_widgets) {
4861 			while(SendMessageW(hwnd, LB_DELETESTRING, 0, 0) > 0)
4862 				{}
4863 
4864 		} else version(custom_widgets) {
4865 			scrollTo(Point(0, 0));
4866 			redraw();
4867 		}
4868 	}
4869 
4870 	Option[] options;
4871 	version(win32_widgets)
4872 		enum multiSelect = false; /// not implemented yet
4873 	else
4874 		bool multiSelect;
4875 
4876 	override int heightStretchiness() { return 6; }
4877 }
4878 
4879 
4880 
4881 /// For [ScrollableWidget], determines when to show the scroll bar to the user.
4882 enum ScrollBarShowPolicy {
4883 	automatic, /// automatically show the scroll bar if it is necessary
4884 	never, /// never show the scroll bar (scrolling must be done programmatically)
4885 	always /// always show the scroll bar, even if it is disabled
4886 }
4887 
4888 /++
4889 	A widget that tries (with, at best, limited success) to offer scrolling that is transparent to the inner.
4890 
4891 	It isn't very good and will very likely be removed. Try [ScrollMessageWidget] or [ScrollableContainerWidget] instead for new code.
4892 +/
4893 // FIXME ScrollBarShowPolicy
4894 // FIXME: use the ScrollMessageWidget in here now that it exists
4895 class ScrollableWidget : Widget {
4896 	// FIXME: make line size configurable
4897 	// FIXME: add keyboard controls
4898 	version(win32_widgets) {
4899 		override int hookedWndProc(UINT msg, WPARAM wParam, LPARAM lParam) {
4900 			if(msg == WM_VSCROLL || msg == WM_HSCROLL) {
4901 				auto pos = HIWORD(wParam);
4902 				auto m = LOWORD(wParam);
4903 
4904 				// FIXME: I can reintroduce the
4905 				// scroll bars now by using this
4906 				// in the top-level window handler
4907 				// to forward comamnds
4908 				auto scrollbarHwnd = lParam;
4909 				switch(m) {
4910 					case SB_BOTTOM:
4911 						if(msg == WM_HSCROLL)
4912 							horizontalScrollTo(contentWidth_);
4913 						else
4914 							verticalScrollTo(contentHeight_);
4915 					break;
4916 					case SB_TOP:
4917 						if(msg == WM_HSCROLL)
4918 							horizontalScrollTo(0);
4919 						else
4920 							verticalScrollTo(0);
4921 					break;
4922 					case SB_ENDSCROLL:
4923 						// idk
4924 					break;
4925 					case SB_LINEDOWN:
4926 						if(msg == WM_HSCROLL)
4927 							horizontalScroll(scaleWithDpi(16));
4928 						else
4929 							verticalScroll(scaleWithDpi(16));
4930 					break;
4931 					case SB_LINEUP:
4932 						if(msg == WM_HSCROLL)
4933 							horizontalScroll(scaleWithDpi(-16));
4934 						else
4935 							verticalScroll(scaleWithDpi(-16));
4936 					break;
4937 					case SB_PAGEDOWN:
4938 						if(msg == WM_HSCROLL)
4939 							horizontalScroll(scaleWithDpi(100));
4940 						else
4941 							verticalScroll(scaleWithDpi(100));
4942 					break;
4943 					case SB_PAGEUP:
4944 						if(msg == WM_HSCROLL)
4945 							horizontalScroll(scaleWithDpi(-100));
4946 						else
4947 							verticalScroll(scaleWithDpi(-100));
4948 					break;
4949 					case SB_THUMBPOSITION:
4950 					case SB_THUMBTRACK:
4951 						if(msg == WM_HSCROLL)
4952 							horizontalScrollTo(pos);
4953 						else
4954 							verticalScrollTo(pos);
4955 
4956 						if(m == SB_THUMBTRACK) {
4957 							// the event loop doesn't seem to carry on with a requested redraw..
4958 							// so we request it to get our dirty bit set...
4959 							redraw();
4960 
4961 							// then we need to immediately actually redraw it too for instant feedback to user
4962 
4963 							SimpleWindow.processAllCustomEvents();
4964 							//if(parentWindow)
4965 								//parentWindow.actualRedraw();
4966 						}
4967 					break;
4968 					default:
4969 				}
4970 			}
4971 			return super.hookedWndProc(msg, wParam, lParam);
4972 		}
4973 	}
4974 	///
4975 	this(Widget parent) {
4976 		this.parentWindow = parent.parentWindow;
4977 
4978 		version(win32_widgets) {
4979 			createWin32Window(this, Win32Class!"arsd_minigui_ScrollableWidget"w, "",
4980 				0|WS_CHILD|WS_VISIBLE|WS_HSCROLL|WS_VSCROLL, 0);
4981 			super(parent);
4982 		} else version(custom_widgets) {
4983 			outerContainer = new InternalScrollableContainerWidget(this, parent);
4984 			super(outerContainer);
4985 		} else static assert(0);
4986 	}
4987 
4988 	version(custom_widgets)
4989 		InternalScrollableContainerWidget outerContainer;
4990 
4991 	override void defaultEventHandler_click(ClickEvent event) {
4992 		if(event.button == MouseButton.wheelUp)
4993 			verticalScroll(scaleWithDpi(-16));
4994 		if(event.button == MouseButton.wheelDown)
4995 			verticalScroll(scaleWithDpi(16));
4996 		super.defaultEventHandler_click(event);
4997 	}
4998 
4999 	override void defaultEventHandler_keydown(KeyDownEvent event) {
5000 		switch(event.key) {
5001 			case Key.Left:
5002 				horizontalScroll(scaleWithDpi(-16));
5003 			break;
5004 			case Key.Right:
5005 				horizontalScroll(scaleWithDpi(16));
5006 			break;
5007 			case Key.Up:
5008 				verticalScroll(scaleWithDpi(-16));
5009 			break;
5010 			case Key.Down:
5011 				verticalScroll(scaleWithDpi(16));
5012 			break;
5013 			case Key.Home:
5014 				verticalScrollTo(0);
5015 			break;
5016 			case Key.End:
5017 				verticalScrollTo(contentHeight);
5018 			break;
5019 			case Key.PageUp:
5020 				verticalScroll(scaleWithDpi(-160));
5021 			break;
5022 			case Key.PageDown:
5023 				verticalScroll(scaleWithDpi(160));
5024 			break;
5025 			default:
5026 		}
5027 		super.defaultEventHandler_keydown(event);
5028 	}
5029 
5030 
5031 	version(win32_widgets)
5032 	override void recomputeChildLayout() {
5033 		super.recomputeChildLayout();
5034 		SCROLLINFO info;
5035 		info.cbSize = info.sizeof;
5036 		info.nPage = viewportHeight;
5037 		info.fMask = SIF_PAGE | SIF_RANGE;
5038 		info.nMin = 0;
5039 		info.nMax = contentHeight_;
5040 		SetScrollInfo(hwnd, SB_VERT, &info, true);
5041 
5042 		info.cbSize = info.sizeof;
5043 		info.nPage = viewportWidth;
5044 		info.fMask = SIF_PAGE | SIF_RANGE;
5045 		info.nMin = 0;
5046 		info.nMax = contentWidth_;
5047 		SetScrollInfo(hwnd, SB_HORZ, &info, true);
5048 	}
5049 
5050 	/*
5051 		Scrolling
5052 		------------
5053 
5054 		You are assigned a width and a height by the layout engine, which
5055 		is your viewport box. However, you may draw more than that by setting
5056 		a contentWidth and contentHeight.
5057 
5058 		If these can be contained by the viewport, no scrollbar is displayed.
5059 		If they cannot fit though, it will automatically show scroll as necessary.
5060 
5061 		If contentWidth == 0, no horizontal scrolling is performed. If contentHeight
5062 		is zero, no vertical scrolling is performed.
5063 
5064 		If scrolling is necessary, the lib will automatically work with the bars.
5065 		When you redraw, the origin and clipping info in the painter is set so if
5066 		you just draw everything, it will work, but you can be more efficient by checking
5067 		the viewportWidth, viewportHeight, and scrollOrigin members.
5068 	*/
5069 
5070 	///
5071 	final @property int viewportWidth() {
5072 		return width - (showingVerticalScroll ? scaleWithDpi(16) : 0);
5073 	}
5074 	///
5075 	final @property int viewportHeight() {
5076 		return height - (showingHorizontalScroll ? scaleWithDpi(16) : 0);
5077 	}
5078 
5079 	// FIXME property
5080 	Point scrollOrigin_;
5081 
5082 	///
5083 	final const(Point) scrollOrigin() {
5084 		return scrollOrigin_;
5085 	}
5086 
5087 	// the user sets these two
5088 	private int contentWidth_ = 0;
5089 	private int contentHeight_ = 0;
5090 
5091 	///
5092 	int contentWidth() { return contentWidth_; }
5093 	///
5094 	int contentHeight() { return contentHeight_; }
5095 
5096 	///
5097 	void setContentSize(int width, int height) {
5098 		contentWidth_ = width;
5099 		contentHeight_ = height;
5100 
5101 		version(custom_widgets) {
5102 			if(showingVerticalScroll || showingHorizontalScroll) {
5103 				outerContainer.recomputeChildLayout();
5104 			}
5105 
5106 			if(showingVerticalScroll())
5107 				outerContainer.verticalScrollBar.redraw();
5108 			if(showingHorizontalScroll())
5109 				outerContainer.horizontalScrollBar.redraw();
5110 		} else version(win32_widgets) {
5111 			recomputeChildLayout();
5112 		} else static assert(0);
5113 	}
5114 
5115 	///
5116 	void verticalScroll(int delta) {
5117 		verticalScrollTo(scrollOrigin.y + delta);
5118 	}
5119 	///
5120 	void verticalScrollTo(int pos) {
5121 		scrollOrigin_.y = pos;
5122 		if(pos == int.max || (scrollOrigin_.y + viewportHeight > contentHeight))
5123 			scrollOrigin_.y = contentHeight - viewportHeight;
5124 
5125 		if(scrollOrigin_.y < 0)
5126 			scrollOrigin_.y = 0;
5127 
5128 		version(win32_widgets) {
5129 			SCROLLINFO info;
5130 			info.cbSize = info.sizeof;
5131 			info.fMask = SIF_POS;
5132 			info.nPos = scrollOrigin_.y;
5133 			SetScrollInfo(hwnd, SB_VERT, &info, true);
5134 		} else version(custom_widgets) {
5135 			outerContainer.verticalScrollBar.setPosition(scrollOrigin_.y);
5136 		} else static assert(0);
5137 
5138 		redraw();
5139 	}
5140 
5141 	///
5142 	void horizontalScroll(int delta) {
5143 		horizontalScrollTo(scrollOrigin.x + delta);
5144 	}
5145 	///
5146 	void horizontalScrollTo(int pos) {
5147 		scrollOrigin_.x = pos;
5148 		if(pos == int.max || (scrollOrigin_.x + viewportWidth > contentWidth))
5149 			scrollOrigin_.x = contentWidth - viewportWidth;
5150 
5151 		if(scrollOrigin_.x < 0)
5152 			scrollOrigin_.x = 0;
5153 
5154 		version(win32_widgets) {
5155 			SCROLLINFO info;
5156 			info.cbSize = info.sizeof;
5157 			info.fMask = SIF_POS;
5158 			info.nPos = scrollOrigin_.x;
5159 			SetScrollInfo(hwnd, SB_HORZ, &info, true);
5160 		} else version(custom_widgets) {
5161 			outerContainer.horizontalScrollBar.setPosition(scrollOrigin_.x);
5162 		} else static assert(0);
5163 
5164 		redraw();
5165 	}
5166 	///
5167 	void scrollTo(Point p) {
5168 		verticalScrollTo(p.y);
5169 		horizontalScrollTo(p.x);
5170 	}
5171 
5172 	///
5173 	void ensureVisibleInScroll(Point p) {
5174 		auto rect = viewportRectangle();
5175 		if(rect.contains(p))
5176 			return;
5177 		if(p.x < rect.left)
5178 			horizontalScroll(p.x - rect.left);
5179 		else if(p.x > rect.right)
5180 			horizontalScroll(p.x - rect.right);
5181 
5182 		if(p.y < rect.top)
5183 			verticalScroll(p.y - rect.top);
5184 		else if(p.y > rect.bottom)
5185 			verticalScroll(p.y - rect.bottom);
5186 	}
5187 
5188 	///
5189 	void ensureVisibleInScroll(Rectangle rect) {
5190 		ensureVisibleInScroll(rect.upperLeft);
5191 		ensureVisibleInScroll(rect.lowerRight);
5192 	}
5193 
5194 	///
5195 	Rectangle viewportRectangle() {
5196 		return Rectangle(scrollOrigin, Size(viewportWidth, viewportHeight));
5197 	}
5198 
5199 	///
5200 	bool showingHorizontalScroll() {
5201 		return contentWidth > width;
5202 	}
5203 	///
5204 	bool showingVerticalScroll() {
5205 		return contentHeight > height;
5206 	}
5207 
5208 	/// This is called before the ordinary paint delegate,
5209 	/// giving you a chance to draw the window frame, etc,
5210 	/// before the scroll clip takes effect
5211 	void paintFrameAndBackground(WidgetPainter painter) {
5212 		version(win32_widgets) {
5213 			auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
5214 			auto p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
5215 			// since the pen is null, to fill the whole space, we need the +1 on both.
5216 			gdi.Rectangle(painter.impl.hdc, 0, 0, this.width + 1, this.height + 1);
5217 			SelectObject(painter.impl.hdc, p);
5218 			SelectObject(painter.impl.hdc, b);
5219 		}
5220 
5221 	}
5222 
5223 	// make space for the scroll bar, and that's it.
5224 	final override int paddingRight() { return scaleWithDpi(16); }
5225 	final override int paddingBottom() { return scaleWithDpi(16); }
5226 
5227 	/*
5228 		END SCROLLING
5229 	*/
5230 
5231 	override WidgetPainter draw() {
5232 		int x = this.x, y = this.y;
5233 		auto parent = this.parent;
5234 		while(parent) {
5235 			x += parent.x;
5236 			y += parent.y;
5237 			parent = parent.parent;
5238 		}
5239 
5240 		//version(win32_widgets) {
5241 			//auto painter = simpleWindowWrappingHwnd ? simpleWindowWrappingHwnd.draw(true) : parentWindow.win.draw(true);
5242 		//} else {
5243 			auto painter = parentWindow.win.draw(true);
5244 		//}
5245 		painter.originX = x;
5246 		painter.originY = y;
5247 
5248 		painter.originX = painter.originX - scrollOrigin.x;
5249 		painter.originY = painter.originY - scrollOrigin.y;
5250 		painter.setClipRectangle(scrollOrigin, viewportWidth(), viewportHeight());
5251 
5252 		return WidgetPainter(painter, this);
5253 	}
5254 
5255 	mixin ScrollableChildren;
5256 }
5257 
5258 // you need to have a Point scrollOrigin in the class somewhere
5259 // and a paintFrameAndBackground
5260 private mixin template ScrollableChildren() {
5261 	override protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force, bool invalidate) {
5262 		if(hidden)
5263 			return;
5264 
5265 		//version(win32_widgets)
5266 			//painter = simpleWindowWrappingHwnd ? simpleWindowWrappingHwnd.draw(true) : parentWindow.win.draw(true);
5267 
5268 		painter.originX = lox + x;
5269 		painter.originY = loy + y;
5270 
5271 		bool actuallyPainted = false;
5272 
5273 		const clip = containment.intersectionOf(Rectangle(Point(lox + x, loy + y), Size(width, height)));
5274 		if(clip == Rectangle.init)
5275 			return;
5276 
5277 		if(force || redrawRequested) {
5278 			//painter.setClipRectangle(scrollOrigin, width, height);
5279 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
5280 			paintFrameAndBackground(painter);
5281 		}
5282 
5283 		painter.originX = painter.originX - scrollOrigin.x;
5284 		painter.originY = painter.originY - scrollOrigin.y;
5285 		if(force || redrawRequested) {
5286 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY) + Point(2, 2) /* border */, clip.width - 4, clip.height - 4);
5287 			//painter.setClipRectangle(scrollOrigin + Point(2, 2) /* border */, width - 4, height - 4);
5288 
5289 			//erase(painter); // we paintFrameAndBackground above so no need
5290 			if(painter.visualTheme)
5291 				painter.visualTheme.doPaint(this, painter);
5292 			else
5293 				paint(painter);
5294 
5295 			if(invalidate) {
5296 				painter.invalidateRect(Rectangle(Point(clip.upperLeft.x - painter.originX, clip.upperRight.y - painter.originY), Size(clip.width, clip.height)));
5297 				// children are contained inside this, so no need to do extra work
5298 				invalidate = false;
5299 			}
5300 
5301 
5302 			actuallyPainted = true;
5303 			redrawRequested = false;
5304 		}
5305 		foreach(child; children) {
5306 			if(cast(FixedPosition) child)
5307 				child.privatePaint(painter, painter.originX + scrollOrigin.x, painter.originY + scrollOrigin.y, clip, actuallyPainted, invalidate);
5308 			else
5309 				child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted, invalidate);
5310 		}
5311 	}
5312 }
5313 
5314 private class InternalScrollableContainerInsideWidget : ContainerWidget {
5315 	ScrollableContainerWidget scw;
5316 
5317 	this(ScrollableContainerWidget parent) {
5318 		scw = parent;
5319 		super(parent);
5320 	}
5321 
5322 	version(custom_widgets)
5323 	override protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force, bool invalidate) {
5324 		if(hidden)
5325 			return;
5326 
5327 		bool actuallyPainted = false;
5328 
5329 		auto scrollOrigin = Point(scw.scrollX_, scw.scrollY_);
5330 
5331 		const clip = containment.intersectionOf(Rectangle(Point(lox + x, loy + y), Size(width + scw.scrollX_, height + scw.scrollY_)));
5332 		if(clip == Rectangle.init)
5333 			return;
5334 
5335 		painter.originX = lox + x - scrollOrigin.x;
5336 		painter.originY = loy + y - scrollOrigin.y;
5337 		if(force || redrawRequested) {
5338 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
5339 
5340 			erase(painter);
5341 			if(painter.visualTheme)
5342 				painter.visualTheme.doPaint(this, painter);
5343 			else
5344 				paint(painter);
5345 
5346 			if(invalidate) {
5347 				painter.invalidateRect(Rectangle(Point(clip.upperLeft.x - painter.originX, clip.upperRight.y - painter.originY), Size(clip.width, clip.height)));
5348 				// children are contained inside this, so no need to do extra work
5349 				invalidate = false;
5350 			}
5351 
5352 			actuallyPainted = true;
5353 			redrawRequested = false;
5354 		}
5355 		foreach(child; children) {
5356 			if(cast(FixedPosition) child)
5357 				child.privatePaint(painter, painter.originX + scrollOrigin.x, painter.originY + scrollOrigin.y, clip, actuallyPainted, invalidate);
5358 			else
5359 				child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted, invalidate);
5360 		}
5361 	}
5362 
5363 	version(custom_widgets)
5364 	override protected void addScrollPosition(ref int x, ref int y) {
5365 		x += scw.scrollX_;
5366 		y += scw.scrollY_;
5367 	}
5368 }
5369 
5370 /++
5371 	A widget meant to contain other widgets that may need to scroll.
5372 
5373 	Currently buggy.
5374 
5375 	History:
5376 		Added July 1, 2021 (dub v10.2)
5377 
5378 		On January 3, 2022, I tried to use it in a few other cases
5379 		and found it only worked well in the original test case. Since
5380 		it still sucks, I think I'm going to rewrite it again.
5381 +/
5382 class ScrollableContainerWidget : ContainerWidget {
5383 	///
5384 	this(Widget parent) {
5385 		super(parent);
5386 
5387 		container = new InternalScrollableContainerInsideWidget(this);
5388 		hsb = new HorizontalScrollbar(this);
5389 		vsb = new VerticalScrollbar(this);
5390 
5391 		tabStop = false;
5392 		container.tabStop = false;
5393 		magic = true;
5394 
5395 
5396 		vsb.addEventListener("scrolltonextline", () {
5397 			scrollBy(0, scaleWithDpi(16));
5398 		});
5399 		vsb.addEventListener("scrolltopreviousline", () {
5400 			scrollBy(0,scaleWithDpi( -16));
5401 		});
5402 		vsb.addEventListener("scrolltonextpage", () {
5403 			scrollBy(0, container.height);
5404 		});
5405 		vsb.addEventListener("scrolltopreviouspage", () {
5406 			scrollBy(0, -container.height);
5407 		});
5408 		vsb.addEventListener((scope ScrollToPositionEvent spe) {
5409 			scrollTo(scrollX_, spe.value);
5410 		});
5411 
5412 		this.addEventListener(delegate (scope ClickEvent e) {
5413 			if(e.button == MouseButton.wheelUp) {
5414 				if(!e.defaultPrevented)
5415 					scrollBy(0, scaleWithDpi(-16));
5416 				e.stopPropagation();
5417 			} else if(e.button == MouseButton.wheelDown) {
5418 				if(!e.defaultPrevented)
5419 					scrollBy(0, scaleWithDpi(16));
5420 				e.stopPropagation();
5421 			}
5422 		});
5423 	}
5424 
5425 	/+
5426 	override void defaultEventHandler_click(ClickEvent e) {
5427 	}
5428 	+/
5429 
5430 	override void removeAllChildren() {
5431 		container.removeAllChildren();
5432 	}
5433 
5434 	void scrollTo(int x, int y) {
5435 		scrollBy(x - scrollX_, y - scrollY_);
5436 	}
5437 
5438 	void scrollBy(int x, int y) {
5439 		auto ox = scrollX_;
5440 		auto oy = scrollY_;
5441 
5442 		auto nx = ox + x;
5443 		auto ny = oy + y;
5444 
5445 		if(nx < 0)
5446 			nx = 0;
5447 		if(ny < 0)
5448 			ny = 0;
5449 
5450 		auto maxX = hsb.max - container.width;
5451 		if(maxX < 0) maxX = 0;
5452 		auto maxY = vsb.max - container.height;
5453 		if(maxY < 0) maxY = 0;
5454 
5455 		if(nx > maxX)
5456 			nx = maxX;
5457 		if(ny > maxY)
5458 			ny = maxY;
5459 
5460 		auto dx = nx - ox;
5461 		auto dy = ny - oy;
5462 
5463 		if(dx || dy) {
5464 			version(win32_widgets)
5465 				ScrollWindowEx(container.hwnd, -dx, -dy, null, null, null, null, SW_SCROLLCHILDREN | SW_INVALIDATE | SW_ERASE);
5466 			else {
5467 				redraw();
5468 			}
5469 
5470 			hsb.setPosition = nx;
5471 			vsb.setPosition = ny;
5472 
5473 			scrollX_ = nx;
5474 			scrollY_ = ny;
5475 		}
5476 	}
5477 
5478 	private int scrollX_;
5479 	private int scrollY_;
5480 
5481 	void setTotalArea(int width, int height) {
5482 		hsb.setMax(width);
5483 		vsb.setMax(height);
5484 	}
5485 
5486 	///
5487 	void setViewableArea(int width, int height) {
5488 		hsb.setViewableArea(width);
5489 		vsb.setViewableArea(height);
5490 	}
5491 
5492 	private bool magic;
5493 	override void addChild(Widget w, int position = int.max) {
5494 		if(magic)
5495 			container.addChild(w, position);
5496 		else
5497 			super.addChild(w, position);
5498 	}
5499 
5500 	override void recomputeChildLayout() {
5501 		if(hsb is null || vsb is null || container is null) return;
5502 
5503 		/+
5504 		writeln(x, " ", y , " ", width, " ", height);
5505 		writeln(this.ContainerWidget.minWidth(), "x", this.ContainerWidget.minHeight());
5506 		+/
5507 
5508 		registerMovement();
5509 
5510 		hsb.height = scaleWithDpi(16); // FIXME? are tese 16s sane?
5511 		hsb.x = 0;
5512 		hsb.y = this.height - hsb.height;
5513 		hsb.width = this.width - scaleWithDpi(16);
5514 		hsb.recomputeChildLayout();
5515 
5516 		vsb.width = scaleWithDpi(16); // FIXME?
5517 		vsb.x = this.width - vsb.width;
5518 		vsb.y = 0;
5519 		vsb.height = this.height - scaleWithDpi(16);
5520 		vsb.recomputeChildLayout();
5521 
5522 		container.x = 0;
5523 		container.y = 0;
5524 		container.width = this.width - vsb.width;
5525 		container.height = this.height - hsb.height;
5526 		container.recomputeChildLayout();
5527 
5528 		scrollX_ = 0;
5529 		scrollY_ = 0;
5530 
5531 		hsb.setPosition(0);
5532 		vsb.setPosition(0);
5533 
5534 		int mw, mh;
5535 		Widget c = container;
5536 		// FIXME: hack here to handle a layout inside...
5537 		if(c.children.length == 1 && cast(Layout) c.children[0])
5538 			c = c.children[0];
5539 		foreach(child; c.children) {
5540 			auto w = child.x + child.width;
5541 			auto h = child.y + child.height;
5542 
5543 			if(w > mw) mw = w;
5544 			if(h > mh) mh = h;
5545 		}
5546 
5547 		setTotalArea(mw, mh);
5548 		setViewableArea(width, height);
5549 	}
5550 
5551 	override int minHeight() { return scaleWithDpi(64); }
5552 
5553 	HorizontalScrollbar hsb;
5554 	VerticalScrollbar vsb;
5555 	ContainerWidget container;
5556 }
5557 
5558 
5559 version(custom_widgets)
5560 private class InternalScrollableContainerWidget : Widget {
5561 
5562 	ScrollableWidget sw;
5563 
5564 	VerticalScrollbar verticalScrollBar;
5565 	HorizontalScrollbar horizontalScrollBar;
5566 
5567 	this(ScrollableWidget sw, Widget parent) {
5568 		this.sw = sw;
5569 
5570 		this.tabStop = false;
5571 
5572 		super(parent);
5573 
5574 		horizontalScrollBar = new HorizontalScrollbar(this);
5575 		verticalScrollBar = new VerticalScrollbar(this);
5576 
5577 		horizontalScrollBar.showing_ = false;
5578 		verticalScrollBar.showing_ = false;
5579 
5580 		horizontalScrollBar.addEventListener("scrolltonextline", {
5581 			horizontalScrollBar.setPosition(horizontalScrollBar.position + 1);
5582 			sw.horizontalScrollTo(horizontalScrollBar.position);
5583 		});
5584 		horizontalScrollBar.addEventListener("scrolltopreviousline", {
5585 			horizontalScrollBar.setPosition(horizontalScrollBar.position - 1);
5586 			sw.horizontalScrollTo(horizontalScrollBar.position);
5587 		});
5588 		verticalScrollBar.addEventListener("scrolltonextline", {
5589 			verticalScrollBar.setPosition(verticalScrollBar.position + 1);
5590 			sw.verticalScrollTo(verticalScrollBar.position);
5591 		});
5592 		verticalScrollBar.addEventListener("scrolltopreviousline", {
5593 			verticalScrollBar.setPosition(verticalScrollBar.position - 1);
5594 			sw.verticalScrollTo(verticalScrollBar.position);
5595 		});
5596 		horizontalScrollBar.addEventListener("scrolltonextpage", {
5597 			horizontalScrollBar.setPosition(horizontalScrollBar.position + horizontalScrollBar.step_);
5598 			sw.horizontalScrollTo(horizontalScrollBar.position);
5599 		});
5600 		horizontalScrollBar.addEventListener("scrolltopreviouspage", {
5601 			horizontalScrollBar.setPosition(horizontalScrollBar.position - horizontalScrollBar.step_);
5602 			sw.horizontalScrollTo(horizontalScrollBar.position);
5603 		});
5604 		verticalScrollBar.addEventListener("scrolltonextpage", {
5605 			verticalScrollBar.setPosition(verticalScrollBar.position + verticalScrollBar.step_);
5606 			sw.verticalScrollTo(verticalScrollBar.position);
5607 		});
5608 		verticalScrollBar.addEventListener("scrolltopreviouspage", {
5609 			verticalScrollBar.setPosition(verticalScrollBar.position - verticalScrollBar.step_);
5610 			sw.verticalScrollTo(verticalScrollBar.position);
5611 		});
5612 		horizontalScrollBar.addEventListener("scrolltoposition", (Event event) {
5613 			horizontalScrollBar.setPosition(event.intValue);
5614 			sw.horizontalScrollTo(horizontalScrollBar.position);
5615 		});
5616 		verticalScrollBar.addEventListener("scrolltoposition", (Event event) {
5617 			verticalScrollBar.setPosition(event.intValue);
5618 			sw.verticalScrollTo(verticalScrollBar.position);
5619 		});
5620 		horizontalScrollBar.addEventListener("scrolltrack", (Event event) {
5621 			horizontalScrollBar.setPosition(event.intValue);
5622 			sw.horizontalScrollTo(horizontalScrollBar.position);
5623 		});
5624 		verticalScrollBar.addEventListener("scrolltrack", (Event event) {
5625 			verticalScrollBar.setPosition(event.intValue);
5626 		});
5627 	}
5628 
5629 	// this is supposed to be basically invisible...
5630 	override int minWidth() { return sw.minWidth; }
5631 	override int minHeight() { return sw.minHeight; }
5632 	override int maxWidth() { return sw.maxWidth; }
5633 	override int maxHeight() { return sw.maxHeight; }
5634 	override int widthStretchiness() { return sw.widthStretchiness; }
5635 	override int heightStretchiness() { return sw.heightStretchiness; }
5636 	override int marginLeft() { return sw.marginLeft; }
5637 	override int marginRight() { return sw.marginRight; }
5638 	override int marginTop() { return sw.marginTop; }
5639 	override int marginBottom() { return sw.marginBottom; }
5640 	override int paddingLeft() { return sw.paddingLeft; }
5641 	override int paddingRight() { return sw.paddingRight; }
5642 	override int paddingTop() { return sw.paddingTop; }
5643 	override int paddingBottom() { return sw.paddingBottom; }
5644 	override void focus() { sw.focus(); }
5645 
5646 
5647 	override void recomputeChildLayout() {
5648 		// The stupid thing needs to calculate if a scroll bar is needed...
5649 		recomputeChildLayoutHelper();
5650 		// then running it again will position things correctly if the bar is NOT needed
5651 		recomputeChildLayoutHelper();
5652 
5653 		// this sucks but meh it barely works
5654 	}
5655 
5656 	private void recomputeChildLayoutHelper() {
5657 		if(sw is null) return;
5658 
5659 		bool both = sw.showingVerticalScroll && sw.showingHorizontalScroll;
5660 		if(horizontalScrollBar && verticalScrollBar) {
5661 			horizontalScrollBar.width = this.width - (both ? verticalScrollBar.minWidth() : 0);
5662 			horizontalScrollBar.height = horizontalScrollBar.minHeight();
5663 			horizontalScrollBar.x = 0;
5664 			horizontalScrollBar.y = this.height - horizontalScrollBar.minHeight();
5665 
5666 			verticalScrollBar.width = verticalScrollBar.minWidth();
5667 			verticalScrollBar.height = this.height - (both ? horizontalScrollBar.minHeight() : 0) - 2 - 2;
5668 			verticalScrollBar.x = this.width - verticalScrollBar.minWidth();
5669 			verticalScrollBar.y = 0 + 2;
5670 
5671 			sw.x = 0;
5672 			sw.y = 0;
5673 			sw.width = this.width - (verticalScrollBar.showing ? verticalScrollBar.width : 0);
5674 			sw.height = this.height - (horizontalScrollBar.showing ? horizontalScrollBar.height : 0);
5675 
5676 			if(sw.contentWidth_ <= this.width)
5677 				sw.scrollOrigin_.x = 0;
5678 			if(sw.contentHeight_ <= this.height)
5679 				sw.scrollOrigin_.y = 0;
5680 
5681 			horizontalScrollBar.recomputeChildLayout();
5682 			verticalScrollBar.recomputeChildLayout();
5683 			sw.recomputeChildLayout();
5684 		}
5685 
5686 		if(sw.contentWidth_ <= this.width)
5687 			sw.scrollOrigin_.x = 0;
5688 		if(sw.contentHeight_ <= this.height)
5689 			sw.scrollOrigin_.y = 0;
5690 
5691 		if(sw.showingHorizontalScroll())
5692 			horizontalScrollBar.showing(true, false);
5693 		else
5694 			horizontalScrollBar.showing(false, false);
5695 		if(sw.showingVerticalScroll())
5696 			verticalScrollBar.showing(true, false);
5697 		else
5698 			verticalScrollBar.showing(false, false);
5699 
5700 		verticalScrollBar.setViewableArea(sw.viewportHeight());
5701 		verticalScrollBar.setMax(sw.contentHeight);
5702 		verticalScrollBar.setPosition(sw.scrollOrigin.y);
5703 
5704 		horizontalScrollBar.setViewableArea(sw.viewportWidth());
5705 		horizontalScrollBar.setMax(sw.contentWidth);
5706 		horizontalScrollBar.setPosition(sw.scrollOrigin.x);
5707 	}
5708 }
5709 
5710 /*
5711 class ScrollableClientWidget : Widget {
5712 	this(Widget parent) {
5713 		super(parent);
5714 	}
5715 	override void paint(WidgetPainter p) {
5716 		parent.paint(p);
5717 	}
5718 }
5719 */
5720 
5721 /++
5722 	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.
5723 +/
5724 abstract class Slider : Widget {
5725 	this(int min, int max, int step, Widget parent) {
5726 		min_ = min;
5727 		max_ = max;
5728 		step_ = step;
5729 		page_ = step;
5730 		super(parent);
5731 	}
5732 
5733 	private int min_;
5734 	private int max_;
5735 	private int step_;
5736 	private int position_;
5737 	private int page_;
5738 
5739 	// selection start and selection end
5740 	// tics
5741 	// tooltip?
5742 	// some way to see and just type the value
5743 	// win32 buddy controls are labels
5744 
5745 	///
5746 	void setMin(int a) {
5747 		min_ = a;
5748 		version(custom_widgets)
5749 			redraw();
5750 		version(win32_widgets)
5751 			SendMessage(hwnd, TBM_SETRANGEMIN, true, a);
5752 	}
5753 	///
5754 	int min() {
5755 		return min_;
5756 	}
5757 	///
5758 	void setMax(int a) {
5759 		max_ = a;
5760 		version(custom_widgets)
5761 			redraw();
5762 		version(win32_widgets)
5763 			SendMessage(hwnd, TBM_SETRANGEMAX, true, a);
5764 	}
5765 	///
5766 	int max() {
5767 		return max_;
5768 	}
5769 	///
5770 	void setPosition(int a) {
5771 		if(a > max)
5772 			a = max;
5773 		if(a < min)
5774 			a = min;
5775 		position_ = a;
5776 		version(custom_widgets)
5777 			setPositionCustom(a);
5778 
5779 		version(win32_widgets)
5780 			setPositionWindows(a);
5781 	}
5782 	version(win32_widgets) {
5783 		protected abstract void setPositionWindows(int a);
5784 	}
5785 
5786 	protected abstract int win32direction();
5787 
5788 	/++
5789 		Alias for [position] for better compatibility with generic code.
5790 
5791 		History:
5792 			Added October 5, 2021
5793 	+/
5794 	@property int value() {
5795 		return position;
5796 	}
5797 
5798 	///
5799 	int position() {
5800 		return position_;
5801 	}
5802 	///
5803 	void setStep(int a) {
5804 		step_ = a;
5805 		version(win32_widgets)
5806 			SendMessage(hwnd, TBM_SETLINESIZE, 0, a);
5807 	}
5808 	///
5809 	int step() {
5810 		return step_;
5811 	}
5812 	///
5813 	void setPageSize(int a) {
5814 		page_ = a;
5815 		version(win32_widgets)
5816 			SendMessage(hwnd, TBM_SETPAGESIZE, 0, a);
5817 	}
5818 	///
5819 	int pageSize() {
5820 		return page_;
5821 	}
5822 
5823 	private void notify() {
5824 		auto event = new ChangeEvent!int(this, &this.position);
5825 		event.dispatch();
5826 	}
5827 
5828 	version(win32_widgets)
5829 	void win32Setup(int style) {
5830 		createWin32Window(this, TRACKBAR_CLASS, "",
5831 			0|WS_CHILD|WS_VISIBLE|style|TBS_TOOLTIPS, 0);
5832 
5833 		// the trackbar sends the same messages as scroll, which
5834 		// our other layer sends as these... just gonna translate
5835 		// here
5836 		this.addDirectEventListener("scrolltoposition", (Event event) {
5837 			event.stopPropagation();
5838 			this.setPosition(this.win32direction > 0 ? event.intValue : max - event.intValue);
5839 			notify();
5840 		});
5841 		this.addDirectEventListener("scrolltonextline", (Event event) {
5842 			event.stopPropagation();
5843 			this.setPosition(this.position + this.step_ * this.win32direction);
5844 			notify();
5845 		});
5846 		this.addDirectEventListener("scrolltopreviousline", (Event event) {
5847 			event.stopPropagation();
5848 			this.setPosition(this.position - this.step_ * this.win32direction);
5849 			notify();
5850 		});
5851 		this.addDirectEventListener("scrolltonextpage", (Event event) {
5852 			event.stopPropagation();
5853 			this.setPosition(this.position + this.page_ * this.win32direction);
5854 			notify();
5855 		});
5856 		this.addDirectEventListener("scrolltopreviouspage", (Event event) {
5857 			event.stopPropagation();
5858 			this.setPosition(this.position - this.page_ * this.win32direction);
5859 			notify();
5860 		});
5861 
5862 		setMin(min_);
5863 		setMax(max_);
5864 		setStep(step_);
5865 		setPageSize(page_);
5866 	}
5867 
5868 	version(custom_widgets) {
5869 		protected MouseTrackingWidget thumb;
5870 
5871 		protected abstract void setPositionCustom(int a);
5872 
5873 		override void defaultEventHandler_keydown(KeyDownEvent event) {
5874 			switch(event.key) {
5875 				case Key.Up:
5876 				case Key.Right:
5877 					setPosition(position() - step() * win32direction);
5878 					changed();
5879 				break;
5880 				case Key.Down:
5881 				case Key.Left:
5882 					setPosition(position() + step() * win32direction);
5883 					changed();
5884 				break;
5885 				case Key.Home:
5886 					setPosition(win32direction > 0 ? min() : max());
5887 					changed();
5888 				break;
5889 				case Key.End:
5890 					setPosition(win32direction > 0 ? max() : min());
5891 					changed();
5892 				break;
5893 				case Key.PageUp:
5894 					setPosition(position() - pageSize() * win32direction);
5895 					changed();
5896 				break;
5897 				case Key.PageDown:
5898 					setPosition(position() + pageSize() * win32direction);
5899 					changed();
5900 				break;
5901 				default:
5902 			}
5903 			super.defaultEventHandler_keydown(event);
5904 		}
5905 
5906 		protected void changed() {
5907 			auto ev = new ChangeEvent!int(this, &position);
5908 			ev.dispatch();
5909 		}
5910 	}
5911 }
5912 
5913 /++
5914 
5915 +/
5916 class VerticalSlider : Slider {
5917 	this(int min, int max, int step, Widget parent) {
5918 		version(custom_widgets)
5919 			initialize();
5920 
5921 		super(min, max, step, parent);
5922 
5923 		version(win32_widgets)
5924 			win32Setup(TBS_VERT | 0x0200 /* TBS_REVERSED */);
5925 	}
5926 
5927 	protected override int win32direction() {
5928 		return -1;
5929 	}
5930 
5931 	version(win32_widgets)
5932 	protected override void setPositionWindows(int a) {
5933 		// the windows thing makes the top 0 and i don't like that.
5934 		SendMessage(hwnd, TBM_SETPOS, true, max - a);
5935 	}
5936 
5937 	version(custom_widgets)
5938 	private void initialize() {
5939 		thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.vertical, this);
5940 
5941 		thumb.tabStop = false;
5942 
5943 		thumb.thumbWidth = width;
5944 		thumb.thumbHeight = scaleWithDpi(16);
5945 
5946 		thumb.addEventListener(EventType.change, () {
5947 			auto sx = thumb.positionY * max() / (thumb.height - scaleWithDpi(16));
5948 			sx = max - sx;
5949 			//informProgramThatUserChangedPosition(sx);
5950 
5951 			position_ = sx;
5952 
5953 			changed();
5954 		});
5955 	}
5956 
5957 	version(custom_widgets)
5958 	override void recomputeChildLayout() {
5959 		thumb.thumbWidth = this.width;
5960 		super.recomputeChildLayout();
5961 		setPositionCustom(position_);
5962 	}
5963 
5964 	version(custom_widgets)
5965 	protected override void setPositionCustom(int a) {
5966 		if(max())
5967 			thumb.positionY = (max - a) * (thumb.height - scaleWithDpi(16)) / max();
5968 		redraw();
5969 	}
5970 }
5971 
5972 /++
5973 
5974 +/
5975 class HorizontalSlider : Slider {
5976 	this(int min, int max, int step, Widget parent) {
5977 		version(custom_widgets)
5978 			initialize();
5979 
5980 		super(min, max, step, parent);
5981 
5982 		version(win32_widgets)
5983 			win32Setup(TBS_HORZ);
5984 	}
5985 
5986 	version(win32_widgets)
5987 	protected override void setPositionWindows(int a) {
5988 		SendMessage(hwnd, TBM_SETPOS, true, a);
5989 	}
5990 
5991 	protected override int win32direction() {
5992 		return 1;
5993 	}
5994 
5995 	version(custom_widgets)
5996 	private void initialize() {
5997 		thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.horizontal, this);
5998 
5999 		thumb.tabStop = false;
6000 
6001 		thumb.thumbWidth = scaleWithDpi(16);
6002 		thumb.thumbHeight = height;
6003 
6004 		thumb.addEventListener(EventType.change, () {
6005 			auto sx = thumb.positionX * max() / (thumb.width - scaleWithDpi(16));
6006 			//informProgramThatUserChangedPosition(sx);
6007 
6008 			position_ = sx;
6009 
6010 			changed();
6011 		});
6012 	}
6013 
6014 	version(custom_widgets)
6015 	override void recomputeChildLayout() {
6016 		thumb.thumbHeight = this.height;
6017 		super.recomputeChildLayout();
6018 		setPositionCustom(position_);
6019 	}
6020 
6021 	version(custom_widgets)
6022 	protected override void setPositionCustom(int a) {
6023 		if(max())
6024 			thumb.positionX = a * (thumb.width - scaleWithDpi(16)) / max();
6025 		redraw();
6026 	}
6027 }
6028 
6029 
6030 ///
6031 abstract class ScrollbarBase : Widget {
6032 	///
6033 	this(Widget parent) {
6034 		super(parent);
6035 		tabStop = false;
6036 		step_ = scaleWithDpi(16);
6037 	}
6038 
6039 	private int viewableArea_;
6040 	private int max_;
6041 	private int step_;// = 16;
6042 	private int position_;
6043 
6044 	///
6045 	bool atEnd() {
6046 		return position_ + viewableArea_ >= max_;
6047 	}
6048 
6049 	///
6050 	bool atStart() {
6051 		return position_ == 0;
6052 	}
6053 
6054 	///
6055 	void setViewableArea(int a) {
6056 		viewableArea_ = a;
6057 		version(custom_widgets)
6058 			redraw();
6059 	}
6060 	///
6061 	void setMax(int a) {
6062 		max_ = a;
6063 		version(custom_widgets)
6064 			redraw();
6065 	}
6066 	///
6067 	int max() {
6068 		return max_;
6069 	}
6070 	///
6071 	void setPosition(int a) {
6072 		auto logicalMax = max_ - viewableArea_;
6073 		if(a == int.max)
6074 			a = logicalMax;
6075 
6076 		if(a > logicalMax)
6077 			a = logicalMax;
6078 		if(a < 0)
6079 			a = 0;
6080 
6081 		position_ = a;
6082 
6083 		version(custom_widgets)
6084 			redraw();
6085 	}
6086 	///
6087 	int position() {
6088 		return position_;
6089 	}
6090 	///
6091 	void setStep(int a) {
6092 		step_ = a;
6093 	}
6094 	///
6095 	int step() {
6096 		return step_;
6097 	}
6098 
6099 	// FIXME: remove this.... maybe
6100 	/+
6101 	protected void informProgramThatUserChangedPosition(int n) {
6102 		position_ = n;
6103 		auto evt = new Event(EventType.change, this);
6104 		evt.intValue = n;
6105 		evt.dispatch();
6106 	}
6107 	+/
6108 
6109 	version(custom_widgets) {
6110 		enum MIN_THUMB_SIZE = 8;
6111 
6112 		abstract protected int getBarDim();
6113 		int thumbSize() {
6114 			if(viewableArea_ >= max_ || max_ == 0)
6115 				return getBarDim();
6116 
6117 			int res = viewableArea_ * getBarDim() / max_;
6118 
6119 			if(res < scaleWithDpi(MIN_THUMB_SIZE))
6120 				res = scaleWithDpi(MIN_THUMB_SIZE);
6121 
6122 			return res;
6123 		}
6124 
6125 		int thumbPosition() {
6126 			/*
6127 				viewableArea_ is the viewport height/width
6128 				position_ is where we are
6129 			*/
6130 			//if(position_ + viewableArea_ >= max_)
6131 				//return getBarDim - thumbSize;
6132 
6133 			auto maximumPossibleValue = getBarDim() - thumbSize;
6134 			auto maximiumLogicalValue = max_ - viewableArea_;
6135 
6136 			auto p = (maximiumLogicalValue > 0) ? cast(int) (cast(long) position_ * maximumPossibleValue / maximiumLogicalValue) : 0;
6137 
6138 			return p;
6139 		}
6140 	}
6141 }
6142 
6143 //public import mgt;
6144 
6145 /++
6146 	A mouse tracking widget is one that follows the mouse when dragged inside it.
6147 
6148 	Concrete subclasses may include a scrollbar thumb and a volume control.
6149 +/
6150 //version(custom_widgets)
6151 class MouseTrackingWidget : Widget {
6152 
6153 	///
6154 	int positionX() { return positionX_; }
6155 	///
6156 	int positionY() { return positionY_; }
6157 
6158 	///
6159 	void positionX(int p) { positionX_ = p; }
6160 	///
6161 	void positionY(int p) { positionY_ = p; }
6162 
6163 	private int positionX_;
6164 	private int positionY_;
6165 
6166 	///
6167 	enum Orientation {
6168 		horizontal, ///
6169 		vertical, ///
6170 		twoDimensional, ///
6171 	}
6172 
6173 	private int thumbWidth_;
6174 	private int thumbHeight_;
6175 
6176 	///
6177 	int thumbWidth() { return thumbWidth_; }
6178 	///
6179 	int thumbHeight() { return thumbHeight_; }
6180 	///
6181 	int thumbWidth(int a) { return thumbWidth_ = a; }
6182 	///
6183 	int thumbHeight(int a) { return thumbHeight_ = a; }
6184 
6185 	private bool dragging;
6186 	private bool hovering;
6187 	private int startMouseX, startMouseY;
6188 
6189 	///
6190 	this(Orientation orientation, Widget parent) {
6191 		super(parent);
6192 
6193 		//assert(parentWindow !is null);
6194 
6195 		addEventListener((MouseDownEvent event) {
6196 			if(event.clientX >= positionX && event.clientX < positionX + thumbWidth && event.clientY >= positionY && event.clientY < positionY + thumbHeight) {
6197 				dragging = true;
6198 				startMouseX = event.clientX - positionX;
6199 				startMouseY = event.clientY - positionY;
6200 				parentWindow.captureMouse(this);
6201 			} else {
6202 				if(orientation == Orientation.horizontal || orientation == Orientation.twoDimensional)
6203 					positionX = event.clientX - thumbWidth / 2;
6204 				if(orientation == Orientation.vertical || orientation == Orientation.twoDimensional)
6205 					positionY = event.clientY - thumbHeight / 2;
6206 
6207 				if(positionX + thumbWidth > this.width)
6208 					positionX = this.width - thumbWidth;
6209 				if(positionY + thumbHeight > this.height)
6210 					positionY = this.height - thumbHeight;
6211 
6212 				if(positionX < 0)
6213 					positionX = 0;
6214 				if(positionY < 0)
6215 					positionY = 0;
6216 
6217 
6218 				// this.emit!(ChangeEvent!void)();
6219 				auto evt = new Event(EventType.change, this);
6220 				evt.sendDirectly();
6221 
6222 				redraw();
6223 
6224 			}
6225 		});
6226 
6227 		addEventListener(EventType.mouseup, (Event event) {
6228 			dragging = false;
6229 			parentWindow.releaseMouseCapture();
6230 		});
6231 
6232 		addEventListener(EventType.mouseout, (Event event) {
6233 			if(!hovering)
6234 				return;
6235 			hovering = false;
6236 			redraw();
6237 		});
6238 
6239 		int lpx, lpy;
6240 
6241 		addEventListener((MouseMoveEvent event) {
6242 			auto oh = hovering;
6243 			if(event.clientX >= positionX && event.clientX < positionX + thumbWidth && event.clientY >= positionY && event.clientY < positionY + thumbHeight) {
6244 				hovering = true;
6245 			} else {
6246 				hovering = false;
6247 			}
6248 			if(!dragging) {
6249 				if(hovering != oh)
6250 					redraw();
6251 				return;
6252 			}
6253 
6254 			if(orientation == Orientation.horizontal || orientation == Orientation.twoDimensional)
6255 				positionX = event.clientX - startMouseX; // FIXME: click could be in the middle of it
6256 			if(orientation == Orientation.vertical || orientation == Orientation.twoDimensional)
6257 				positionY = event.clientY - startMouseY;
6258 
6259 			if(positionX + thumbWidth > this.width)
6260 				positionX = this.width - thumbWidth;
6261 			if(positionY + thumbHeight > this.height)
6262 				positionY = this.height - thumbHeight;
6263 
6264 			if(positionX < 0)
6265 				positionX = 0;
6266 			if(positionY < 0)
6267 				positionY = 0;
6268 
6269 			if(positionX != lpx || positionY != lpy) {
6270 				lpx = positionX;
6271 				lpy = positionY;
6272 
6273 				auto evt = new Event(EventType.change, this);
6274 				evt.sendDirectly();
6275 			}
6276 
6277 			redraw();
6278 		});
6279 	}
6280 
6281 	version(custom_widgets)
6282 	override void paint(WidgetPainter painter) {
6283 		auto cs = getComputedStyle();
6284 		auto c = darken(cs.windowBackgroundColor, 0.2);
6285 		painter.outlineColor = c;
6286 		painter.fillColor = c;
6287 		painter.drawRectangle(Point(0, 0), this.width, this.height);
6288 
6289 		auto color = hovering ? cs.hoveringColor : cs.windowBackgroundColor;
6290 		draw3dFrame(positionX, positionY, thumbWidth, thumbHeight, painter, FrameStyle.risen, color);
6291 	}
6292 }
6293 
6294 //version(custom_widgets)
6295 //private
6296 class HorizontalScrollbar : ScrollbarBase {
6297 
6298 	version(custom_widgets) {
6299 		private MouseTrackingWidget thumb;
6300 
6301 		override int getBarDim() {
6302 			return thumb.width;
6303 		}
6304 	}
6305 
6306 	override void setViewableArea(int a) {
6307 		super.setViewableArea(a);
6308 
6309 		version(win32_widgets) {
6310 			SCROLLINFO info;
6311 			info.cbSize = info.sizeof;
6312 			info.nPage = a + 1;
6313 			info.fMask = SIF_PAGE;
6314 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6315 		} else version(custom_widgets) {
6316 			thumb.positionX = thumbPosition;
6317 			thumb.thumbWidth = thumbSize;
6318 			thumb.redraw();
6319 		} else static assert(0);
6320 
6321 	}
6322 
6323 	override void setMax(int a) {
6324 		super.setMax(a);
6325 		version(win32_widgets) {
6326 			SCROLLINFO info;
6327 			info.cbSize = info.sizeof;
6328 			info.nMin = 0;
6329 			info.nMax = max;
6330 			info.fMask = SIF_RANGE;
6331 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6332 		} else version(custom_widgets) {
6333 			thumb.positionX = thumbPosition;
6334 			thumb.thumbWidth = thumbSize;
6335 			thumb.redraw();
6336 		}
6337 	}
6338 
6339 	override void setPosition(int a) {
6340 		super.setPosition(a);
6341 		version(win32_widgets) {
6342 			SCROLLINFO info;
6343 			info.cbSize = info.sizeof;
6344 			info.fMask = SIF_POS;
6345 			info.nPos = position;
6346 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6347 		} else version(custom_widgets) {
6348 			thumb.positionX = thumbPosition();
6349 			thumb.thumbWidth = thumbSize;
6350 			thumb.redraw();
6351 		} else static assert(0);
6352 	}
6353 
6354 	this(Widget parent) {
6355 		super(parent);
6356 
6357 		version(win32_widgets) {
6358 			createWin32Window(this, "Scrollbar"w, "",
6359 				0|WS_CHILD|WS_VISIBLE|SBS_HORZ|SBS_BOTTOMALIGN, 0);
6360 		} else version(custom_widgets) {
6361 			auto vl = new HorizontalLayout(this);
6362 			auto leftButton = new ArrowButton(ArrowDirection.left, vl);
6363 			leftButton.setClickRepeat(scrollClickRepeatInterval);
6364 			thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.horizontal, vl);
6365 			auto rightButton = new ArrowButton(ArrowDirection.right, vl);
6366 			rightButton.setClickRepeat(scrollClickRepeatInterval);
6367 
6368 			leftButton.tabStop = false;
6369 			rightButton.tabStop = false;
6370 			thumb.tabStop = false;
6371 
6372 			leftButton.addEventListener(EventType.triggered, () {
6373 				this.emitCommand!"scrolltopreviousline"();
6374 				//informProgramThatUserChangedPosition(position - step());
6375 			});
6376 			rightButton.addEventListener(EventType.triggered, () {
6377 				this.emitCommand!"scrolltonextline"();
6378 				//informProgramThatUserChangedPosition(position + step());
6379 			});
6380 
6381 			thumb.thumbWidth = this.minWidth;
6382 			thumb.thumbHeight = scaleWithDpi(16);
6383 
6384 			thumb.addEventListener(EventType.change, () {
6385 				auto maximumPossibleValue = thumb.width - thumb.thumbWidth;
6386 				auto sx = maximumPossibleValue ? cast(int)(cast(long) thumb.positionX * (max()-viewableArea_) / maximumPossibleValue) : 0;
6387 
6388 				//informProgramThatUserChangedPosition(sx);
6389 
6390 				auto ev = new ScrollToPositionEvent(this, sx);
6391 				ev.dispatch();
6392 			});
6393 		}
6394 	}
6395 
6396 	override int minHeight() { return scaleWithDpi(16); }
6397 	override int maxHeight() { return scaleWithDpi(16); }
6398 	override int minWidth() { return scaleWithDpi(48); }
6399 }
6400 
6401 class ScrollToPositionEvent : Event {
6402 	enum EventString = "scrolltoposition";
6403 
6404 	this(Widget target, int value) {
6405 		this.value = value;
6406 		super(EventString, target);
6407 	}
6408 
6409 	immutable int value;
6410 
6411 	override @property int intValue() {
6412 		return value;
6413 	}
6414 }
6415 
6416 //version(custom_widgets)
6417 //private
6418 class VerticalScrollbar : ScrollbarBase {
6419 
6420 	version(custom_widgets) {
6421 		override int getBarDim() {
6422 			return thumb.height;
6423 		}
6424 
6425 		private MouseTrackingWidget thumb;
6426 	}
6427 
6428 	override void setViewableArea(int a) {
6429 		super.setViewableArea(a);
6430 
6431 		version(win32_widgets) {
6432 			SCROLLINFO info;
6433 			info.cbSize = info.sizeof;
6434 			info.nPage = a + 1;
6435 			info.fMask = SIF_PAGE;
6436 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6437 		} else version(custom_widgets) {
6438 			thumb.positionY = thumbPosition;
6439 			thumb.thumbHeight = thumbSize;
6440 			thumb.redraw();
6441 		} else static assert(0);
6442 
6443 	}
6444 
6445 	override void setMax(int a) {
6446 		super.setMax(a);
6447 		version(win32_widgets) {
6448 			SCROLLINFO info;
6449 			info.cbSize = info.sizeof;
6450 			info.nMin = 0;
6451 			info.nMax = max;
6452 			info.fMask = SIF_RANGE;
6453 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6454 		} else version(custom_widgets) {
6455 			thumb.positionY = thumbPosition;
6456 			thumb.thumbHeight = thumbSize;
6457 			thumb.redraw();
6458 		}
6459 	}
6460 
6461 	override void setPosition(int a) {
6462 		super.setPosition(a);
6463 		version(win32_widgets) {
6464 			SCROLLINFO info;
6465 			info.cbSize = info.sizeof;
6466 			info.fMask = SIF_POS;
6467 			info.nPos = position;
6468 			SetScrollInfo(hwnd, SB_CTL, &info, true);
6469 		} else version(custom_widgets) {
6470 			thumb.positionY = thumbPosition;
6471 			thumb.thumbHeight = thumbSize;
6472 			thumb.redraw();
6473 		} else static assert(0);
6474 	}
6475 
6476 	this(Widget parent) {
6477 		super(parent);
6478 
6479 		version(win32_widgets) {
6480 			createWin32Window(this, "Scrollbar"w, "",
6481 				0|WS_CHILD|WS_VISIBLE|SBS_VERT|SBS_RIGHTALIGN, 0);
6482 		} else version(custom_widgets) {
6483 			auto vl = new VerticalLayout(this);
6484 			auto upButton = new ArrowButton(ArrowDirection.up, vl);
6485 			upButton.setClickRepeat(scrollClickRepeatInterval);
6486 			thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.vertical, vl);
6487 			auto downButton = new ArrowButton(ArrowDirection.down, vl);
6488 			downButton.setClickRepeat(scrollClickRepeatInterval);
6489 
6490 			upButton.addEventListener(EventType.triggered, () {
6491 				this.emitCommand!"scrolltopreviousline"();
6492 				//informProgramThatUserChangedPosition(position - step());
6493 			});
6494 			downButton.addEventListener(EventType.triggered, () {
6495 				this.emitCommand!"scrolltonextline"();
6496 				//informProgramThatUserChangedPosition(position + step());
6497 			});
6498 
6499 			thumb.thumbWidth = this.minWidth;
6500 			thumb.thumbHeight = scaleWithDpi(16);
6501 
6502 			thumb.addEventListener(EventType.change, () {
6503 				auto maximumPossibleValue = thumb.height - thumb.thumbHeight;
6504 				auto sy = maximumPossibleValue ? cast(int) (cast(long) thumb.positionY * (max()-viewableArea_) / maximumPossibleValue) : 0;
6505 
6506 				auto ev = new ScrollToPositionEvent(this, sy);
6507 				ev.dispatch();
6508 
6509 				//informProgramThatUserChangedPosition(sy);
6510 			});
6511 
6512 			upButton.tabStop = false;
6513 			downButton.tabStop = false;
6514 			thumb.tabStop = false;
6515 		}
6516 	}
6517 
6518 	override int minWidth() { return scaleWithDpi(16); }
6519 	override int maxWidth() { return scaleWithDpi(16); }
6520 	override int minHeight() { return scaleWithDpi(48); }
6521 }
6522 
6523 
6524 /++
6525 	EXPERIMENTAL
6526 
6527 	A widget specialized for being a container for other widgets.
6528 
6529 	History:
6530 		Added May 29, 2021. Not stabilized at this time.
6531 +/
6532 class WidgetContainer : Widget {
6533 	this(Widget parent) {
6534 		tabStop = false;
6535 		super(parent);
6536 	}
6537 
6538 	override int maxHeight() {
6539 		if(this.children.length == 1) {
6540 			return saturatedSum(this.children[0].maxHeight, this.children[0].marginTop, this.children[0].marginBottom);
6541 		} else {
6542 			return int.max;
6543 		}
6544 	}
6545 
6546 	override int maxWidth() {
6547 		if(this.children.length == 1) {
6548 			return saturatedSum(this.children[0].maxWidth, this.children[0].marginLeft, this.children[0].marginRight);
6549 		} else {
6550 			return int.max;
6551 		}
6552 	}
6553 
6554 	/+
6555 
6556 	override int minHeight() {
6557 		int largest = 0;
6558 		int margins = 0;
6559 		int lastMargin = 0;
6560 		foreach(child; children) {
6561 			auto mh = child.minHeight();
6562 			if(mh > largest)
6563 				largest = mh;
6564 			margins += mymax(lastMargin, child.marginTop());
6565 			lastMargin = child.marginBottom();
6566 		}
6567 		return largest + margins;
6568 	}
6569 
6570 	override int maxHeight() {
6571 		int largest = 0;
6572 		int margins = 0;
6573 		int lastMargin = 0;
6574 		foreach(child; children) {
6575 			auto mh = child.maxHeight();
6576 			if(mh == int.max)
6577 				return int.max;
6578 			if(mh > largest)
6579 				largest = mh;
6580 			margins += mymax(lastMargin, child.marginTop());
6581 			lastMargin = child.marginBottom();
6582 		}
6583 		return largest + margins;
6584 	}
6585 
6586 	override int minWidth() {
6587 		int min;
6588 		foreach(child; children) {
6589 			auto cm = child.minWidth;
6590 			if(cm > min)
6591 				min = cm;
6592 		}
6593 		return min + paddingLeft + paddingRight;
6594 	}
6595 
6596 	override int minHeight() {
6597 		int min;
6598 		foreach(child; children) {
6599 			auto cm = child.minHeight;
6600 			if(cm > min)
6601 				min = cm;
6602 		}
6603 		return min + paddingTop + paddingBottom;
6604 	}
6605 
6606 	override int maxHeight() {
6607 		int largest = 0;
6608 		int margins = 0;
6609 		int lastMargin = 0;
6610 		foreach(child; children) {
6611 			auto mh = child.maxHeight();
6612 			if(mh == int.max)
6613 				return int.max;
6614 			if(mh > largest)
6615 				largest = mh;
6616 			margins += mymax(lastMargin, child.marginTop());
6617 			lastMargin = child.marginBottom();
6618 		}
6619 		return largest + margins;
6620 	}
6621 
6622 	override int heightStretchiness() {
6623 		int max;
6624 		foreach(child; children) {
6625 			auto c = child.heightStretchiness;
6626 			if(c > max)
6627 				max = c;
6628 		}
6629 		return max;
6630 	}
6631 
6632 	override int marginTop() {
6633 		if(this.children.length)
6634 			return this.children[0].marginTop;
6635 		return 0;
6636 	}
6637 	+/
6638 }
6639 
6640 ///
6641 abstract class Layout : Widget {
6642 	this(Widget parent) {
6643 		tabStop = false;
6644 		super(parent);
6645 	}
6646 }
6647 
6648 /++
6649 	Makes all children minimum width and height, placing them down
6650 	left to right, top to bottom.
6651 
6652 	Useful if you want to make a list of buttons that automatically
6653 	wrap to a new line when necessary.
6654 +/
6655 class InlineBlockLayout : Layout {
6656 	///
6657 	this(Widget parent) { super(parent); }
6658 
6659 	override void recomputeChildLayout() {
6660 		registerMovement();
6661 
6662 		int x = this.paddingLeft, y = this.paddingTop;
6663 
6664 		int lineHeight;
6665 		int previousMargin = 0;
6666 		int previousMarginBottom = 0;
6667 
6668 		foreach(child; children) {
6669 			if(child.hidden)
6670 				continue;
6671 			if(cast(FixedPosition) child) {
6672 				child.recomputeChildLayout();
6673 				continue;
6674 			}
6675 			child.width = child.flexBasisWidth();
6676 			if(child.width == 0)
6677 				child.width = child.minWidth();
6678 			if(child.width == 0)
6679 				child.width = 32;
6680 
6681 			child.height = child.flexBasisHeight();
6682 			if(child.height == 0)
6683 				child.height = child.minHeight();
6684 			if(child.height == 0)
6685 				child.height = 32;
6686 
6687 			if(x + child.width + paddingRight > this.width) {
6688 				x = this.paddingLeft;
6689 				y += lineHeight;
6690 				lineHeight = 0;
6691 				previousMargin = 0;
6692 				previousMarginBottom = 0;
6693 			}
6694 
6695 			auto margin = child.marginLeft;
6696 			if(previousMargin > margin)
6697 				margin = previousMargin;
6698 
6699 			x += margin;
6700 
6701 			child.x = x;
6702 			child.y = y;
6703 
6704 			int marginTopApplied;
6705 			if(child.marginTop > previousMarginBottom) {
6706 				child.y += child.marginTop;
6707 				marginTopApplied = child.marginTop;
6708 			}
6709 
6710 			x += child.width;
6711 			previousMargin = child.marginRight;
6712 
6713 			if(child.marginBottom > previousMarginBottom)
6714 				previousMarginBottom = child.marginBottom;
6715 
6716 			auto h = child.height + previousMarginBottom + marginTopApplied;
6717 			if(h > lineHeight)
6718 				lineHeight = h;
6719 
6720 			child.recomputeChildLayout();
6721 		}
6722 
6723 	}
6724 
6725 	override int minWidth() {
6726 		int min;
6727 		foreach(child; children) {
6728 			auto cm = child.minWidth;
6729 			if(cm > min)
6730 				min = cm;
6731 		}
6732 		return min + paddingLeft + paddingRight;
6733 	}
6734 
6735 	override int minHeight() {
6736 		int min;
6737 		foreach(child; children) {
6738 			auto cm = child.minHeight;
6739 			if(cm > min)
6740 				min = cm;
6741 		}
6742 		return min + paddingTop + paddingBottom;
6743 	}
6744 }
6745 
6746 /++
6747 	A TabMessageWidget is a clickable row of tabs followed by a content area, very similar
6748 	to the [TabWidget]. The difference is the TabMessageWidget only sends messages, whereas
6749 	the [TabWidget] will automatically change pages of child widgets.
6750 
6751 	This allows you to react to it however you see fit rather than having to
6752 	be tied to just the new sets of child widgets.
6753 
6754 	It sends the message in the form of `this.emitCommand!"changetab"();`.
6755 
6756 	History:
6757 		Added December 24, 2021 (dub v10.5)
6758 +/
6759 class TabMessageWidget : Widget {
6760 
6761 	protected void tabIndexClicked(int item) {
6762 		this.emitCommand!"changetab"();
6763 	}
6764 
6765 	/++
6766 		Adds the a new tab to the control with the given title.
6767 
6768 		Returns:
6769 			The index of the newly added tab. You will need to know
6770 			this index to refer to it later and to know which tab to
6771 			change to when you get a changetab message.
6772 	+/
6773 	int addTab(string title, int pos = int.max) {
6774 		version(win32_widgets) {
6775 			TCITEM item;
6776 			item.mask = TCIF_TEXT;
6777 			WCharzBuffer buf = WCharzBuffer(title);
6778 			item.pszText = buf.ptr;
6779 			return cast(int) SendMessage(hwnd, TCM_INSERTITEM, pos, cast(LPARAM) &item);
6780 		} else version(custom_widgets) {
6781 			if(pos >= tabs.length) {
6782 				tabs ~= title;
6783 				redraw();
6784 				return cast(int) tabs.length - 1;
6785 			} else if(pos <= 0) {
6786 				tabs = title ~ tabs;
6787 				redraw();
6788 				return 0;
6789 			} else {
6790 				tabs = tabs[0 .. pos] ~ title ~ title[pos .. $];
6791 				redraw();
6792 				return pos;
6793 			}
6794 		}
6795 	}
6796 
6797 	override void addChild(Widget child, int pos = int.max) {
6798 		if(container)
6799 			container.addChild(child, pos);
6800 		else
6801 			super.addChild(child, pos);
6802 	}
6803 
6804 	protected Widget makeContainer() {
6805 		return new Widget(this);
6806 	}
6807 
6808 	private Widget container;
6809 
6810 	override void recomputeChildLayout() {
6811 		version(win32_widgets) {
6812 			this.registerMovement();
6813 
6814 			RECT rect;
6815 			GetWindowRect(hwnd, &rect);
6816 
6817 			auto left = rect.left;
6818 			auto top = rect.top;
6819 
6820 			TabCtrl_AdjustRect(hwnd, false, &rect);
6821 			foreach(child; children) {
6822 				if(!child.showing) continue;
6823 				child.x = rect.left - left;
6824 				child.y = rect.top - top;
6825 				child.width = rect.right - rect.left;
6826 				child.height = rect.bottom - rect.top;
6827 				child.recomputeChildLayout();
6828 			}
6829 		} else version(custom_widgets) {
6830 			this.registerMovement();
6831 			foreach(child; children) {
6832 				if(!child.showing) continue;
6833 				child.x = 2;
6834 				child.y = tabBarHeight + 2; // for the border
6835 				child.width = width - 4; // for the border
6836 				child.height = height - tabBarHeight - 2 - 2; // for the border
6837 				child.recomputeChildLayout();
6838 			}
6839 		} else static assert(0);
6840 	}
6841 
6842 	version(custom_widgets)
6843 		string[] tabs;
6844 
6845 	this(Widget parent) {
6846 		super(parent);
6847 
6848 		tabStop = false;
6849 
6850 		version(win32_widgets) {
6851 			createWin32Window(this, WC_TABCONTROL, "", 0);
6852 		} else version(custom_widgets) {
6853 			addEventListener((ClickEvent event) {
6854 				if(event.target !is this)
6855 					return;
6856 				if(event.clientY >= 0 && event.clientY < tabBarHeight) {
6857 					auto t = (event.clientX / tabWidth);
6858 					if(t >= 0 && t < tabs.length) {
6859 						currentTab_ = t;
6860 						tabIndexClicked(t);
6861 						redraw();
6862 					}
6863 				}
6864 			});
6865 		} else static assert(0);
6866 
6867 		this.container = makeContainer();
6868 	}
6869 
6870 	override int marginTop() { return 4; }
6871 	override int paddingBottom() { return 4; }
6872 
6873 	override int minHeight() {
6874 		int max = 0;
6875 		foreach(child; children)
6876 			max = mymax(child.minHeight, max);
6877 
6878 
6879 		version(win32_widgets) {
6880 			RECT rect;
6881 			rect.right = this.width;
6882 			rect.bottom = max;
6883 			TabCtrl_AdjustRect(hwnd, true, &rect);
6884 
6885 			max = rect.bottom;
6886 		} else {
6887 			max += defaultLineHeight + 4;
6888 		}
6889 
6890 
6891 		return max;
6892 	}
6893 
6894 	version(win32_widgets)
6895 	override int handleWmNotify(NMHDR* hdr, int code, out int mustReturn) {
6896 		switch(code) {
6897 			case TCN_SELCHANGE:
6898 				auto sel = TabCtrl_GetCurSel(hwnd);
6899 				tabIndexClicked(sel);
6900 			break;
6901 			default:
6902 		}
6903 		return 0;
6904 	}
6905 
6906 	version(custom_widgets) {
6907 		private int currentTab_;
6908 		private int tabBarHeight() { return defaultLineHeight; }
6909 		int tabWidth() { return scaleWithDpi(80); }
6910 	}
6911 
6912 	version(win32_widgets)
6913 	override void paint(WidgetPainter painter) {}
6914 
6915 	version(custom_widgets)
6916 	override void paint(WidgetPainter painter) {
6917 		auto cs = getComputedStyle();
6918 
6919 		draw3dFrame(0, tabBarHeight - 2, width, height - tabBarHeight + 2, painter, FrameStyle.risen, cs.background.color);
6920 
6921 		int posX = 0;
6922 		foreach(idx, title; tabs) {
6923 			auto isCurrent = idx == getCurrentTab();
6924 
6925 			painter.setClipRectangle(Point(posX, 0), tabWidth, tabBarHeight);
6926 
6927 			draw3dFrame(posX, 0, tabWidth, tabBarHeight, painter, isCurrent ? FrameStyle.risen : FrameStyle.sunk, isCurrent ? cs.windowBackgroundColor : darken(cs.windowBackgroundColor, 0.1));
6928 			painter.outlineColor = cs.foregroundColor;
6929 			painter.drawText(Point(posX + 4, 2), title, Point(posX + tabWidth, tabBarHeight - 2), TextAlignment.VerticalCenter);
6930 
6931 			if(isCurrent) {
6932 				painter.outlineColor = cs.windowBackgroundColor;
6933 				painter.fillColor = Color.transparent;
6934 				painter.drawLine(Point(posX + 2, tabBarHeight - 1), Point(posX + tabWidth, tabBarHeight - 1));
6935 				painter.drawLine(Point(posX + 2, tabBarHeight - 2), Point(posX + tabWidth, tabBarHeight - 2));
6936 
6937 				painter.outlineColor = Color.white;
6938 				painter.drawPixel(Point(posX + 1, tabBarHeight - 1));
6939 				painter.drawPixel(Point(posX + 1, tabBarHeight - 2));
6940 				painter.outlineColor = cs.activeTabColor;
6941 				painter.drawPixel(Point(posX, tabBarHeight - 1));
6942 			}
6943 
6944 			posX += tabWidth - 2;
6945 		}
6946 	}
6947 
6948 	///
6949 	@scriptable
6950 	void setCurrentTab(int item) {
6951 		version(win32_widgets)
6952 			TabCtrl_SetCurSel(hwnd, item);
6953 		else version(custom_widgets)
6954 			currentTab_ = item;
6955 		else static assert(0);
6956 
6957 		tabIndexClicked(item);
6958 	}
6959 
6960 	///
6961 	@scriptable
6962 	int getCurrentTab() {
6963 		version(win32_widgets)
6964 			return TabCtrl_GetCurSel(hwnd);
6965 		else version(custom_widgets)
6966 			return currentTab_; // FIXME
6967 		else static assert(0);
6968 	}
6969 
6970 	///
6971 	@scriptable
6972 	void removeTab(int item) {
6973 		if(item && item == getCurrentTab())
6974 			setCurrentTab(item - 1);
6975 
6976 		version(win32_widgets) {
6977 			TabCtrl_DeleteItem(hwnd, item);
6978 		}
6979 
6980 		for(int a = item; a < children.length - 1; a++)
6981 			this._children[a] = this._children[a + 1];
6982 		this._children = this._children[0 .. $-1];
6983 	}
6984 
6985 }
6986 
6987 
6988 /++
6989 	A tab widget is a set of clickable tab buttons followed by a content area.
6990 
6991 
6992 	Tabs can change existing content or can be new pages.
6993 
6994 	When the user picks a different tab, a `change` message is generated.
6995 +/
6996 class TabWidget : TabMessageWidget {
6997 	this(Widget parent) {
6998 		super(parent);
6999 	}
7000 
7001 	override protected Widget makeContainer() {
7002 		return null;
7003 	}
7004 
7005 	override void addChild(Widget child, int pos = int.max) {
7006 		if(auto twp = cast(TabWidgetPage) child) {
7007 			Widget.addChild(child, pos);
7008 			if(pos == int.max)
7009 				pos = cast(int) this.children.length - 1;
7010 
7011 			super.addTab(twp.title, pos); // need to bypass the override here which would get into a loop...
7012 
7013 			if(pos != getCurrentTab) {
7014 				child.showing = false;
7015 			}
7016 		} else {
7017 			assert(0, "Don't add children directly to a tab widget, instead add them to a page (see addPage)");
7018 		}
7019 	}
7020 
7021 	// FIXME: add tab icons at some point, Windows supports them
7022 	/++
7023 		Adds a page and its associated tab with the given label to the widget.
7024 
7025 		Returns:
7026 			The added page object, to which you can add other widgets.
7027 	+/
7028 	@scriptable
7029 	TabWidgetPage addPage(string title) {
7030 		return new TabWidgetPage(title, this);
7031 	}
7032 
7033 	/++
7034 		Gets the page at the given tab index, or `null` if the index is bad.
7035 
7036 		History:
7037 			Added December 24, 2021.
7038 	+/
7039 	TabWidgetPage getPage(int index) {
7040 		if(index < this.children.length)
7041 			return null;
7042 		return cast(TabWidgetPage) this.children[index];
7043 	}
7044 
7045 	/++
7046 		While you can still use the addTab from the parent class,
7047 		*strongly* recommend you use [addPage] insteaad.
7048 
7049 		History:
7050 			Added December 24, 2021 to fulful the interface
7051 			requirement that came from adding [TabMessageWidget].
7052 
7053 			You should not use it though since the [addPage] function
7054 			is much easier to use here.
7055 	+/
7056 	override int addTab(string title, int pos = int.max) {
7057 		auto p = addPage(title);
7058 		foreach(idx, child; this.children)
7059 			if(child is p)
7060 				return cast(int) idx;
7061 		return -1;
7062 	}
7063 
7064 	protected override void tabIndexClicked(int item) {
7065 		foreach(idx, child; children) {
7066 			child.showing(false, false); // batch the recalculates for the end
7067 		}
7068 
7069 		foreach(idx, child; children) {
7070 			if(idx == item) {
7071 				child.showing(true, false);
7072 				if(parentWindow) {
7073 					auto f = parentWindow.getFirstFocusable(child);
7074 					if(f)
7075 						f.focus();
7076 				}
7077 				recomputeChildLayout();
7078 			}
7079 		}
7080 
7081 		version(win32_widgets) {
7082 			InvalidateRect(hwnd, null, true);
7083 		} else version(custom_widgets) {
7084 			this.redraw();
7085 		}
7086 	}
7087 
7088 }
7089 
7090 /++
7091 	A page widget is basically a tab widget with hidden tabs. It is also sometimes called a "StackWidget".
7092 
7093 	You add [TabWidgetPage]s to it.
7094 +/
7095 class PageWidget : Widget {
7096 	this(Widget parent) {
7097 		super(parent);
7098 	}
7099 
7100 	override int minHeight() {
7101 		int max = 0;
7102 		foreach(child; children)
7103 			max = mymax(child.minHeight, max);
7104 
7105 		return max;
7106 	}
7107 
7108 
7109 	override void addChild(Widget child, int pos = int.max) {
7110 		if(auto twp = cast(TabWidgetPage) child) {
7111 			super.addChild(child, pos);
7112 			if(pos == int.max)
7113 				pos = cast(int) this.children.length - 1;
7114 
7115 			if(pos != getCurrentTab) {
7116 				child.showing = false;
7117 			}
7118 		} else {
7119 			assert(0, "Don't add children directly to a page widget, instead add them to a page (see addPage)");
7120 		}
7121 	}
7122 
7123 	override void recomputeChildLayout() {
7124 		this.registerMovement();
7125 		foreach(child; children) {
7126 			child.x = 0;
7127 			child.y = 0;
7128 			child.width = width;
7129 			child.height = height;
7130 			child.recomputeChildLayout();
7131 		}
7132 	}
7133 
7134 	private int currentTab_;
7135 
7136 	///
7137 	@scriptable
7138 	void setCurrentTab(int item) {
7139 		currentTab_ = item;
7140 
7141 		showOnly(item);
7142 	}
7143 
7144 	///
7145 	@scriptable
7146 	int getCurrentTab() {
7147 		return currentTab_;
7148 	}
7149 
7150 	///
7151 	@scriptable
7152 	void removeTab(int item) {
7153 		if(item && item == getCurrentTab())
7154 			setCurrentTab(item - 1);
7155 
7156 		for(int a = item; a < children.length - 1; a++)
7157 			this._children[a] = this._children[a + 1];
7158 		this._children = this._children[0 .. $-1];
7159 	}
7160 
7161 	///
7162 	@scriptable
7163 	TabWidgetPage addPage(string title) {
7164 		return new TabWidgetPage(title, this);
7165 	}
7166 
7167 	private void showOnly(int item) {
7168 		foreach(idx, child; children)
7169 			if(idx == item) {
7170 				child.show();
7171 				child.recomputeChildLayout();
7172 			} else {
7173 				child.hide();
7174 			}
7175 	}
7176 
7177 }
7178 
7179 /++
7180 
7181 +/
7182 class TabWidgetPage : Widget {
7183 	string title;
7184 	this(string title, Widget parent) {
7185 		this.title = title;
7186 		this.tabStop = false;
7187 		super(parent);
7188 
7189 		///*
7190 		version(win32_widgets) {
7191 			createWin32Window(this, Win32Class!"arsd_minigui_TabWidgetPage"w, "", 0);
7192 		}
7193 		//*/
7194 	}
7195 
7196 	override int minHeight() {
7197 		int sum = 0;
7198 		foreach(child; children)
7199 			sum += child.minHeight();
7200 		return sum;
7201 	}
7202 }
7203 
7204 version(none)
7205 /++
7206 	A collapsable sidebar is a container that shows if its assigned width is greater than its minimum and otherwise shows as a button.
7207 
7208 	I think I need to modify the layout algorithms to support this.
7209 +/
7210 class CollapsableSidebar : Widget {
7211 
7212 }
7213 
7214 /// Stacks the widgets vertically, taking all the available width for each child.
7215 class VerticalLayout : Layout {
7216 	// most of this is intentionally blank - widget's default is vertical layout right now
7217 	///
7218 	this(Widget parent) { super(parent); }
7219 
7220 	/++
7221 		Sets a max width for the layout so you don't have to subclass. The max width
7222 		is in device-independent pixels, meaning pixels at 96 dpi that are auto-scaled.
7223 
7224 		History:
7225 			Added November 29, 2021 (dub v10.5)
7226 	+/
7227 	this(int maxWidth, Widget parent) {
7228 		this.mw = maxWidth;
7229 		super(parent);
7230 	}
7231 
7232 	private int mw = int.max;
7233 
7234 	override int maxWidth() { return scaleWithDpi(mw); }
7235 }
7236 
7237 /// Stacks the widgets horizontally, taking all the available height for each child.
7238 class HorizontalLayout : Layout {
7239 	///
7240 	this(Widget parent) { super(parent); }
7241 
7242 	/++
7243 		Sets a max height for the layout so you don't have to subclass. The max height
7244 		is in device-independent pixels, meaning pixels at 96 dpi that are auto-scaled.
7245 
7246 		History:
7247 			Added November 29, 2021 (dub v10.5)
7248 	+/
7249 	this(int maxHeight, Widget parent) {
7250 		this.mh = maxHeight;
7251 		super(parent);
7252 	}
7253 
7254 	private int mh = 0;
7255 
7256 
7257 
7258 	override void recomputeChildLayout() {
7259 		.recomputeChildLayout!"width"(this);
7260 	}
7261 
7262 	override int minHeight() {
7263 		int largest = 0;
7264 		int margins = 0;
7265 		int lastMargin = 0;
7266 		foreach(child; children) {
7267 			auto mh = child.minHeight();
7268 			if(mh > largest)
7269 				largest = mh;
7270 			margins += mymax(lastMargin, child.marginTop());
7271 			lastMargin = child.marginBottom();
7272 		}
7273 		return largest + margins;
7274 	}
7275 
7276 	override int maxHeight() {
7277 		if(mh != 0)
7278 			return mymax(minHeight, scaleWithDpi(mh));
7279 
7280 		int largest = 0;
7281 		int margins = 0;
7282 		int lastMargin = 0;
7283 		foreach(child; children) {
7284 			auto mh = child.maxHeight();
7285 			if(mh == int.max)
7286 				return int.max;
7287 			if(mh > largest)
7288 				largest = mh;
7289 			margins += mymax(lastMargin, child.marginTop());
7290 			lastMargin = child.marginBottom();
7291 		}
7292 		return largest + margins;
7293 	}
7294 
7295 	override int heightStretchiness() {
7296 		int max;
7297 		foreach(child; children) {
7298 			auto c = child.heightStretchiness;
7299 			if(c > max)
7300 				max = c;
7301 		}
7302 		return max;
7303 	}
7304 
7305 }
7306 
7307 version(win32_widgets)
7308 private
7309 extern(Windows)
7310 LRESULT DoubleBufferWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) nothrow {
7311 	Widget* pwin = hwnd in Widget.nativeMapping;
7312 	if(pwin is null)
7313 		return DefWindowProc(hwnd, message, wparam, lparam);
7314 	SimpleWindow win = pwin.simpleWindowWrappingHwnd;
7315 	if(win is null)
7316 		return DefWindowProc(hwnd, message, wparam, lparam);
7317 
7318 	switch(message) {
7319 		case WM_SIZE:
7320 			auto width = LOWORD(lparam);
7321 			auto height = HIWORD(lparam);
7322 
7323 			auto hdc = GetDC(hwnd);
7324 			auto hdcBmp = CreateCompatibleDC(hdc);
7325 
7326 			// FIXME: could this be more efficient? it never relinquishes a large bitmap
7327 			if(width > win.bmpWidth || height > win.bmpHeight) {
7328 				auto oldBuffer = win.buffer;
7329 				win.buffer = CreateCompatibleBitmap(hdc, width, height);
7330 
7331 				if(oldBuffer)
7332 					DeleteObject(oldBuffer);
7333 
7334 				win.bmpWidth = width;
7335 				win.bmpHeight = height;
7336 			}
7337 
7338 			// just always erase it upon resizing so minigui can draw over with a clean slate
7339 			auto oldBmp = SelectObject(hdcBmp, win.buffer);
7340 
7341 			auto brush = GetSysColorBrush(COLOR_3DFACE);
7342 			RECT r;
7343 			r.left = 0;
7344 			r.top = 0;
7345 			r.right = width;
7346 			r.bottom = height;
7347 			FillRect(hdcBmp, &r, brush);
7348 
7349 			SelectObject(hdcBmp, oldBmp);
7350 			DeleteDC(hdcBmp);
7351 			ReleaseDC(hwnd, hdc);
7352 		break;
7353 		case WM_PAINT:
7354 			if(win.buffer is null)
7355 				goto default;
7356 
7357 			BITMAP bm;
7358 			PAINTSTRUCT ps;
7359 
7360 			HDC hdc = BeginPaint(hwnd, &ps);
7361 
7362 			HDC hdcMem = CreateCompatibleDC(hdc);
7363 			HBITMAP hbmOld = SelectObject(hdcMem, win.buffer);
7364 
7365 			GetObject(win.buffer, bm.sizeof, &bm);
7366 
7367 			BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
7368 
7369 			SelectObject(hdcMem, hbmOld);
7370 			DeleteDC(hdcMem);
7371 			EndPaint(hwnd, &ps);
7372 		break;
7373 		default:
7374 			return DefWindowProc(hwnd, message, wparam, lparam);
7375 	}
7376 
7377 	return 0;
7378 }
7379 
7380 private wstring Win32Class(wstring name)() {
7381 	static bool classRegistered;
7382 	if(!classRegistered) {
7383 		HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
7384 		WNDCLASSEX wc;
7385 		wc.cbSize = wc.sizeof;
7386 		wc.hInstance = hInstance;
7387 		wc.hbrBackground = cast(HBRUSH) (COLOR_3DFACE+1); // GetStockObject(WHITE_BRUSH);
7388 		wc.lpfnWndProc = &DoubleBufferWndProc;
7389 		wc.lpszClassName = name.ptr;
7390 		if(!RegisterClassExW(&wc))
7391 			throw new Exception("RegisterClass ");// ~ to!string(GetLastError()));
7392 		classRegistered = true;
7393 	}
7394 
7395 		return name;
7396 }
7397 
7398 /+
7399 version(win32_widgets)
7400 extern(Windows)
7401 private
7402 LRESULT CustomDrawWindowProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
7403 	switch(iMessage) {
7404 		case WM_PAINT:
7405 			if(auto te = hWnd in Widget.nativeMapping) {
7406 				try {
7407 					//te.redraw();
7408 					writeln(te, " drawing");
7409 				} catch(Exception) {}
7410 			}
7411 			return DefWindowProc(hWnd, iMessage, wParam, lParam);
7412 		default:
7413 			return DefWindowProc(hWnd, iMessage, wParam, lParam);
7414 	}
7415 }
7416 +/
7417 
7418 
7419 /++
7420 	A widget specifically designed to hold other widgets.
7421 
7422 	History:
7423 		Added July 1, 2021
7424 +/
7425 class ContainerWidget : Widget {
7426 	this(Widget parent) {
7427 		super(parent);
7428 		this.tabStop = false;
7429 
7430 		version(win32_widgets) {
7431 			createWin32Window(this, Win32Class!"arsd_minigui_ContainerWidget"w, "", 0);
7432 		}
7433 	}
7434 }
7435 
7436 /++
7437 	A widget that takes your widget, puts scroll bars around it, and sends
7438 	messages to it when the user scrolls. Unlike [ScrollableWidget], it makes
7439 	no effort to automatically scroll or clip its child widgets - it just sends
7440 	the messages.
7441 
7442 
7443 	A ScrollMessageWidget notifies you with a [ScrollEvent] that it has changed.
7444 	The scroll coordinates are all given in a unit you interpret as you wish. One
7445 	of these units is moved on each press of the arrow buttons and represents the
7446 	smallest amount the user can scroll. The intention is for this to be one line,
7447 	one item in a list, one row in a table, etc. Whatever makes sense for your widget
7448 	in each direction that the user might be interested in.
7449 
7450 	You can set a "page size" with the [step] property. (Yes, I regret the name...)
7451 	This is the amount it jumps when the user pressed page up and page down, or clicks
7452 	in the exposed part of the scroll bar.
7453 
7454 	You should add child content to the ScrollMessageWidget. However, it is important to
7455 	note that the coordinates are always independent of the scroll position! It is YOUR
7456 	responsibility to do any necessary transforms, clipping, etc., while drawing the
7457 	content and interpreting mouse events if they are supposed to change with the scroll.
7458 	This is in contrast to the (likely to be deprecated) [ScrollableWidget], which tries
7459 	to maintain the illusion that there's an infinite space. The [ScrollMessageWidget] gives
7460 	you more control (which can be considerably more efficient and adapted to your actual data)
7461 	at the expense of you also needing to be aware of its reality.
7462 
7463 	Please note that it does NOT react to mouse wheel events or various keyboard events as of
7464 	version 10.3. Maybe this will change in the future.... but for now you must call
7465 	[addDefaultKeyboardListeners] and/or [addDefaultWheelListeners] or set something up yourself.
7466 +/
7467 class ScrollMessageWidget : Widget {
7468 	this(Widget parent) {
7469 		super(parent);
7470 
7471 		container = new Widget(this);
7472 		hsb = new HorizontalScrollbar(this);
7473 		vsb = new VerticalScrollbar(this);
7474 
7475 		hsb.addEventListener("scrolltonextline", {
7476 			hsb.setPosition(hsb.position + movementPerButtonClickH_);
7477 			notify();
7478 		});
7479 		hsb.addEventListener("scrolltopreviousline", {
7480 			hsb.setPosition(hsb.position - movementPerButtonClickH_);
7481 			notify();
7482 		});
7483 		vsb.addEventListener("scrolltonextline", {
7484 			vsb.setPosition(vsb.position + movementPerButtonClickV_);
7485 			notify();
7486 		});
7487 		vsb.addEventListener("scrolltopreviousline", {
7488 			vsb.setPosition(vsb.position - movementPerButtonClickV_);
7489 			notify();
7490 		});
7491 		hsb.addEventListener("scrolltonextpage", {
7492 			hsb.setPosition(hsb.position + hsb.step_);
7493 			notify();
7494 		});
7495 		hsb.addEventListener("scrolltopreviouspage", {
7496 			hsb.setPosition(hsb.position - hsb.step_);
7497 			notify();
7498 		});
7499 		vsb.addEventListener("scrolltonextpage", {
7500 			vsb.setPosition(vsb.position + vsb.step_);
7501 			notify();
7502 		});
7503 		vsb.addEventListener("scrolltopreviouspage", {
7504 			vsb.setPosition(vsb.position - vsb.step_);
7505 			notify();
7506 		});
7507 		hsb.addEventListener("scrolltoposition", (Event event) {
7508 			hsb.setPosition(event.intValue);
7509 			notify();
7510 		});
7511 		vsb.addEventListener("scrolltoposition", (Event event) {
7512 			vsb.setPosition(event.intValue);
7513 			notify();
7514 		});
7515 
7516 
7517 		tabStop = false;
7518 		container.tabStop = false;
7519 		magic = true;
7520 	}
7521 
7522 	private int movementPerButtonClickH_ = 1;
7523 	private int movementPerButtonClickV_ = 1;
7524 	public void movementPerButtonClick(int h, int v) {
7525 		movementPerButtonClickH_ = h;
7526 		movementPerButtonClickV_ = v;
7527 	}
7528 
7529 	/++
7530 		Add default event listeners for keyboard and mouse wheel scrolling shortcuts.
7531 
7532 
7533 		The defaults for [addDefaultWheelListeners] are:
7534 
7535 			$(LIST
7536 				* Mouse wheel scrolls vertically
7537 				* Alt key + mouse wheel scrolls horiontally
7538 				* Shift + mouse wheel scrolls faster.
7539 				* Any mouse click or wheel event will focus the inner widget if it has `tabStop = true`
7540 			)
7541 
7542 		The defaults for [addDefaultKeyboardListeners] are:
7543 
7544 			$(LIST
7545 				* Arrow keys scroll by the given amounts
7546 				* Shift+arrow keys scroll by the given amounts times the given shiftMultiplier
7547 				* Page up and down scroll by the vertical viewable area
7548 				* Home and end scroll to the start and end of the verticle viewable area.
7549 				* Alt + page up / page down / home / end will horizonally scroll instead of vertical.
7550 			)
7551 
7552 		My recommendation is to change the scroll amounts if you are scrolling by pixels, but otherwise keep them at one line.
7553 
7554 		Params:
7555 			horizontalArrowScrollAmount =
7556 			verticalArrowScrollAmount =
7557 			verticalWheelScrollAmount = how much should be scrolled vertically on each tick of the mouse wheel
7558 			horizontalWheelScrollAmount = how much should be scrolled horizontally when alt is held on each tick of the mouse wheel
7559 			shiftMultiplier = multiplies the scroll amount by this when shift is held
7560 	+/
7561 	void addDefaultKeyboardListeners(int verticalArrowScrollAmount = 1, int horizontalArrowScrollAmount = 1, int shiftMultiplier = 3) {
7562 		auto _this = this;
7563 
7564 		container.addEventListener((scope KeyDownEvent ke) {
7565 			switch(ke.key) {
7566 				case Key.Left:
7567 					_this.scrollLeft(horizontalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7568 				break;
7569 				case Key.Right:
7570 					_this.scrollRight(horizontalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7571 				break;
7572 				case Key.Up:
7573 					_this.scrollUp(verticalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7574 				break;
7575 				case Key.Down:
7576 					_this.scrollDown(verticalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
7577 				break;
7578 				case Key.PageUp:
7579 					if(ke.altKey)
7580 						_this.scrollLeft(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7581 					else
7582 						_this.scrollUp(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7583 				break;
7584 				case Key.PageDown:
7585 					if(ke.altKey)
7586 						_this.scrollRight(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7587 					else
7588 						_this.scrollDown(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
7589 				break;
7590 				case Key.Home:
7591 					if(ke.altKey)
7592 						_this.scrollLeft(short.max * 16);
7593 					else
7594 						_this.scrollUp(short.max * 16);
7595 				break;
7596 				case Key.End:
7597 					if(ke.altKey)
7598 						_this.scrollRight(short.max * 16);
7599 					else
7600 						_this.scrollDown(short.max * 16);
7601 				break;
7602 
7603 				default:
7604 					// ignore, not for us.
7605 			}
7606 
7607 		});
7608 	}
7609 
7610 	/// ditto
7611 	void addDefaultWheelListeners(int verticalWheelScrollAmount = 1, int horizontalWheelScrollAmount = 1, int shiftMultiplier = 3) {
7612 		auto _this = this;
7613 		container.addEventListener((scope ClickEvent ce) {
7614 
7615 			if(ce.target && ce.target.tabStop)
7616 				ce.target.focus();
7617 
7618 			// ctrl is reserved for the application
7619 			if(ce.ctrlKey)
7620 				return;
7621 
7622 			if(horizontalWheelScrollAmount == 0 && ce.altKey)
7623 				return;
7624 
7625 			if(shiftMultiplier == 0 && ce.shiftKey)
7626 				return;
7627 
7628 			if(ce.button == MouseButton.wheelDown) {
7629 				if(ce.altKey)
7630 					_this.scrollRight(horizontalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7631 				else
7632 					_this.scrollDown(verticalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7633 			} else if(ce.button == MouseButton.wheelUp) {
7634 				if(ce.altKey)
7635 					_this.scrollLeft(horizontalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7636 				else
7637 					_this.scrollUp(verticalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
7638 			}
7639 		});
7640 	}
7641 
7642 	/++
7643 		Scrolls the given amount.
7644 
7645 		History:
7646 			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.
7647 	+/
7648 	void scrollUp(int amount = 1) {
7649 		vsb.setPosition(vsb.position - amount);
7650 		notify();
7651 	}
7652 	/// ditto
7653 	void scrollDown(int amount = 1) {
7654 		vsb.setPosition(vsb.position + amount);
7655 		notify();
7656 	}
7657 	/// ditto
7658 	void scrollLeft(int amount = 1) {
7659 		hsb.setPosition(hsb.position - amount);
7660 		notify();
7661 	}
7662 	/// ditto
7663 	void scrollRight(int amount = 1) {
7664 		hsb.setPosition(hsb.position + amount);
7665 		notify();
7666 	}
7667 
7668 	///
7669 	VerticalScrollbar verticalScrollBar() { return vsb; }
7670 	///
7671 	HorizontalScrollbar horizontalScrollBar() { return hsb; }
7672 
7673 	void notify() {
7674 		static bool insideNotify;
7675 
7676 		if(insideNotify)
7677 			return; // avoid the recursive call, even if it isn't strictly correct
7678 
7679 		insideNotify = true;
7680 		scope(exit) insideNotify = false;
7681 
7682 		this.emit!ScrollEvent();
7683 	}
7684 
7685 	mixin Emits!ScrollEvent;
7686 
7687 	///
7688 	Point position() {
7689 		return Point(hsb.position, vsb.position);
7690 	}
7691 
7692 	///
7693 	void setPosition(int x, int y) {
7694 		hsb.setPosition(x);
7695 		vsb.setPosition(y);
7696 	}
7697 
7698 	///
7699 	void setPageSize(int unitsX, int unitsY) {
7700 		hsb.setStep(unitsX);
7701 		vsb.setStep(unitsY);
7702 	}
7703 
7704 	/// Always call this BEFORE setViewableArea
7705 	void setTotalArea(int width, int height) {
7706 		hsb.setMax(width);
7707 		vsb.setMax(height);
7708 	}
7709 
7710 	/++
7711 		Always set the viewable area AFTER setitng the total area if you are going to change both.
7712 		NEVER call this from inside a scroll event. This includes through recomputeChildLayout.
7713 		If you need to do that, use [queueRecomputeChildLayout].
7714 	+/
7715 	void setViewableArea(int width, int height) {
7716 
7717 		// actually there IS A need to dothis cuz the max might have changed since then
7718 		//if(width == hsb.viewableArea_ && height == vsb.viewableArea_)
7719 			//return; // no need to do what is already done
7720 		hsb.setViewableArea(width);
7721 		vsb.setViewableArea(height);
7722 
7723 		bool needsNotify = false;
7724 
7725 		// FIXME: if at any point the rhs is outside the scrollbar, we need
7726 		// to reset to 0. but it should remember the old position in case the
7727 		// window resizes again, so it can kinda return ot where it was.
7728 		//
7729 		// so there's an inner position and a exposed position. the exposed one is always in bounds and thus may be (0,0)
7730 		if(width >= hsb.max) {
7731 			// there's plenty of room to display it all so we need to reset to zero
7732 			// FIXME: adjust so it matches the note above
7733 			hsb.setPosition(0);
7734 			needsNotify = true;
7735 		}
7736 		if(height >= vsb.max) {
7737 			// there's plenty of room to display it all so we need to reset to zero
7738 			// FIXME: adjust so it matches the note above
7739 			vsb.setPosition(0);
7740 			needsNotify = true;
7741 		}
7742 		if(needsNotify)
7743 			notify();
7744 	}
7745 
7746 	private bool magic;
7747 	override void addChild(Widget w, int position = int.max) {
7748 		if(magic)
7749 			container.addChild(w, position);
7750 		else
7751 			super.addChild(w, position);
7752 	}
7753 
7754 	override void recomputeChildLayout() {
7755 		if(hsb is null || vsb is null || container is null) return;
7756 
7757 		registerMovement();
7758 
7759 		enum BUTTON_SIZE = 16;
7760 
7761 		hsb.height = scaleWithDpi(BUTTON_SIZE); // FIXME? are tese 16s sane?
7762 		hsb.x = 0;
7763 		hsb.y = this.height - hsb.height;
7764 
7765 		vsb.width = scaleWithDpi(BUTTON_SIZE); // FIXME?
7766 		vsb.x = this.width - vsb.width;
7767 		vsb.y = 0;
7768 
7769 		auto vsb_width = vsb.showing ? vsb.width : 0;
7770 		auto hsb_height = hsb.showing ? hsb.height : 0;
7771 
7772 		hsb.width = this.width - vsb_width;
7773 		vsb.height = this.height - hsb_height;
7774 
7775 		hsb.recomputeChildLayout();
7776 		vsb.recomputeChildLayout();
7777 
7778 		if(this.header is null) {
7779 			container.x = 0;
7780 			container.y = 0;
7781 			container.width = this.width - vsb_width;
7782 			container.height = this.height - hsb_height;
7783 			container.recomputeChildLayout();
7784 		} else {
7785 			header.x = 0;
7786 			header.y = 0;
7787 			header.width = this.width - vsb_width;
7788 			header.height = scaleWithDpi(BUTTON_SIZE); // size of the button
7789 			header.recomputeChildLayout();
7790 
7791 			container.x = 0;
7792 			container.y = scaleWithDpi(BUTTON_SIZE);
7793 			container.width = this.width - vsb_width;
7794 			container.height = this.height - hsb_height - scaleWithDpi(BUTTON_SIZE);
7795 			container.recomputeChildLayout();
7796 		}
7797 	}
7798 
7799 	private HorizontalScrollbar hsb;
7800 	private VerticalScrollbar vsb;
7801 	Widget container;
7802 	private Widget header;
7803 
7804 	/++
7805 		Adds a fixed-size "header" widget. This will be positioned to align with the scroll up button.
7806 
7807 		History:
7808 			Added September 27, 2021 (dub v10.3)
7809 	+/
7810 	Widget getHeader() {
7811 		if(this.header is null) {
7812 			magic = false;
7813 			scope(exit) magic = true;
7814 			this.header = new Widget(this);
7815 			recomputeChildLayout();
7816 		}
7817 		return this.header;
7818 	}
7819 
7820 	/++
7821 		Makes an effort to ensure as much of `rect` is visible as possible, scrolling if necessary.
7822 
7823 		History:
7824 			Added January 3, 2023 (dub v11.0)
7825 	+/
7826 	void scrollIntoView(Rectangle rect) {
7827 		Rectangle viewRectangle = Rectangle(position, Size(hsb.viewableArea_, vsb.viewableArea_));
7828 
7829 		// import std.stdio;writeln(viewRectangle, "\n", rect, " ", viewRectangle.contains(rect.lowerRight - Point(1, 1)));
7830 
7831 		// the lower right is exclusive normally
7832 		auto test = rect.lowerRight;
7833 		if(test.x > 0) test.x--;
7834 		if(test.y > 0) test.y--;
7835 
7836 		if(!viewRectangle.contains(test) || !viewRectangle.contains(rect.upperLeft)) {
7837 			// try to scroll only one dimension at a time if we can
7838 			if(!viewRectangle.contains(Point(test.x, position.y)) || !viewRectangle.contains(Point(rect.upperLeft.x, position.y)))
7839 				setPosition(rect.upperLeft.x, position.y);
7840 			if(!viewRectangle.contains(Point(position.x, test.y)) || !viewRectangle.contains(Point(position.x, rect.upperLeft.y)))
7841 				setPosition(position.x, rect.upperLeft.y);
7842 		}
7843 
7844 	}
7845 
7846 	override int minHeight() {
7847 		int min = mymax(container ? container.minHeight : 0, (verticalScrollBar.showing ? verticalScrollBar.minHeight : 0));
7848 		if(header !is null)
7849 			min += header.minHeight;
7850 		if(horizontalScrollBar.showing)
7851 			min += horizontalScrollBar.minHeight;
7852 		return min;
7853 	}
7854 
7855 	override int maxHeight() {
7856 		int max = container ? container.maxHeight : int.max;
7857 		if(max == int.max)
7858 			return max;
7859 		if(horizontalScrollBar.showing)
7860 			max += horizontalScrollBar.minHeight;
7861 		return max;
7862 	}
7863 }
7864 
7865 /++
7866 	$(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")
7867 	$(IMG //arsdnet.net/minigui-screenshots/linux/ScrollMessageWidget.png, Same thing, but in the default Linux theme.)
7868 +/
7869 version(minigui_screenshots)
7870 @Screenshot("ScrollMessageWidget")
7871 unittest {
7872 	auto window = new Window("ScrollMessageWidget");
7873 
7874 	auto smw = new ScrollMessageWidget(window);
7875 	smw.addDefaultKeyboardListeners();
7876 	smw.addDefaultWheelListeners();
7877 
7878 	window.loop();
7879 }
7880 
7881 /++
7882 	Bypasses automatic layout for its children, using manual positioning and sizing only.
7883 	While you need to manually position them, you must ensure they are inside the StaticLayout's
7884 	bounding box to avoid undefined behavior.
7885 
7886 	You should almost never use this.
7887 +/
7888 class StaticLayout : Layout {
7889 	///
7890 	this(Widget parent) { super(parent); }
7891 	override void recomputeChildLayout() {
7892 		registerMovement();
7893 		foreach(child; children)
7894 			child.recomputeChildLayout();
7895 	}
7896 }
7897 
7898 /++
7899 	Bypasses automatic positioning when being laid out. It is your responsibility to make
7900 	room for this widget in the parent layout.
7901 
7902 	Its children are laid out normally, unless there is exactly one, in which case it takes
7903 	on the full size of the `StaticPosition` object (if you plan to put stuff on the edge, you
7904 	can do that with `padding`).
7905 +/
7906 class StaticPosition : Layout {
7907 	///
7908 	this(Widget parent) { super(parent); }
7909 
7910 	override void recomputeChildLayout() {
7911 		registerMovement();
7912 		if(this.children.length == 1) {
7913 			auto child = children[0];
7914 			child.x = 0;
7915 			child.y = 0;
7916 			child.width = this.width;
7917 			child.height = this.height;
7918 			child.recomputeChildLayout();
7919 		} else
7920 		foreach(child; children)
7921 			child.recomputeChildLayout();
7922 	}
7923 
7924 	alias width = typeof(super).width;
7925 	alias height = typeof(super).height;
7926 
7927 	@property int width(int w) @nogc pure @safe nothrow {
7928 		return this._width = w;
7929 	}
7930 
7931 	@property int height(int w) @nogc pure @safe nothrow {
7932 		return this._height = w;
7933 	}
7934 
7935 }
7936 
7937 /++
7938 	FixedPosition is like [StaticPosition], but its coordinates
7939 	are always relative to the viewport, meaning they do not scroll with
7940 	the parent content.
7941 +/
7942 class FixedPosition : StaticPosition {
7943 	///
7944 	this(Widget parent) { super(parent); }
7945 }
7946 
7947 version(win32_widgets)
7948 int processWmCommand(HWND parentWindow, HWND handle, ushort cmd, ushort idm) {
7949 	if(true) {
7950 		// cmd == 0 = menu, cmd == 1 = accelerator
7951 		if(auto item = idm in Action.mapping) {
7952 			foreach(handler; (*item).triggered)
7953 				handler();
7954 		/*
7955 			auto event = new Event("triggered", *item);
7956 			event.button = idm;
7957 			event.dispatch();
7958 		*/
7959 			return 0;
7960 		}
7961 	}
7962 	if(handle)
7963 	if(auto widgetp = handle in Widget.nativeMapping) {
7964 		(*widgetp).handleWmCommand(cmd, idm);
7965 		return 0;
7966 	}
7967 	return 1;
7968 }
7969 
7970 
7971 ///
7972 class Window : Widget {
7973 	int mouseCaptureCount = 0;
7974 	Widget mouseCapturedBy;
7975 	void captureMouse(Widget byWhom) {
7976 		assert(mouseCapturedBy is null || byWhom is mouseCapturedBy);
7977 		mouseCaptureCount++;
7978 		mouseCapturedBy = byWhom;
7979 		win.grabInput();
7980 	}
7981 	void releaseMouseCapture() {
7982 		mouseCaptureCount--;
7983 		mouseCapturedBy = null;
7984 		win.releaseInputGrab();
7985 	}
7986 
7987 	/++
7988 		Sets the window icon which is often seen in title bars and taskbars.
7989 
7990 		History:
7991 			Added April 5, 2022 (dub v10.8)
7992 	+/
7993 	@property void icon(MemoryImage icon) {
7994 		if(win && icon)
7995 			win.icon = icon;
7996 	}
7997 
7998 	///
7999 	@scriptable
8000 	@property bool focused() {
8001 		return win.focused;
8002 	}
8003 
8004 	static class Style : Widget.Style {
8005 		override WidgetBackground background() {
8006 			version(custom_widgets)
8007 				return WidgetBackground(WidgetPainter.visualTheme.windowBackgroundColor);
8008 			else version(win32_widgets)
8009 				return WidgetBackground(Color.transparent);
8010 			else static assert(0);
8011 		}
8012 	}
8013 	mixin OverrideStyle!Style;
8014 
8015 	/++
8016 		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.
8017 	+/
8018 	deprecated("Use the non-static Widget.defaultLineHeight() instead") static int lineHeight() {
8019 		return lineHeightNotDeprecatedButShouldBeSinceItIsJustAFallback();
8020 	}
8021 
8022 	private static int lineHeightNotDeprecatedButShouldBeSinceItIsJustAFallback() {
8023 		OperatingSystemFont font;
8024 		if(auto vt = WidgetPainter.visualTheme) {
8025 			font = vt.defaultFontCached(96); // FIXME
8026 		}
8027 
8028 		if(font is null) {
8029 			static int defaultHeightCache;
8030 			if(defaultHeightCache == 0) {
8031 				font = new OperatingSystemFont;
8032 				font.loadDefault;
8033 				defaultHeightCache = font.height();// * 5 / 4;
8034 			}
8035 			return defaultHeightCache;
8036 		}
8037 
8038 		return font.height();// * 5 / 4;
8039 	}
8040 
8041 	Widget focusedWidget;
8042 
8043 	private SimpleWindow win_;
8044 
8045 	@property {
8046 		/++
8047 			Provides access to the underlying [SimpleWindow]. Note that changing properties on this window may disconnect minigui's event dispatchers.
8048 
8049 			History:
8050 				Prior to June 21, 2021, it was a public (but undocumented) member. Now it a semi-protected property.
8051 		+/
8052 		public SimpleWindow win() {
8053 			return win_;
8054 		}
8055 		///
8056 		protected void win(SimpleWindow w) {
8057 			win_ = w;
8058 		}
8059 	}
8060 
8061 	/// YOU ALMOST CERTAINLY SHOULD NOT USE THIS. This is really only for special purposes like pseudowindows or popup windows doing their own thing.
8062 	this(Widget p) {
8063 		tabStop = false;
8064 		super(p);
8065 	}
8066 
8067 	private void actualRedraw() {
8068 		if(recomputeChildLayoutRequired)
8069 			recomputeChildLayoutEntry();
8070 		if(!showing) return;
8071 
8072 		assert(parentWindow !is null);
8073 
8074 		auto w = drawableWindow;
8075 		if(w is null)
8076 			w = parentWindow.win;
8077 
8078 		if(w.closed())
8079 			return;
8080 
8081 		auto ugh = this.parent;
8082 		int lox, loy;
8083 		while(ugh) {
8084 			lox += ugh.x;
8085 			loy += ugh.y;
8086 			ugh = ugh.parent;
8087 		}
8088 		auto painter = w.draw(true);
8089 		privatePaint(WidgetPainter(painter, this), lox, loy, Rectangle(0, 0, int.max, int.max), false, willDraw());
8090 		// RedrawWindow(hwnd, null, null, RDW_ERASE | RDW_INVALIDATE | RDW_ALLCHILDREN);
8091 	}
8092 
8093 
8094 	private bool skipNextChar = false;
8095 
8096 	/++
8097 		Creates a window from an existing [SimpleWindow]. This constructor attaches various event handlers to the SimpleWindow object which may overwrite your existing handlers.
8098 
8099 		This constructor is intended primarily for internal use and may be changed to `protected` later.
8100 	+/
8101 	this(SimpleWindow win) {
8102 
8103 		static if(UsingSimpledisplayX11) {
8104 			win.discardAdditionalConnectionState = &discardXConnectionState;
8105 			win.recreateAdditionalConnectionState = &recreateXConnectionState;
8106 		}
8107 
8108 		tabStop = false;
8109 		super(null);
8110 		this.win = win;
8111 
8112 		win.addEventListener((Widget.RedrawEvent) {
8113 			if(win.eventQueued!RecomputeEvent) {
8114 				// writeln("skipping");
8115 				return; // let the recompute event do the actual redraw
8116 			}
8117 			this.actualRedraw();
8118 		});
8119 
8120 		win.addEventListener((Widget.RecomputeEvent) {
8121 			recomputeChildLayoutEntry();
8122 			if(win.eventQueued!RedrawEvent)
8123 				return; // let the queued one do it
8124 			else {
8125 				// writeln("drawing");
8126 				this.actualRedraw(); // if not queued, it needs to be done now anyway
8127 			}
8128 		});
8129 
8130 		this.width = win.width;
8131 		this.height = win.height;
8132 		this.parentWindow = this;
8133 
8134 		win.closeQuery = () {
8135 			if(this.emit!ClosingEvent())
8136 				win.close();
8137 		};
8138 		win.onClosing = () {
8139 			this.emit!ClosedEvent();
8140 		};
8141 
8142 		win.windowResized = (int w, int h) {
8143 			this.width = w;
8144 			this.height = h;
8145 			recomputeChildLayout();
8146 			// this causes a HUGE performance problem for no apparent benefit, hence the commenting
8147 			//version(win32_widgets)
8148 				//InvalidateRect(hwnd, null, true);
8149 			redraw();
8150 		};
8151 
8152 		win.onFocusChange = (bool getting) {
8153 			if(this.focusedWidget) {
8154 				if(getting) {
8155 					this.focusedWidget.emit!FocusEvent();
8156 					this.focusedWidget.emit!FocusInEvent();
8157 				} else {
8158 					this.focusedWidget.emit!BlurEvent();
8159 					this.focusedWidget.emit!FocusOutEvent();
8160 				}
8161 			}
8162 
8163 			if(getting) {
8164 				this.emit!FocusEvent();
8165 				this.emit!FocusInEvent();
8166 			} else {
8167 				this.emit!BlurEvent();
8168 				this.emit!FocusOutEvent();
8169 			}
8170 		};
8171 
8172 		win.onDpiChanged = {
8173 			this.queueRecomputeChildLayout();
8174 			auto event = new DpiChangedEvent(this);
8175 			event.sendDirectly();
8176 
8177 			privateDpiChanged();
8178 		};
8179 
8180 		win.setEventHandlers(
8181 			(MouseEvent e) {
8182 				dispatchMouseEvent(e);
8183 			},
8184 			(KeyEvent e) {
8185 				//writefln("%x   %s", cast(uint) e.key, e.key);
8186 				dispatchKeyEvent(e);
8187 			},
8188 			(dchar e) {
8189 				if(e == 13) e = 10; // hack?
8190 				if(e == 127) return; // linux sends this, windows doesn't. we don't want it.
8191 				dispatchCharEvent(e);
8192 			},
8193 		);
8194 
8195 		addEventListener("char", (Widget, Event ev) {
8196 			if(skipNextChar) {
8197 				ev.preventDefault();
8198 				skipNextChar = false;
8199 			}
8200 		});
8201 
8202 		version(win32_widgets)
8203 		win.handleNativeEvent = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
8204 			if(hwnd !is this.win.impl.hwnd)
8205 				return 1; // we don't care... pass it on
8206 			auto ret = WindowProcedureHelper(this, hwnd, msg, wParam, lParam, mustReturn);
8207 			if(mustReturn)
8208 				return ret;
8209 			return 1; // pass it on
8210 		};
8211 
8212 		if(Window.newWindowCreated)
8213 			Window.newWindowCreated(this);
8214 	}
8215 
8216 	version(custom_widgets)
8217 	override void defaultEventHandler_click(ClickEvent event) {
8218 		if(event.target && event.target.tabStop)
8219 			event.target.focus();
8220 	}
8221 
8222 	private static void delegate(Window) newWindowCreated;
8223 
8224 	version(win32_widgets)
8225 	override void paint(WidgetPainter painter) {
8226 		/*
8227 		RECT rect;
8228 		rect.right = this.width;
8229 		rect.bottom = this.height;
8230 		DrawThemeBackground(theme, painter.impl.hdc, 4, 1, &rect, null);
8231 		*/
8232 		// 3dface is used as window backgrounds by Windows too, so that's why I'm using it here
8233 		auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
8234 		auto p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
8235 		// since the pen is null, to fill the whole space, we need the +1 on both.
8236 		gdi.Rectangle(painter.impl.hdc, 0, 0, this.width + 1, this.height + 1);
8237 		SelectObject(painter.impl.hdc, p);
8238 		SelectObject(painter.impl.hdc, b);
8239 	}
8240 	version(custom_widgets)
8241 	override void paint(WidgetPainter painter) {
8242 		auto cs = getComputedStyle();
8243 		painter.fillColor = cs.windowBackgroundColor;
8244 		painter.outlineColor = cs.windowBackgroundColor;
8245 		painter.drawRectangle(Point(0, 0), this.width, this.height);
8246 	}
8247 
8248 
8249 	override void defaultEventHandler_keydown(KeyDownEvent event) {
8250 		Widget _this = event.target;
8251 
8252 		if(event.key == Key.Tab) {
8253 			/* Window tab ordering is a recursive thingy with each group */
8254 
8255 			// FIXME inefficient
8256 			Widget[] helper(Widget p) {
8257 				if(p.hidden)
8258 					return null;
8259 				Widget[] childOrdering;
8260 
8261 				auto children = p.children.dup;
8262 
8263 				while(true) {
8264 					// UIs should be generally small, so gonna brute force it a little
8265 					// note that it must be a stable sort here; if all are index 0, it should be in order of declaration
8266 
8267 					Widget smallestTab;
8268 					foreach(ref c; children) {
8269 						if(c is null) continue;
8270 						if(smallestTab is null || c.tabOrder < smallestTab.tabOrder) {
8271 							smallestTab = c;
8272 							c = null;
8273 						}
8274 					}
8275 					if(smallestTab !is null) {
8276 						if(smallestTab.tabStop && !smallestTab.hidden)
8277 							childOrdering ~= smallestTab;
8278 						if(!smallestTab.hidden)
8279 							childOrdering ~= helper(smallestTab);
8280 					} else
8281 						break;
8282 
8283 				}
8284 
8285 				return childOrdering;
8286 			}
8287 
8288 			Widget[] tabOrdering = helper(this);
8289 
8290 			Widget recipient;
8291 
8292 			if(tabOrdering.length) {
8293 				bool seenThis = false;
8294 				Widget previous;
8295 				foreach(idx, child; tabOrdering) {
8296 					if(child is focusedWidget) {
8297 
8298 						if(event.shiftKey) {
8299 							if(idx == 0)
8300 								recipient = tabOrdering[$-1];
8301 							else
8302 								recipient = tabOrdering[idx - 1];
8303 							break;
8304 						}
8305 
8306 						seenThis = true;
8307 						if(idx + 1 == tabOrdering.length) {
8308 							// we're at the end, either move to the next group
8309 							// or start back over
8310 							recipient = tabOrdering[0];
8311 						}
8312 						continue;
8313 					}
8314 					if(seenThis) {
8315 						recipient = child;
8316 						break;
8317 					}
8318 					previous = child;
8319 				}
8320 			}
8321 
8322 			if(recipient !is null) {
8323 				//  writeln(typeid(recipient));
8324 				recipient.focus();
8325 
8326 				skipNextChar = true;
8327 			}
8328 		}
8329 
8330 		debug if(event.key == Key.F12) {
8331 			if(devTools) {
8332 				devTools.close();
8333 				devTools = null;
8334 			} else {
8335 				devTools = new DevToolWindow(this);
8336 				devTools.show();
8337 			}
8338 		}
8339 	}
8340 
8341 	debug DevToolWindow devTools;
8342 
8343 
8344 	/++
8345 		Creates a window. Please note windows are created in a hidden state, so you must call [show] or [loop] to get it to display.
8346 
8347 		History:
8348 			Prior to May 12, 2021, the default title was "D Application" (simpledisplay.d's default). After that, the default is `Runtime.args[0]` instead.
8349 
8350 			The width and height arguments were added to the overload that takes `string` first on June 21, 2021.
8351 	+/
8352 	this(int width = 500, int height = 500, string title = null) {
8353 		if(title is null) {
8354 			import core.runtime;
8355 			if(Runtime.args.length)
8356 				title = Runtime.args[0];
8357 		}
8358 		win = new SimpleWindow(width, height, title, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.normal, WindowFlags.dontAutoShow | WindowFlags.managesChildWindowFocus);
8359 
8360 		static if(UsingSimpledisplayX11) {
8361 		///+
8362 		// for input proxy
8363 		auto display = XDisplayConnection.get;
8364 		auto inputProxy = XCreateSimpleWindow(display, win.window, -1, -1, 1, 1, 0, 0, 0);
8365 		XSelectInput(display, inputProxy, EventMask.KeyPressMask | EventMask.KeyReleaseMask | EventMask.FocusChangeMask);
8366 		XMapWindow(display, inputProxy);
8367 		// writefln("input proxy: 0x%0x", inputProxy);
8368 		this.inputProxy = new SimpleWindow(inputProxy);
8369 
8370 		XEvent lastEvent;
8371 		this.inputProxy.handleNativeEvent = (XEvent ev) {
8372 			lastEvent = ev;
8373 			return 1;
8374 		};
8375 		this.inputProxy.setEventHandlers(
8376 			(MouseEvent e) {
8377 				dispatchMouseEvent(e);
8378 			},
8379 			(KeyEvent e) {
8380 				//writefln("%x   %s", cast(uint) e.key, e.key);
8381 				if(dispatchKeyEvent(e)) {
8382 					// FIXME: i should trap error
8383 					if(auto nw = cast(NestedChildWindowWidget) focusedWidget) {
8384 						auto thing = nw.focusableWindow();
8385 						if(thing && thing.window) {
8386 							lastEvent.xkey.window = thing.window;
8387 							// writeln("sending event ", lastEvent.xkey);
8388 							trapXErrors( {
8389 								XSendEvent(XDisplayConnection.get, thing.window, false, 0, &lastEvent);
8390 							});
8391 						}
8392 					}
8393 				}
8394 			},
8395 			(dchar e) {
8396 				if(e == 13) e = 10; // hack?
8397 				if(e == 127) return; // linux sends this, windows doesn't. we don't want it.
8398 				dispatchCharEvent(e);
8399 			},
8400 		);
8401 
8402 		this.inputProxy.populateXic();
8403 		// done
8404 		//+/
8405 		}
8406 
8407 
8408 
8409 		win.setRequestedInputFocus = &this.setRequestedInputFocus;
8410 
8411 		this(win);
8412 	}
8413 
8414 	SimpleWindow inputProxy;
8415 
8416 	private SimpleWindow setRequestedInputFocus() {
8417 		return inputProxy;
8418 	}
8419 
8420 	/// ditto
8421 	this(string title, int width = 500, int height = 500) {
8422 		this(width, height, title);
8423 	}
8424 
8425 	///
8426 	@property string title() { return parentWindow.win.title; }
8427 	///
8428 	@property void title(string title) { parentWindow.win.title = title; }
8429 
8430 	///
8431 	@scriptable
8432 	void close() {
8433 		win.close();
8434 		// I synchronize here upon window closing to ensure all child windows
8435 		// get updated too before the event loop. This avoids some random X errors.
8436 		static if(UsingSimpledisplayX11) {
8437 			runInGuiThread( {
8438 				XSync(XDisplayConnection.get, false);
8439 			});
8440 		}
8441 	}
8442 
8443 	bool dispatchKeyEvent(KeyEvent ev) {
8444 		auto wid = focusedWidget;
8445 		if(wid is null)
8446 			wid = this;
8447 		KeyEventBase event = ev.pressed ? new KeyDownEvent(wid) : new KeyUpEvent(wid);
8448 		event.originalKeyEvent = ev;
8449 		event.key = ev.key;
8450 		event.state = ev.modifierState;
8451 		event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false;
8452 		event.altKey = (ev.modifierState & ModifierState.alt) ? true : false;
8453 		event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false;
8454 		event.dispatch();
8455 
8456 		return !event.propagationStopped;
8457 	}
8458 
8459 	// returns true if propagation should continue into nested things.... prolly not a great thing to do.
8460 	bool dispatchCharEvent(dchar ch) {
8461 		if(focusedWidget) {
8462 			auto event = new CharEvent(focusedWidget, ch);
8463 			event.dispatch();
8464 			return !event.propagationStopped;
8465 		}
8466 		return true;
8467 	}
8468 
8469 	Widget mouseLastOver;
8470 	Widget mouseLastDownOn;
8471 	bool lastWasDoubleClick;
8472 	bool dispatchMouseEvent(MouseEvent ev) {
8473 		auto eleR = widgetAtPoint(this, ev.x, ev.y);
8474 		auto ele = eleR.widget;
8475 
8476 		auto captureEle = ele;
8477 
8478 		if(mouseCapturedBy !is null) {
8479 			if(ele !is mouseCapturedBy && !mouseCapturedBy.isAParentOf(ele))
8480 				captureEle = mouseCapturedBy;
8481 		}
8482 
8483 		// a hack to get it relative to the widget.
8484 		eleR.x = ev.x;
8485 		eleR.y = ev.y;
8486 		auto pain = captureEle;
8487 		while(pain) {
8488 			eleR.x -= pain.x;
8489 			eleR.y -= pain.y;
8490 			pain.addScrollPosition(eleR.x, eleR.y);
8491 			pain = pain.parent;
8492 		}
8493 
8494 		void populateMouseEventBase(MouseEventBase event) {
8495 			event.button = ev.button;
8496 			event.buttonLinear = ev.buttonLinear;
8497 			event.state = ev.modifierState;
8498 			event.clientX = eleR.x;
8499 			event.clientY = eleR.y;
8500 
8501 			event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false;
8502 			event.altKey = (ev.modifierState & ModifierState.alt) ? true : false;
8503 			event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false;
8504 		}
8505 
8506 		if(ev.type == MouseEventType.buttonPressed) {
8507 			{
8508 				auto event = new MouseDownEvent(captureEle);
8509 				populateMouseEventBase(event);
8510 				event.dispatch();
8511 			}
8512 
8513 			if(ev.button != MouseButton.wheelDown && ev.button != MouseButton.wheelUp && mouseLastDownOn is ele && ev.doubleClick) {
8514 				auto event = new DoubleClickEvent(captureEle);
8515 				populateMouseEventBase(event);
8516 				event.dispatch();
8517 				lastWasDoubleClick = ev.doubleClick;
8518 			} else {
8519 				lastWasDoubleClick = false;
8520 			}
8521 
8522 			mouseLastDownOn = ele;
8523 		} else if(ev.type == MouseEventType.buttonReleased) {
8524 			{
8525 				auto event = new MouseUpEvent(captureEle);
8526 				populateMouseEventBase(event);
8527 				event.dispatch();
8528 			}
8529 			if(!lastWasDoubleClick && mouseLastDownOn is ele) {
8530 				auto event = new ClickEvent(captureEle);
8531 				populateMouseEventBase(event);
8532 				event.dispatch();
8533 			}
8534 		} else if(ev.type == MouseEventType.motion) {
8535 			// motion
8536 			{
8537 				auto event = new MouseMoveEvent(captureEle);
8538 				populateMouseEventBase(event); // fills in button which is meaningless but meh
8539 				event.dispatch();
8540 			}
8541 
8542 			if(mouseLastOver !is ele) {
8543 				if(ele !is null) {
8544 					if(!isAParentOf(ele, mouseLastOver)) {
8545 						ele.setDynamicState(DynamicState.hover, true);
8546 						auto event = new MouseEnterEvent(ele);
8547 						event.relatedTarget = mouseLastOver;
8548 						event.sendDirectly();
8549 
8550 						ele.useStyleProperties((scope Widget.Style s) {
8551 							ele.parentWindow.win.cursor = s.cursor;
8552 						});
8553 					}
8554 				}
8555 
8556 				if(mouseLastOver !is null) {
8557 					if(!isAParentOf(mouseLastOver, ele)) {
8558 						mouseLastOver.setDynamicState(DynamicState.hover, false);
8559 						auto event = new MouseLeaveEvent(mouseLastOver);
8560 						event.relatedTarget = ele;
8561 						event.sendDirectly();
8562 					}
8563 				}
8564 
8565 				if(ele !is null) {
8566 					auto event = new MouseOverEvent(ele);
8567 					event.relatedTarget = mouseLastOver;
8568 					event.dispatch();
8569 				}
8570 
8571 				if(mouseLastOver !is null) {
8572 					auto event = new MouseOutEvent(mouseLastOver);
8573 					event.relatedTarget = ele;
8574 					event.dispatch();
8575 				}
8576 
8577 				mouseLastOver = ele;
8578 			}
8579 		}
8580 
8581 		return true; // FIXME: the event default prevented?
8582 	}
8583 
8584 	/++
8585 		Shows the window and runs the application event loop.
8586 
8587 		Blocks until this window is closed.
8588 
8589 		Bugs:
8590 
8591 		$(PITFALL
8592 			You should always have one event loop live for your application.
8593 			If you make two windows in sequence, the second call to loop (or
8594 			simpledisplay's [SimpleWindow.eventLoop], upon which this is built)
8595 			might fail:
8596 
8597 			---
8598 			// don't do this!
8599 			auto window = new Window();
8600 			window.loop();
8601 
8602 			// or new Window or new MainWindow, all the same
8603 			auto window2 = new SimpleWindow();
8604 			window2.eventLoop(0); // problematic! might crash
8605 			---
8606 
8607 			simpledisplay's current implementation assumes that final cleanup is
8608 			done when the event loop refcount reaches zero. So after the first
8609 			eventLoop returns, when there isn't already another one active, it assumes
8610 			the program will exit soon and cleans up.
8611 
8612 			This is arguably a bug that it doesn't reinitialize, and I'll probably change
8613 			it eventually, but in the mean time, there's an easy solution:
8614 
8615 			---
8616 			// do this
8617 			EventLoop mainEventLoop = EventLoop.get; // just add this line
8618 
8619 			auto window = new Window();
8620 			window.loop();
8621 
8622 			// or any other type of Window etc.
8623 			auto window2 = new Window();
8624 			window2.loop(); // perfectly fine since mainEventLoop still alive
8625 			---
8626 
8627 			By adding a top-level reference to the event loop, it ensures the final cleanup
8628 			is not performed until it goes out of scope too, letting the individual window loops
8629 			work without trouble despite the bug.
8630 		)
8631 
8632 		History:
8633 			The [BlockingMode] parameter was added on December 8, 2021.
8634 			The default behavior is to block until the application quits
8635 			(so all windows have been closed), unless another minigui or
8636 			simpledisplay event loop is already running, in which case it
8637 			will block until this window closes specifically.
8638 	+/
8639 	@scriptable
8640 	void loop(BlockingMode bm = BlockingMode.automatic) {
8641 		if(win.closed)
8642 			return; // otherwise show will throw
8643 		show();
8644 		win.eventLoopWithBlockingMode(bm, 0);
8645 	}
8646 
8647 	private bool firstShow = true;
8648 
8649 	@scriptable
8650 	override void show() {
8651 		bool rd = false;
8652 		if(firstShow) {
8653 			firstShow = false;
8654 			recomputeChildLayout();
8655 			auto f = getFirstFocusable(this); // FIXME: autofocus?
8656 			if(f)
8657 				f.focus();
8658 			redraw();
8659 		}
8660 		win.show();
8661 		super.show();
8662 	}
8663 	@scriptable
8664 	override void hide() {
8665 		win.hide();
8666 		super.hide();
8667 	}
8668 
8669 	static Widget getFirstFocusable(Widget start) {
8670 		if(start is null)
8671 			return null;
8672 
8673 		foreach(widget; &start.focusableWidgets) {
8674 			return widget;
8675 		}
8676 
8677 		return null;
8678 	}
8679 
8680 	static Widget getLastFocusable(Widget start) {
8681 		if(start is null)
8682 			return null;
8683 
8684 		Widget last;
8685 		foreach(widget; &start.focusableWidgets) {
8686 			last = widget;
8687 		}
8688 
8689 		return last;
8690 	}
8691 
8692 
8693 	mixin Emits!ClosingEvent;
8694 	mixin Emits!ClosedEvent;
8695 }
8696 
8697 /++
8698 	History:
8699 		Added January 12, 2022
8700 +/
8701 class DpiChangedEvent : Event {
8702 	enum EventString = "dpichanged";
8703 
8704 	this(Widget target) {
8705 		super(EventString, target);
8706 	}
8707 }
8708 
8709 debug private class DevToolWindow : Window {
8710 	Window p;
8711 
8712 	TextEdit parentList;
8713 	TextEdit logWindow;
8714 	TextLabel clickX, clickY;
8715 
8716 	this(Window p) {
8717 		this.p = p;
8718 		super(400, 300, "Developer Toolbox");
8719 
8720 		logWindow = new TextEdit(this);
8721 		parentList = new TextEdit(this);
8722 
8723 		auto hl = new HorizontalLayout(this);
8724 		clickX = new TextLabel("", TextAlignment.Right, hl);
8725 		clickY = new TextLabel("", TextAlignment.Right, hl);
8726 
8727 		parentListeners ~= p.addEventListener("*", (Event ev) {
8728 			log(typeid(ev.source).name, " emitted ", typeid(ev).name);
8729 		});
8730 
8731 		parentListeners ~= p.addEventListener((ClickEvent ev) {
8732 			auto s = ev.srcElement;
8733 
8734 			string list;
8735 
8736 			void addInfo(Widget s) {
8737 				list ~= s.toString();
8738 				list ~= "\n\tminHeight: " ~ toInternal!string(s.minHeight);
8739 				list ~= "\n\tmaxHeight: " ~ toInternal!string(s.maxHeight);
8740 				list ~= "\n\theightStretchiness: " ~ toInternal!string(s.heightStretchiness);
8741 				list ~= "\n\theight: " ~ toInternal!string(s.height);
8742 				list ~= "\n\tminWidth: " ~ toInternal!string(s.minWidth);
8743 				list ~= "\n\tmaxWidth: " ~ toInternal!string(s.maxWidth);
8744 				list ~= "\n\twidthStretchiness: " ~ toInternal!string(s.widthStretchiness);
8745 				list ~= "\n\twidth: " ~ toInternal!string(s.width);
8746 				list ~= "\n\tmarginTop: " ~ toInternal!string(s.marginTop);
8747 				list ~= "\n\tmarginBottom: " ~ toInternal!string(s.marginBottom);
8748 			}
8749 
8750 			addInfo(s);
8751 
8752 			s = s.parent;
8753 			while(s) {
8754 				list ~= "\n";
8755 				addInfo(s);
8756 				s = s.parent;
8757 			}
8758 			parentList.content = list;
8759 
8760 			clickX.label = toInternal!string(ev.clientX);
8761 			clickY.label = toInternal!string(ev.clientY);
8762 		});
8763 	}
8764 
8765 	EventListener[] parentListeners;
8766 
8767 	override void close() {
8768 		assert(p !is null);
8769 		foreach(p; parentListeners)
8770 			p.disconnect();
8771 		parentListeners = null;
8772 		p.devTools = null;
8773 		p = null;
8774 		super.close();
8775 	}
8776 
8777 	override void defaultEventHandler_keydown(KeyDownEvent ev) {
8778 		if(ev.key == Key.F12) {
8779 			this.close();
8780 			if(p)
8781 				p.devTools = null;
8782 		} else {
8783 			super.defaultEventHandler_keydown(ev);
8784 		}
8785 	}
8786 
8787 	void log(T...)(T t) {
8788 		string str;
8789 		import std.conv;
8790 		foreach(i; t)
8791 			str ~= to!string(i);
8792 		str ~= "\n";
8793 		logWindow.addText(str);
8794 
8795 		//version(custom_widgets)
8796 		//logWindow.ensureVisibleInScroll(logWindow.textLayout.caretBoundingBox());
8797 	}
8798 }
8799 
8800 /++
8801 	A dialog is a transient window that intends to get information from
8802 	the user before being dismissed.
8803 +/
8804 abstract class Dialog : Window {
8805 	///
8806 	this(int width, int height, string title = null) {
8807 		super(width, height, title);
8808 	}
8809 
8810 	///
8811 	abstract void OK();
8812 
8813 	///
8814 	void Cancel() {
8815 		this.close();
8816 	}
8817 }
8818 
8819 /++
8820 	A custom widget similar to the HTML5 <details> tag.
8821 +/
8822 version(none)
8823 class DetailsView : Widget {
8824 
8825 }
8826 
8827 // FIXME: maybe i should expose the other list views Windows offers too
8828 
8829 /++
8830 	A TableView is a widget made to display a table of data strings.
8831 
8832 
8833 	Future_Directions:
8834 		Each item should be able to take an icon too and maybe I'll allow more of the view modes Windows offers.
8835 
8836 		I will add a selection changed event at some point, as well as item clicked events.
8837 	History:
8838 		Added September 24, 2021. Initial api stabilized in dub v10.4, but it isn't completely feature complete yet.
8839 	See_Also:
8840 		[ListWidget] which displays a list of strings without additional columns.
8841 +/
8842 class TableView : Widget {
8843 	/++
8844 
8845 	+/
8846 	this(Widget parent) {
8847 		super(parent);
8848 
8849 		version(win32_widgets) {
8850 			createWin32Window(this, WC_LISTVIEW, "", LVS_REPORT | LVS_OWNERDATA);//| LVS_OWNERDRAWFIXED);
8851 		} else version(custom_widgets) {
8852 			auto smw = new ScrollMessageWidget(this);
8853 			smw.addDefaultKeyboardListeners();
8854 			smw.addDefaultWheelListeners(1, scaleWithDpi(16));
8855 			tvwi = new TableViewWidgetInner(this, smw);
8856 		}
8857 	}
8858 
8859 	// FIXME: auto-size columns on double click of header thing like in Windows
8860 	// it need only make the currently displayed things fit well.
8861 
8862 
8863 	private ColumnInfo[] columns;
8864 	private int itemCount;
8865 
8866 	version(custom_widgets) private {
8867 		TableViewWidgetInner tvwi;
8868 	}
8869 
8870 	/// Passed to [setColumnInfo]
8871 	static struct ColumnInfo {
8872 		const(char)[] name; /// the name displayed in the header
8873 		/++
8874 			The default width, in pixels. As a special case, you can set this to -1
8875 			if you want the system to try to automatically size the width to fit visible
8876 			content. If it can't, it will try to pick a sensible default size.
8877 
8878 			Any other negative value is not allowed and may lead to unpredictable results.
8879 
8880 			History:
8881 				The -1 behavior was specified on December 3, 2021. It actually worked before
8882 				anyway on Win32 but now it is a formal feature with partial Linux support.
8883 
8884 			Bugs:
8885 				It doesn't actually attempt to calculate a best-fit width on Linux as of
8886 				December 3, 2021. I do plan to fix this in the future, but Windows is the
8887 				priority right now. At least it doesn't break things when you use it now.
8888 		+/
8889 		int width;
8890 
8891 		/++
8892 			Alignment of the text in the cell. Applies to the header as well as all data in this
8893 			column.
8894 
8895 			Bugs:
8896 				On Windows, the first column ignores this member and is always left aligned.
8897 				You can work around this by inserting a dummy first column with width = 0
8898 				then putting your actual data in the second column, which does respect the
8899 				alignment.
8900 
8901 				This is a quirk of the operating system's implementation going back a very
8902 				long time and is unlikely to ever be fixed.
8903 		+/
8904 		TextAlignment alignment;
8905 
8906 		/++
8907 			After all the pixel widths have been assigned, any left over
8908 			space is divided up among all columns and distributed to according
8909 			to the widthPercent field.
8910 
8911 
8912 			For example, if you have two fields, both with width 50 and one with
8913 			widthPercent of 25 and the other with widthPercent of 75, and the
8914 			container is 200 pixels wide, first both get their width of 50.
8915 			then the 100 remaining pixels are split up, so the one gets a total
8916 			of 75 pixels and the other gets a total of 125.
8917 
8918 			This is automatically applied as the window is resized.
8919 
8920 			If there is not enough space - that is, when a horizontal scrollbar
8921 			needs to appear - there are 0 pixels divided up, and thus everyone
8922 			gets 0. This can cause a column to shrink out of proportion when
8923 			passing the scroll threshold.
8924 
8925 			It is important to still set a fixed width (that is, to populate the
8926 			`width` field) even if you use the percents because that will be the
8927 			default minimum in the event of a scroll bar appearing.
8928 
8929 			The percents total in the column can never exceed 100 or be less than 0.
8930 			Doing this will trigger an assert error.
8931 
8932 			Implementation note:
8933 
8934 			Please note that percentages are only recalculated 1) upon original
8935 			construction and 2) upon resizing the control. If the user adjusts the
8936 			width of a column, the percentage items will not be updated.
8937 
8938 			On the other hand, if the user adjusts the width of a percentage column
8939 			then resizes the window, it is recalculated, meaning their hand adjustment
8940 			is discarded. This specific behavior may change in the future as it is
8941 			arguably a bug, but I'm not certain yet.
8942 
8943 			History:
8944 				Added November 10, 2021 (dub v10.4)
8945 		+/
8946 		int widthPercent;
8947 
8948 
8949 		private int calculatedWidth;
8950 	}
8951 	/++
8952 		Sets the number of columns along with information about the headers.
8953 
8954 		Please note: on Windows, the first column ignores your alignment preference
8955 		and is always left aligned.
8956 	+/
8957 	void setColumnInfo(ColumnInfo[] columns...) {
8958 
8959 		foreach(ref c; columns) {
8960 			c.name = c.name.idup;
8961 		}
8962 		this.columns = columns.dup;
8963 
8964 		updateCalculatedWidth(false);
8965 
8966 		version(custom_widgets) {
8967 			tvwi.header.updateHeaders();
8968 			tvwi.updateScrolls();
8969 		} else version(win32_widgets)
8970 		foreach(i, column; this.columns) {
8971 			LVCOLUMN lvColumn;
8972 			lvColumn.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
8973 			lvColumn.cx = column.width == -1 ? -1 : column.calculatedWidth;
8974 
8975 			auto bfr = WCharzBuffer(column.name);
8976 			lvColumn.pszText = bfr.ptr;
8977 
8978 			if(column.alignment & TextAlignment.Center)
8979 				lvColumn.fmt = LVCFMT_CENTER;
8980 			else if(column.alignment & TextAlignment.Right)
8981 				lvColumn.fmt = LVCFMT_RIGHT;
8982 			else
8983 				lvColumn.fmt = LVCFMT_LEFT;
8984 
8985 			if(SendMessage(hwnd, LVM_INSERTCOLUMN, cast(WPARAM) i, cast(LPARAM) &lvColumn) == -1)
8986 				throw new WindowsApiException("Insert Column Fail", GetLastError());
8987 		}
8988 	}
8989 
8990 	private int getActualSetSize(size_t i, bool askWindows) {
8991 		version(win32_widgets)
8992 			if(askWindows)
8993 				return cast(int) SendMessage(hwnd, LVM_GETCOLUMNWIDTH, cast(WPARAM) i, 0);
8994 		auto w = columns[i].width;
8995 		if(w == -1)
8996 			return 50; // idk, just give it some space so the percents aren't COMPLETELY off FIXME
8997 		return w;
8998 	}
8999 
9000 	private void updateCalculatedWidth(bool informWindows) {
9001 		int padding;
9002 		version(win32_widgets)
9003 			padding = 4;
9004 		int remaining = this.width;
9005 		foreach(i, column; columns)
9006 			remaining -= this.getActualSetSize(i, informWindows && column.widthPercent == 0) + padding;
9007 		remaining -= padding;
9008 		if(remaining < 0)
9009 			remaining = 0;
9010 
9011 		int percentTotal;
9012 		foreach(i, ref column; columns) {
9013 			percentTotal += column.widthPercent;
9014 
9015 			auto c = this.getActualSetSize(i, informWindows && column.widthPercent == 0) + (remaining * column.widthPercent) / 100;
9016 
9017 			column.calculatedWidth = c;
9018 
9019 			version(win32_widgets)
9020 			if(informWindows)
9021 				SendMessage(hwnd, LVM_SETCOLUMNWIDTH, i, c); // LVSCW_AUTOSIZE or LVSCW_AUTOSIZE_USEHEADER are amazing omg
9022 		}
9023 
9024 		assert(percentTotal >= 0, "The total percents in your column definitions were negative. They must add up to something between 0 and 100.");
9025 		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).");
9026 
9027 
9028 	}
9029 
9030 	override void registerMovement() {
9031 		super.registerMovement();
9032 
9033 		updateCalculatedWidth(true);
9034 	}
9035 
9036 	/++
9037 		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.
9038 	+/
9039 	void setItemCount(int count) {
9040 		this.itemCount = count;
9041 		version(custom_widgets) {
9042 			tvwi.updateScrolls();
9043 			redraw();
9044 		} else version(win32_widgets) {
9045 			SendMessage(hwnd, LVM_SETITEMCOUNT, count, 0);
9046 		}
9047 	}
9048 
9049 	/++
9050 		Clears all items;
9051 	+/
9052 	void clear() {
9053 		this.itemCount = 0;
9054 		this.columns = null;
9055 		version(custom_widgets) {
9056 			tvwi.header.updateHeaders();
9057 			tvwi.updateScrolls();
9058 			redraw();
9059 		} else version(win32_widgets) {
9060 			SendMessage(hwnd, LVM_DELETEALLITEMS, 0, 0);
9061 		}
9062 	}
9063 
9064 	/+
9065 	version(win32_widgets)
9066 	override int handleWmDrawItem(DRAWITEMSTRUCT* dis)
9067 		auto itemId = dis.itemID;
9068 		auto hdc = dis.hDC;
9069 		auto rect = dis.rcItem;
9070 		switch(dis.itemAction) {
9071 			case ODA_DRAWENTIRE:
9072 
9073 				// FIXME: do other items
9074 				// FIXME: do the focus rectangle i guess
9075 				// FIXME: alignment
9076 				// FIXME: column width
9077 				// FIXME: padding left
9078 				// FIXME: check dpi scaling
9079 				// FIXME: don't owner draw unless it is necessary.
9080 
9081 				auto padding = GetSystemMetrics(SM_CXEDGE); // FIXME: for dpi
9082 				RECT itemRect;
9083 				itemRect.top = 1; // subitem idx, 1-based
9084 				itemRect.left = LVIR_BOUNDS;
9085 
9086 				SendMessage(hwnd, LVM_GETSUBITEMRECT, itemId, cast(LPARAM) &itemRect);
9087 				itemRect.left += padding;
9088 
9089 				getData(itemId, 0, (in char[] data) {
9090 					auto wdata = WCharzBuffer(data);
9091 					DrawTextW(hdc, wdata.ptr, wdata.length, &itemRect, DT_RIGHT| DT_END_ELLIPSIS);
9092 
9093 				});
9094 			goto case;
9095 			case ODA_FOCUS:
9096 				if(dis.itemState & ODS_FOCUS)
9097 					DrawFocusRect(hdc, &rect);
9098 			break;
9099 			case ODA_SELECT:
9100 				// itemState & ODS_SELECTED
9101 			break;
9102 			default:
9103 		}
9104 		return 1;
9105 	}
9106 	+/
9107 
9108 	version(win32_widgets) {
9109 		CellStyle last;
9110 		COLORREF defaultColor;
9111 		COLORREF defaultBackground;
9112 	}
9113 
9114 	version(win32_widgets)
9115 	override int handleWmNotify(NMHDR* hdr, int code, out int mustReturn) {
9116 		switch(code) {
9117 			case NM_CUSTOMDRAW:
9118 				auto s = cast(NMLVCUSTOMDRAW*) hdr;
9119 				switch(s.nmcd.dwDrawStage) {
9120 					case CDDS_PREPAINT:
9121 						if(getCellStyle is null)
9122 							return 0;
9123 
9124 						mustReturn = true;
9125 						return CDRF_NOTIFYITEMDRAW;
9126 					case CDDS_ITEMPREPAINT:
9127 						mustReturn = true;
9128 						return CDRF_NOTIFYSUBITEMDRAW;
9129 					case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
9130 						mustReturn = true;
9131 
9132 						if(getCellStyle is null) // this SHOULD never happen...
9133 							return 0;
9134 
9135 						if(s.iSubItem == 0) {
9136 							// Windows resets it per row so we'll use item 0 as a chance
9137 							// to capture these for later
9138 							defaultColor = s.clrText;
9139 							defaultBackground = s.clrTextBk;
9140 						}
9141 
9142 						auto style = getCellStyle(cast(int) s.nmcd.dwItemSpec, cast(int) s.iSubItem);
9143 						// if no special style and no reset needed...
9144 						if(style == CellStyle.init && (s.iSubItem == 0 || last == CellStyle.init))
9145 							return 0; // allow default processing to continue
9146 
9147 						last = style;
9148 
9149 						// might still need to reset or use the preference.
9150 
9151 						if(style.flags & CellStyle.Flags.textColorSet)
9152 							s.clrText = style.textColor.asWindowsColorRef;
9153 						else
9154 							s.clrText = defaultColor; // reset in case it was set from last iteration not a fan
9155 						if(style.flags & CellStyle.Flags.backgroundColorSet)
9156 							s.clrTextBk = style.backgroundColor.asWindowsColorRef;
9157 						else
9158 							s.clrTextBk = defaultBackground; // need to reset it... not a fan of this
9159 
9160 						return CDRF_NEWFONT;
9161 					default:
9162 						return 0;
9163 
9164 				}
9165 			case NM_RETURN: // no need since i subclass keydown
9166 			break;
9167 			case LVN_COLUMNCLICK:
9168 				auto info = cast(LPNMLISTVIEW) hdr;
9169 				this.emit!HeaderClickedEvent(info.iSubItem);
9170 			break;
9171 			case NM_CLICK:
9172 			case NM_DBLCLK:
9173 			case NM_RCLICK:
9174 			case NM_RDBLCLK:
9175 				// the item/subitem is set here and that can be a useful notification
9176 				// even beyond the normal click notification
9177 			break;
9178 			case LVN_GETDISPINFO:
9179 				LV_DISPINFO* info = cast(LV_DISPINFO*) hdr;
9180 				if(info.item.mask & LVIF_TEXT) {
9181 					if(getData) {
9182 						getData(info.item.iItem, info.item.iSubItem, (in char[] dataReceived) {
9183 							auto bfr = WCharzBuffer(dataReceived);
9184 							auto len = info.item.cchTextMax;
9185 							if(bfr.length < len)
9186 								len = cast(typeof(len)) bfr.length;
9187 							info.item.pszText[0 .. len] = bfr.ptr[0 .. len];
9188 							info.item.pszText[len] = 0;
9189 						});
9190 					} else {
9191 						info.item.pszText[0] = 0;
9192 					}
9193 					//info.item.iItem
9194 					//if(info.item.iSubItem)
9195 				}
9196 			break;
9197 			default:
9198 		}
9199 		return 0;
9200 	}
9201 
9202 	override bool encapsulatedChildren() {
9203 		return true;
9204 	}
9205 
9206 	/++
9207 		Informs the control that content has changed.
9208 
9209 		History:
9210 			Added November 10, 2021 (dub v10.4)
9211 	+/
9212 	void update() {
9213 		version(custom_widgets)
9214 			redraw();
9215 		else {
9216 			SendMessage(hwnd, LVM_REDRAWITEMS, 0, SendMessage(hwnd, LVM_GETITEMCOUNT, 0, 0));
9217 			UpdateWindow(hwnd);
9218 		}
9219 
9220 
9221 	}
9222 
9223 	/++
9224 		Called by the system to request the text content of an individual cell. You
9225 		should pass the text into the provided `sink` delegate. This function will be
9226 		called for each visible cell as-needed when drawing.
9227 	+/
9228 	void delegate(int row, int column, scope void delegate(in char[]) sink) getData;
9229 
9230 	/++
9231 		Available per-cell style customization options. Use one of the constructors
9232 		provided to set the values conveniently, or default construct it and set individual
9233 		values yourself. Just remember to set the `flags` so your values are actually used.
9234 		If the flag isn't set, the field is ignored and the system default is used instead.
9235 
9236 		This is returned by the [getCellStyle] delegate.
9237 
9238 		Examples:
9239 			---
9240 			// assumes you have a variables called `my_data` which is an array of arrays of numbers
9241 			auto table = new TableView(window);
9242 			// snip: you would set up columns here
9243 
9244 			// this is how you provide data to the table view class
9245 			table.getData = delegate(int row, int column, scope void delegate(in char[]) sink) {
9246 				import std.conv;
9247 				sink(to!string(my_data[row][column]));
9248 			};
9249 
9250 			// and this is how you customize the colors
9251 			table.getCellStyle = delegate(int row, int column) {
9252 				return (my_data[row][column] < 0) ?
9253 					TableView.CellStyle(Color.red); // make negative numbers red
9254 					: TableView.CellStyle.init; // leave the rest alone
9255 			};
9256 			// snip: you would call table.setItemCount here then continue with the rest of your window setup work
9257 			---
9258 
9259 		History:
9260 			Added November 27, 2021 (dub v10.4)
9261 	+/
9262 	struct CellStyle {
9263 		/// 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.
9264 		this(Color textColor) {
9265 			this.textColor = textColor;
9266 			this.flags |= Flags.textColorSet;
9267 		}
9268 		/// Sets a custom text and background color.
9269 		this(Color textColor, Color backgroundColor) {
9270 			this.textColor = textColor;
9271 			this.backgroundColor = backgroundColor;
9272 			this.flags |= Flags.textColorSet | Flags.backgroundColorSet;
9273 		}
9274 
9275 		Color textColor;
9276 		Color backgroundColor;
9277 		int flags; /// bitmask of [Flags]
9278 		/// available options to combine into [flags]
9279 		enum Flags {
9280 			textColorSet = 1 << 0,
9281 			backgroundColorSet = 1 << 1,
9282 		}
9283 	}
9284 	/++
9285 		Companion delegate to [getData] that allows you to custom style each
9286 		cell of the table.
9287 
9288 		Returns:
9289 			A [CellStyle] structure that describes the desired style for the
9290 			given cell. `return CellStyle.init` if you want the default style.
9291 
9292 		History:
9293 			Added November 27, 2021 (dub v10.4)
9294 	+/
9295 	CellStyle delegate(int row, int column) getCellStyle;
9296 
9297 	// i want to be able to do things like draw little colored things to show red for negative numbers
9298 	// or background color indicators or even in-cell charts
9299 	// void delegate(int row, int column, WidgetPainter painter, int width, int height, in char[] text) drawCell;
9300 
9301 	/++
9302 		When the user clicks on a header, this event is emitted. It has a meber to identify which header (by index) was clicked.
9303 	+/
9304 	mixin Emits!HeaderClickedEvent;
9305 }
9306 
9307 /++
9308 	This is emitted by the [TableView] when a user clicks on a column header.
9309 
9310 	Its member `columnIndex` has the zero-based index of the column that was clicked.
9311 
9312 	The default behavior of this event is to do nothing, so `preventDefault` has no effect.
9313 
9314 	History:
9315 		Added November 27, 2021 (dub v10.4)
9316 +/
9317 class HeaderClickedEvent : Event {
9318 	enum EventString = "HeaderClicked";
9319 	this(Widget target, int columnIndex) {
9320 		this.columnIndex = columnIndex;
9321 		super(EventString, target);
9322 	}
9323 
9324 	/// The index of the column
9325 	int columnIndex;
9326 
9327 	///
9328 	override @property int intValue() {
9329 		return columnIndex;
9330 	}
9331 }
9332 
9333 version(custom_widgets)
9334 private class TableViewWidgetInner : Widget {
9335 
9336 // wrap this thing in a ScrollMessageWidget
9337 
9338 	TableView tvw;
9339 	ScrollMessageWidget smw;
9340 	HeaderWidget header;
9341 
9342 	this(TableView tvw, ScrollMessageWidget smw) {
9343 		this.tvw = tvw;
9344 		this.smw = smw;
9345 		super(smw);
9346 
9347 		this.tabStop = true;
9348 
9349 		header = new HeaderWidget(this, smw.getHeader());
9350 
9351 		smw.addEventListener("scroll", () {
9352 			this.redraw();
9353 			header.redraw();
9354 		});
9355 
9356 
9357 		// I need headers outside the scroll area but rendered on the same line as the up arrow
9358 		// FIXME: add a fixed header to the SMW
9359 	}
9360 
9361 	enum padding = 3;
9362 
9363 	void updateScrolls() {
9364 		int w;
9365 		foreach(idx, column; tvw.columns) {
9366 			if(column.width == 0) continue;
9367 			w += tvw.getActualSetSize(idx, false);// + padding;
9368 		}
9369 		smw.setTotalArea(w, tvw.itemCount);
9370 		columnsWidth = w;
9371 	}
9372 
9373 	private int columnsWidth;
9374 
9375 	private int lh() { return scaleWithDpi(16); } // FIXME lineHeight
9376 
9377 	override void registerMovement() {
9378 		super.registerMovement();
9379 		// FIXME: actual column width. it might need to be done per-pixel instead of per-colum
9380 		smw.setViewableArea(this.width, this.height / lh);
9381 	}
9382 
9383 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
9384 		int x;
9385 		int y;
9386 
9387 		int row = smw.position.y;
9388 
9389 		foreach(lol; 0 .. this.height / lh) {
9390 			if(row >= tvw.itemCount)
9391 				break;
9392 			x = 0;
9393 			foreach(columnNumber, column; tvw.columns) {
9394 				auto x2 = x + column.calculatedWidth;
9395 				auto smwx = smw.position.x;
9396 
9397 				if(x2 > smwx /* if right side of it is visible at all */ || (x >= smwx && x < smwx + this.width) /* left side is visible at all*/) {
9398 					auto startX = x;
9399 					auto endX = x + column.calculatedWidth;
9400 					switch (column.alignment & (TextAlignment.Left | TextAlignment.Center | TextAlignment.Right)) {
9401 						case TextAlignment.Left: startX += padding; break;
9402 						case TextAlignment.Center: startX += padding; endX -= padding; break;
9403 						case TextAlignment.Right: endX -= padding; break;
9404 						default: /* broken */ break;
9405 					}
9406 					if(column.width != 0) // no point drawing an invisible column
9407 					tvw.getData(row, cast(int) columnNumber, (in char[] info) {
9408 						// auto clip = painter.setClipRectangle(
9409 
9410 						void dotext(WidgetPainter painter) {
9411 							painter.drawText(Point(startX - smw.position.x, y), info, Point(endX - smw.position.x, y + lh), column.alignment);
9412 						}
9413 
9414 						if(tvw.getCellStyle !is null) {
9415 							auto style = tvw.getCellStyle(row, cast(int) columnNumber);
9416 
9417 							if(style.flags & TableView.CellStyle.Flags.backgroundColorSet) {
9418 								auto tempPainter = painter;
9419 								tempPainter.fillColor = style.backgroundColor;
9420 								tempPainter.outlineColor = style.backgroundColor;
9421 
9422 								tempPainter.drawRectangle(Point(startX - smw.position.x, y),
9423 									Point(endX - smw.position.x, y + lh));
9424 							}
9425 							auto tempPainter = painter;
9426 							if(style.flags & TableView.CellStyle.Flags.textColorSet)
9427 								tempPainter.outlineColor = style.textColor;
9428 
9429 							dotext(tempPainter);
9430 						} else {
9431 							dotext(painter);
9432 						}
9433 					});
9434 				}
9435 
9436 				x += column.calculatedWidth;
9437 			}
9438 			row++;
9439 			y += lh;
9440 		}
9441 		return bounds;
9442 	}
9443 
9444 	static class Style : Widget.Style {
9445 		override WidgetBackground background() {
9446 			return WidgetBackground(WidgetPainter.visualTheme.widgetBackgroundColor);
9447 		}
9448 	}
9449 	mixin OverrideStyle!Style;
9450 
9451 	private static class HeaderWidget : Widget {
9452 		this(TableViewWidgetInner tvw, Widget parent) {
9453 			super(parent);
9454 			this.tvw = tvw;
9455 
9456 			this.remainder = new Button("", this);
9457 
9458 			this.addEventListener((scope ClickEvent ev) {
9459 				int header = -1;
9460 				foreach(idx, child; this.children[1 .. $]) {
9461 					if(child is ev.target) {
9462 						header = cast(int) idx;
9463 						break;
9464 					}
9465 				}
9466 
9467 				if(header != -1) {
9468 					auto hce = new HeaderClickedEvent(tvw.tvw, header);
9469 					hce.dispatch();
9470 				}
9471 
9472 			});
9473 		}
9474 
9475 		void updateHeaders() {
9476 			foreach(child; children[1 .. $])
9477 				child.removeWidget();
9478 
9479 			foreach(column; tvw.tvw.columns) {
9480 				// the cast is ok because I dup it above, just the type is never changed.
9481 				// all this is private so it should never get messed up.
9482 				new Button(ImageLabel(cast(string) column.name, column.alignment), this);
9483 			}
9484 		}
9485 
9486 		Button remainder;
9487 		TableViewWidgetInner tvw;
9488 
9489 		override void recomputeChildLayout() {
9490 			registerMovement();
9491 			int pos;
9492 			foreach(idx, child; children[1 .. $]) {
9493 				if(idx >= tvw.tvw.columns.length)
9494 					continue;
9495 				child.x = pos;
9496 				child.y = 0;
9497 				child.width = tvw.tvw.columns[idx].calculatedWidth;
9498 				child.height = scaleWithDpi(16);// this.height;
9499 				pos += child.width;
9500 
9501 				child.recomputeChildLayout();
9502 			}
9503 
9504 			if(remainder is null)
9505 				return;
9506 
9507 			remainder.x = pos;
9508 			remainder.y = 0;
9509 			if(pos < this.width)
9510 				remainder.width = this.width - pos;// + 4;
9511 			else
9512 				remainder.width = 0;
9513 			remainder.height = scaleWithDpi(16);
9514 
9515 			remainder.recomputeChildLayout();
9516 		}
9517 
9518 		// for the scrollable children mixin
9519 		Point scrollOrigin() {
9520 			return Point(tvw.smw.position.x, 0);
9521 		}
9522 		void paintFrameAndBackground(WidgetPainter painter) { }
9523 
9524 		mixin ScrollableChildren;
9525 	}
9526 }
9527 
9528 /+
9529 
9530 // given struct / array / number / string / etc, make it viewable and editable
9531 class DataViewerWidget : Widget {
9532 
9533 }
9534 +/
9535 
9536 /++
9537 	A line edit box with an associated label.
9538 
9539 	History:
9540 		On May 17, 2021, the default internal layout was changed from horizontal to vertical.
9541 
9542 		```
9543 		Old: ________
9544 
9545 		New:
9546 		____________
9547 		```
9548 
9549 		To restore the old behavior, use `new LabeledLineEdit("label", TextAlignment.Right, parent);`
9550 
9551 		You can also use `new LabeledLineEdit("label", TextAlignment.Left, parent);` if you want a
9552 		horizontal label but left aligned. You may also consider a [GridLayout].
9553 +/
9554 alias LabeledLineEdit = Labeled!LineEdit;
9555 
9556 /++
9557 	History:
9558 		Added May 19, 2021
9559 +/
9560 class Labeled(T) : Widget {
9561 	///
9562 	this(string label, Widget parent) {
9563 		super(parent);
9564 		initialize!VerticalLayout(label, TextAlignment.Left, parent);
9565 	}
9566 
9567 	/++
9568 		History:
9569 			The alignment parameter was added May 17, 2021
9570 	+/
9571 	this(string label, TextAlignment alignment, Widget parent) {
9572 		super(parent);
9573 		initialize!HorizontalLayout(label, alignment, parent);
9574 	}
9575 
9576 	private void initialize(L)(string label, TextAlignment alignment, Widget parent) {
9577 		tabStop = false;
9578 		horizontal = is(L == HorizontalLayout);
9579 		auto hl = new L(this);
9580 		if(horizontal) {
9581 			static class SpecialTextLabel : TextLabel {
9582 				this(string label, TextAlignment alignment, Widget parent) {
9583 					super(label, alignment, parent);
9584 				}
9585 
9586 				override int paddingTop() { return 6; }
9587 			}
9588 			this.label = new SpecialTextLabel(label, alignment, hl);
9589 		} else
9590 			this.label = new TextLabel(label, alignment, hl);
9591 		this.lineEdit = new T(hl);
9592 
9593 		this.label.labelFor = this.lineEdit;
9594 	}
9595 
9596 	private bool horizontal;
9597 
9598 	TextLabel label; ///
9599 	T lineEdit; ///
9600 
9601 	override int flexBasisWidth() { return 250; }
9602 
9603 	override int minHeight() {
9604 		return this.children[0].minHeight;
9605 	}
9606 	override int maxHeight() { return minHeight(); }
9607 	override int marginTop() { return 4; }
9608 	override int marginBottom() { return 4; }
9609 
9610 	// FIXME: i should prolly call it value as well as content tbh
9611 
9612 	///
9613 	@property string content() {
9614 		return lineEdit.content;
9615 	}
9616 	///
9617 	@property void content(string c) {
9618 		return lineEdit.content(c);
9619 	}
9620 
9621 	///
9622 	void selectAll() {
9623 		lineEdit.selectAll();
9624 	}
9625 
9626 	override void focus() {
9627 		lineEdit.focus();
9628 	}
9629 }
9630 
9631 /++
9632 	A labeled password edit.
9633 
9634 	History:
9635 		Added as a class on January 25, 2021, changed into an alias of the new [Labeled] template on May 19, 2021
9636 
9637 		The default parameters for the constructors were also removed on May 19, 2021
9638 +/
9639 alias LabeledPasswordEdit = Labeled!PasswordEdit;
9640 
9641 private string toMenuLabel(string s) {
9642 	string n;
9643 	n.reserve(s.length);
9644 	foreach(c; s)
9645 		if(c == '_')
9646 			n ~= ' ';
9647 		else
9648 			n ~= c;
9649 	return n;
9650 }
9651 
9652 private void autoExceptionHandler(Exception e) {
9653 	messageBox(e.msg);
9654 }
9655 
9656 private void delegate() makeAutomaticHandler(alias fn, T)(T t) {
9657 	static if(is(T : void delegate())) {
9658 		return () {
9659 			try
9660 				t();
9661 			catch(Exception e)
9662 				autoExceptionHandler(e);
9663 		};
9664 	} else static if(is(typeof(fn) Params == __parameters)) {
9665 		static if(Params.length == 1 && is(Params[0] == FileName!(member, filters, type), alias member, string[] filters, FileDialogType type)) {
9666 			return () {
9667 				void onOK(string s) {
9668 					member = s;
9669 					try
9670 						t(Params[0](s));
9671 					catch(Exception e)
9672 						autoExceptionHandler(e);
9673 				}
9674 
9675 				if(
9676 					(type == FileDialogType.Automatic && (__traits(identifier, fn).startsWith("Save") || __traits(identifier, fn).startsWith("Export")))
9677 					|| type == FileDialogType.Save)
9678 				{
9679 					getSaveFileName(&onOK, member, filters, null);
9680 				} else
9681 					getOpenFileName(&onOK, member, filters, null);
9682 			};
9683 		} else {
9684 			struct S {
9685 				static if(!__traits(compiles, mixin(`{ static foreach(i; 1..4) {} }`))) {
9686 					pragma(msg, "warning: automatic handler of params not yet implemented on your compiler");
9687 				} else mixin(q{
9688 				static foreach(idx, ignore; Params) {
9689 					mixin("Params[idx] " ~ __traits(identifier, Params[idx .. idx + 1]) ~ ";");
9690 				}
9691 				});
9692 			}
9693 			return () {
9694 				dialog((S s) {
9695 					try {
9696 						static if(is(typeof(t) Ret == return)) {
9697 							static if(is(Ret == void)) {
9698 								t(s.tupleof);
9699 							} else {
9700 								auto ret = t(s.tupleof);
9701 								import std.conv;
9702 								messageBox(to!string(ret), "Returned Value");
9703 							}
9704 						}
9705 					} catch(Exception e)
9706 						autoExceptionHandler(e);
9707 				}, null, __traits(identifier, fn));
9708 			};
9709 		}
9710 	}
9711 }
9712 
9713 private template hasAnyRelevantAnnotations(a...) {
9714 	bool helper() {
9715 		bool any;
9716 		foreach(attr; a) {
9717 			static if(is(typeof(attr) == .menu))
9718 				any = true;
9719 			else static if(is(typeof(attr) == .toolbar))
9720 				any = true;
9721 			else static if(is(attr == .separator))
9722 				any = true;
9723 			else static if(is(typeof(attr) == .accelerator))
9724 				any = true;
9725 			else static if(is(typeof(attr) == .hotkey))
9726 				any = true;
9727 			else static if(is(typeof(attr) == .icon))
9728 				any = true;
9729 			else static if(is(typeof(attr) == .label))
9730 				any = true;
9731 			else static if(is(typeof(attr) == .tip))
9732 				any = true;
9733 		}
9734 		return any;
9735 	}
9736 
9737 	enum bool hasAnyRelevantAnnotations = helper();
9738 }
9739 
9740 /++
9741 	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.
9742 +/
9743 class MainWindow : Window {
9744 	///
9745 	this(string title = null, int initialWidth = 500, int initialHeight = 500) {
9746 		super(initialWidth, initialHeight, title);
9747 
9748 		_clientArea = new ClientAreaWidget();
9749 		_clientArea.x = 0;
9750 		_clientArea.y = 0;
9751 		_clientArea.width = this.width;
9752 		_clientArea.height = this.height;
9753 		_clientArea.tabStop = false;
9754 
9755 		super.addChild(_clientArea);
9756 
9757 		statusBar = new StatusBar(this);
9758 	}
9759 
9760 	/++
9761 		Adds a menu and toolbar from annotated functions.
9762 
9763 	---
9764         struct Commands {
9765                 @menu("File") {
9766                         void New() {}
9767                         void Open() {}
9768                         void Save() {}
9769                         @separator
9770                         void Exit() @accelerator("Alt+F4") @hotkey('x') {
9771                                 window.close();
9772                         }
9773                 }
9774 
9775                 @menu("Edit") {
9776                         void Undo() {
9777                                 undo();
9778                         }
9779                         @separator
9780                         void Cut() {}
9781                         void Copy() {}
9782                         void Paste() {}
9783                 }
9784 
9785                 @menu("Help") {
9786                         void About() {}
9787                 }
9788         }
9789 
9790         Commands commands;
9791 
9792         window.setMenuAndToolbarFromAnnotatedCode(commands);
9793 	---
9794 
9795 	Note that you can call this function multiple times and it will add the items in order to the given items.
9796 
9797 	+/
9798 	void setMenuAndToolbarFromAnnotatedCode(T)(ref T t) if(!is(T == class) && !is(T == interface)) {
9799 		setMenuAndToolbarFromAnnotatedCode_internal(t);
9800 	}
9801 	void setMenuAndToolbarFromAnnotatedCode(T)(T t) if(is(T == class) || is(T == interface)) {
9802 		setMenuAndToolbarFromAnnotatedCode_internal(t);
9803 	}
9804 	void setMenuAndToolbarFromAnnotatedCode_internal(T)(ref T t) {
9805 		Action[] toolbarActions;
9806 		auto menuBar = this.menuBar is null ? new MenuBar() : this.menuBar;
9807 		Menu[string] mcs;
9808 
9809 		foreach(menu; menuBar.subMenus) {
9810 			mcs[menu.label] = menu;
9811 		}
9812 
9813 		foreach(memberName; __traits(derivedMembers, T)) {
9814 			static if(memberName != "this")
9815 			static if(hasAnyRelevantAnnotations!(__traits(getAttributes, __traits(getMember, T, memberName)))) {
9816 				.menu menu;
9817 				.toolbar toolbar;
9818 				bool separator;
9819 				.accelerator accelerator;
9820 				.hotkey hotkey;
9821 				.icon icon;
9822 				string label;
9823 				string tip;
9824 				foreach(attr; __traits(getAttributes, __traits(getMember, T, memberName))) {
9825 					static if(is(typeof(attr) == .menu))
9826 						menu = attr;
9827 					else static if(is(typeof(attr) == .toolbar))
9828 						toolbar = attr;
9829 					else static if(is(attr == .separator))
9830 						separator = true;
9831 					else static if(is(typeof(attr) == .accelerator))
9832 						accelerator = attr;
9833 					else static if(is(typeof(attr) == .hotkey))
9834 						hotkey = attr;
9835 					else static if(is(typeof(attr) == .icon))
9836 						icon = attr;
9837 					else static if(is(typeof(attr) == .label))
9838 						label = attr.label;
9839 					else static if(is(typeof(attr) == .tip))
9840 						tip = attr.tip;
9841 				}
9842 
9843 				if(menu !is .menu.init || toolbar !is .toolbar.init) {
9844 					ushort correctIcon = icon.id; // FIXME
9845 					if(label.length == 0)
9846 						label = memberName.toMenuLabel;
9847 
9848 					auto handler = makeAutomaticHandler!(__traits(getMember, T, memberName))(&__traits(getMember, t, memberName));
9849 
9850 					auto action = new Action(label, correctIcon, handler);
9851 
9852 					if(accelerator.keyString.length) {
9853 						auto ke = KeyEvent.parse(accelerator.keyString);
9854 						action.accelerator = ke;
9855 						accelerators[ke.toStr] = handler;
9856 					}
9857 
9858 					if(toolbar !is .toolbar.init)
9859 						toolbarActions ~= action;
9860 					if(menu !is .menu.init) {
9861 						Menu mc;
9862 						if(menu.name in mcs) {
9863 							mc = mcs[menu.name];
9864 						} else {
9865 							mc = new Menu(menu.name, this);
9866 							menuBar.addItem(mc);
9867 							mcs[menu.name] = mc;
9868 						}
9869 
9870 						if(separator)
9871 							mc.addSeparator();
9872 						mc.addItem(new MenuItem(action));
9873 					}
9874 				}
9875 			}
9876 		}
9877 
9878 		this.menuBar = menuBar;
9879 
9880 		if(toolbarActions.length) {
9881 			auto tb = new ToolBar(toolbarActions, this);
9882 		}
9883 	}
9884 
9885 	void delegate()[string] accelerators;
9886 
9887 	override void defaultEventHandler_keydown(KeyDownEvent event) {
9888 		auto str = event.originalKeyEvent.toStr;
9889 		if(auto acl = str in accelerators)
9890 			(*acl)();
9891 		super.defaultEventHandler_keydown(event);
9892 	}
9893 
9894 	override void defaultEventHandler_mouseover(MouseOverEvent event) {
9895 		super.defaultEventHandler_mouseover(event);
9896 		if(this.statusBar !is null && event.target.statusTip.length)
9897 			this.statusBar.parts[0].content = event.target.statusTip;
9898 		else if(this.statusBar !is null && this.statusTip.length)
9899 			this.statusBar.parts[0].content = this.statusTip; // ~ " " ~ event.target.toString();
9900 	}
9901 
9902 	override void addChild(Widget c, int position = int.max) {
9903 		if(auto tb = cast(ToolBar) c)
9904 			version(win32_widgets)
9905 				super.addChild(c, 0);
9906 			else version(custom_widgets)
9907 				super.addChild(c, menuBar ? 1 : 0);
9908 			else static assert(0);
9909 		else
9910 			clientArea.addChild(c, position);
9911 	}
9912 
9913 	ToolBar _toolBar;
9914 	///
9915 	ToolBar toolBar() { return _toolBar; }
9916 	///
9917 	ToolBar toolBar(ToolBar t) {
9918 		_toolBar = t;
9919 		foreach(child; this.children)
9920 			if(child is t)
9921 				return t;
9922 		version(win32_widgets)
9923 			super.addChild(t, 0);
9924 		else version(custom_widgets)
9925 			super.addChild(t, menuBar ? 1 : 0);
9926 		else static assert(0);
9927 		return t;
9928 	}
9929 
9930 	MenuBar _menu;
9931 	///
9932 	MenuBar menuBar() { return _menu; }
9933 	///
9934 	MenuBar menuBar(MenuBar m) {
9935 		if(m is _menu) {
9936 			version(custom_widgets)
9937 				recomputeChildLayout();
9938 			return m;
9939 		}
9940 
9941 		if(_menu !is null) {
9942 			// make sure it is sanely removed
9943 			// FIXME
9944 		}
9945 
9946 		_menu = m;
9947 
9948 		version(win32_widgets) {
9949 			SetMenu(parentWindow.win.impl.hwnd, m.handle);
9950 		} else version(custom_widgets) {
9951 			super.addChild(m, 0);
9952 
9953 		//	clientArea.y = menu.height;
9954 		//	clientArea.height = this.height - menu.height;
9955 
9956 			recomputeChildLayout();
9957 		} else static assert(false);
9958 
9959 		return _menu;
9960 	}
9961 	private Widget _clientArea;
9962 	///
9963 	@property Widget clientArea() { return _clientArea; }
9964 	protected @property void clientArea(Widget wid) {
9965 		_clientArea = wid;
9966 	}
9967 
9968 	private StatusBar _statusBar;
9969 	/++
9970 		Returns the window's [StatusBar]. Be warned it may be `null`.
9971 	+/
9972 	@property StatusBar statusBar() { return _statusBar; }
9973 	/// ditto
9974 	@property void statusBar(StatusBar bar) {
9975 		if(_statusBar !is null)
9976 			_statusBar.removeWidget();
9977 		_statusBar = bar;
9978 		if(bar !is null)
9979 			super.addChild(_statusBar);
9980 	}
9981 }
9982 
9983 /+
9984 	This is really an implementation detail of [MainWindow]
9985 +/
9986 private class ClientAreaWidget : Widget {
9987 	this() {
9988 		this.tabStop = false;
9989 		super(null);
9990 		//sa = new ScrollableWidget(this);
9991 	}
9992 	/*
9993 	ScrollableWidget sa;
9994 	override void addChild(Widget w, int position) {
9995 		if(sa is null)
9996 			super.addChild(w, position);
9997 		else {
9998 			sa.addChild(w, position);
9999 			sa.setContentSize(this.minWidth + 1, this.minHeight);
10000 			writeln(sa.contentWidth, "x", sa.contentHeight);
10001 		}
10002 	}
10003 	*/
10004 }
10005 
10006 /**
10007 	Toolbars are lists of buttons (typically icons) that appear under the menu.
10008 	Each button ought to correspond to a menu item, represented by [Action] objects.
10009 */
10010 class ToolBar : Widget {
10011 	version(win32_widgets) {
10012 		private int idealHeight;
10013 		override int minHeight() { return idealHeight; }
10014 		override int maxHeight() { return idealHeight; }
10015 	} else version(custom_widgets) {
10016 		override int minHeight() { return toolbarIconSize; }// defaultLineHeight * 3/2; }
10017 		override int maxHeight() { return toolbarIconSize; } //defaultLineHeight * 3/2; }
10018 	} else static assert(false);
10019 	override int heightStretchiness() { return 0; }
10020 
10021 	version(win32_widgets) {
10022 		HIMAGELIST imageListSmall;
10023 		HIMAGELIST imageListLarge;
10024 	}
10025 
10026 	this(Widget parent) {
10027 		this(null, parent);
10028 	}
10029 
10030 	version(win32_widgets)
10031 	void changeIconSize(bool useLarge) {
10032 		SendMessageW(hwnd, TB_SETIMAGELIST, cast(WPARAM) 0, cast(LPARAM) (useLarge ? imageListLarge : imageListSmall));
10033 
10034 		/+
10035 		SIZE size;
10036 		import core.sys.windows.commctrl;
10037 		SendMessageW(hwnd, TB_GETMAXSIZE, 0, cast(LPARAM) &size);
10038 		idealHeight = size.cy + 4; // the plus 4 is a hack
10039 		+/
10040 
10041 		idealHeight = useLarge ? 34 : 26;
10042 
10043 		if(parent) {
10044 			parent.recomputeChildLayout();
10045 			parent.redraw();
10046 		}
10047 
10048 		SendMessageW(hwnd, TB_SETBUTTONSIZE, 0, (idealHeight-4) << 16 | (idealHeight-4));
10049 		SendMessageW(hwnd, TB_AUTOSIZE, 0, 0);
10050 	}
10051 
10052 	///
10053 	this(Action[] actions, Widget parent) {
10054 		super(parent);
10055 
10056 		tabStop = false;
10057 
10058 		version(win32_widgets) {
10059 			// so i like how the flat thing looks on windows, but not on wine
10060 			// and eh, with windows visual styles enabled it looks cool anyway soooo gonna
10061 			// leave it commented
10062 			createWin32Window(this, "ToolbarWindow32"w, "", TBSTYLE_LIST|/*TBSTYLE_FLAT|*/TBSTYLE_TOOLTIPS);
10063 
10064 			SendMessageW(hwnd, TB_SETEXTENDEDSTYLE, 0, 8/*TBSTYLE_EX_MIXEDBUTTONS*/);
10065 
10066 			imageListSmall = ImageList_Create(
10067 				// width, height
10068 				16, 16,
10069 				ILC_COLOR16 | ILC_MASK,
10070 				16 /*numberOfButtons*/, 0);
10071 
10072 			imageListLarge = ImageList_Create(
10073 				// width, height
10074 				24, 24,
10075 				ILC_COLOR16 | ILC_MASK,
10076 				16 /*numberOfButtons*/, 0);
10077 
10078 			SendMessageW(hwnd, TB_SETIMAGELIST, cast(WPARAM) 0, cast(LPARAM) imageListSmall);
10079 			SendMessageW(hwnd, TB_LOADIMAGES, cast(WPARAM) IDB_STD_SMALL_COLOR, cast(LPARAM) HINST_COMMCTRL);
10080 
10081 			SendMessageW(hwnd, TB_SETIMAGELIST, cast(WPARAM) 0, cast(LPARAM) imageListLarge);
10082 			SendMessageW(hwnd, TB_LOADIMAGES, cast(WPARAM) IDB_STD_LARGE_COLOR, cast(LPARAM) HINST_COMMCTRL);
10083 
10084 			SendMessageW(hwnd, TB_SETMAXTEXTROWS, 0, 0);
10085 
10086 			TBBUTTON[] buttons;
10087 
10088 			// FIXME: I_IMAGENONE is if here is no icon
10089 			foreach(action; actions)
10090 				buttons ~= TBBUTTON(
10091 					MAKELONG(cast(ushort)(action.iconId ? (action.iconId - 1) : -2 /* I_IMAGENONE */), 0),
10092 					action.id,
10093 					TBSTATE_ENABLED, // state
10094 					0, // style
10095 					0, // reserved array, just zero it out
10096 					0, // dwData
10097 					cast(size_t) toWstringzInternal(action.label) // INT_PTR
10098 				);
10099 
10100 			SendMessageW(hwnd, TB_BUTTONSTRUCTSIZE, cast(WPARAM)TBBUTTON.sizeof, 0);
10101 			SendMessageW(hwnd, TB_ADDBUTTONSW, cast(WPARAM) buttons.length, cast(LPARAM)buttons.ptr);
10102 
10103 			/*
10104 			RECT rect;
10105 			GetWindowRect(hwnd, &rect);
10106 			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
10107 			*/
10108 
10109 			dpiChanged(); // to load the things calling changeIconSize the first time
10110 
10111 			assert(idealHeight);
10112 		} else version(custom_widgets) {
10113 			foreach(action; actions)
10114 				new ToolButton(action, this);
10115 		} else static assert(false);
10116 	}
10117 
10118 	override void recomputeChildLayout() {
10119 		.recomputeChildLayout!"width"(this);
10120 	}
10121 
10122 
10123 	version(win32_widgets)
10124 	override protected void dpiChanged() {
10125 		auto sz = scaleWithDpi(16);
10126 		if(sz >= 20)
10127 			changeIconSize(true);
10128 		else
10129 			changeIconSize(false);
10130 	}
10131 }
10132 
10133 enum toolbarIconSize = 24;
10134 
10135 /// 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.
10136 class ToolButton : Button {
10137 	///
10138 	this(string label, Widget parent) {
10139 		super(label, parent);
10140 		tabStop = false;
10141 	}
10142 	///
10143 	this(Action action, Widget parent) {
10144 		super(action.label, parent);
10145 		tabStop = false;
10146 		this.action = action;
10147 	}
10148 
10149 	version(custom_widgets)
10150 	override void defaultEventHandler_click(ClickEvent event) {
10151 		foreach(handler; action.triggered)
10152 			handler();
10153 	}
10154 
10155 	Action action;
10156 
10157 	override int maxWidth() { return toolbarIconSize; }
10158 	override int minWidth() { return toolbarIconSize; }
10159 	override int maxHeight() { return toolbarIconSize; }
10160 	override int minHeight() { return toolbarIconSize; }
10161 
10162 	version(custom_widgets)
10163 	override void paint(WidgetPainter painter) {
10164 	painter.drawThemed(delegate Rectangle (const Rectangle bounds) {
10165 		painter.outlineColor = Color.black;
10166 
10167 		// I want to get from 16 to 24. that's * 3 / 2
10168 		static assert(toolbarIconSize >= 16);
10169 		enum multiplier = toolbarIconSize / 8;
10170 		enum divisor = 2 + ((toolbarIconSize % 8) ? 1 : 0);
10171 		switch(action.iconId) {
10172 			case GenericIcons.New:
10173 				painter.fillColor = Color.white;
10174 				painter.drawPolygon(
10175 					Point(3, 2) * multiplier / divisor, Point(3, 13) * multiplier / divisor, Point(12, 13) * multiplier / divisor, Point(12, 6) * multiplier / divisor,
10176 					Point(8, 2) * multiplier / divisor, Point(8, 6) * multiplier / divisor, Point(12, 6) * multiplier / divisor, Point(8, 2) * multiplier / divisor,
10177 					Point(3, 2) * multiplier / divisor, Point(3, 13) * multiplier / divisor
10178 				);
10179 			break;
10180 			case GenericIcons.Save:
10181 				painter.fillColor = Color.white;
10182 				painter.outlineColor = Color.black;
10183 				painter.drawRectangle(Point(2, 2) * multiplier / divisor, Point(13, 13) * multiplier / divisor);
10184 
10185 				// the label
10186 				painter.drawRectangle(Point(4, 8) * multiplier / divisor, Point(11, 13) * multiplier / divisor);
10187 
10188 				// the slider
10189 				painter.fillColor = Color.black;
10190 				painter.outlineColor = Color.black;
10191 				painter.drawRectangle(Point(4, 3) * multiplier / divisor, Point(10, 6) * multiplier / divisor);
10192 
10193 				painter.fillColor = Color.white;
10194 				painter.outlineColor = Color.white;
10195 				// the disc window
10196 				painter.drawRectangle(Point(5, 3) * multiplier / divisor, Point(6, 5) * multiplier / divisor);
10197 			break;
10198 			case GenericIcons.Open:
10199 				painter.fillColor = Color.white;
10200 				painter.drawPolygon(
10201 					Point(4, 4) * multiplier / divisor, Point(4, 12) * multiplier / divisor, Point(13, 12) * multiplier / divisor, Point(13, 3) * multiplier / divisor,
10202 					Point(9, 3) * multiplier / divisor, Point(9, 4) * multiplier / divisor, Point(4, 4) * multiplier / divisor);
10203 				painter.drawPolygon(
10204 					Point(2, 6) * multiplier / divisor, Point(11, 6) * multiplier / divisor,
10205 					Point(12, 12) * multiplier / divisor, Point(4, 12) * multiplier / divisor,
10206 					Point(2, 6) * multiplier / divisor);
10207 				//painter.drawLine(Point(9, 6) * multiplier / divisor, Point(13, 7) * multiplier / divisor);
10208 			break;
10209 			case GenericIcons.Copy:
10210 				painter.fillColor = Color.white;
10211 				painter.drawRectangle(Point(3, 2) * multiplier / divisor, Point(9, 10) * multiplier / divisor);
10212 				painter.drawRectangle(Point(6, 5) * multiplier / divisor, Point(12, 13) * multiplier / divisor);
10213 			break;
10214 			case GenericIcons.Cut:
10215 				painter.fillColor = Color.transparent;
10216 				painter.outlineColor = getComputedStyle.foregroundColor();
10217 				painter.drawLine(Point(3, 2) * multiplier / divisor, Point(10, 9) * multiplier / divisor);
10218 				painter.drawLine(Point(4, 9) * multiplier / divisor, Point(11, 2) * multiplier / divisor);
10219 				painter.drawRectangle(Point(3, 9) * multiplier / divisor, Point(5, 13) * multiplier / divisor);
10220 				painter.drawRectangle(Point(9, 9) * multiplier / divisor, Point(11, 12) * multiplier / divisor);
10221 			break;
10222 			case GenericIcons.Paste:
10223 				painter.fillColor = Color.white;
10224 				painter.drawRectangle(Point(2, 3) * multiplier / divisor, Point(11, 11) * multiplier / divisor);
10225 				painter.drawRectangle(Point(6, 8) * multiplier / divisor, Point(13, 13) * multiplier / divisor);
10226 				painter.drawLine(Point(6, 2) * multiplier / divisor, Point(4, 5) * multiplier / divisor);
10227 				painter.drawLine(Point(6, 2) * multiplier / divisor, Point(9, 5) * multiplier / divisor);
10228 				painter.fillColor = Color.black;
10229 				painter.drawRectangle(Point(4, 5) * multiplier / divisor, Point(9, 6) * multiplier / divisor);
10230 			break;
10231 			case GenericIcons.Help:
10232 				painter.outlineColor = getComputedStyle.foregroundColor();
10233 				painter.drawText(Point(0, 0), "?", Point(width, height), TextAlignment.Center | TextAlignment.VerticalCenter);
10234 			break;
10235 			case GenericIcons.Undo:
10236 				painter.fillColor = Color.transparent;
10237 				painter.drawArc(Point(3, 4) * multiplier / divisor, 9 * multiplier / divisor, 9 * multiplier / divisor, 0, 360 * 64);
10238 				painter.outlineColor = Color.black;
10239 				painter.fillColor = Color.black;
10240 				painter.drawPolygon(
10241 					Point(4, 4) * multiplier / divisor,
10242 					Point(8, 2) * multiplier / divisor,
10243 					Point(8, 6) * multiplier / divisor,
10244 					Point(4, 4) * multiplier / divisor,
10245 				);
10246 			break;
10247 			case GenericIcons.Redo:
10248 				painter.fillColor = Color.transparent;
10249 				painter.drawArc(Point(3, 4) * multiplier / divisor, 9 * multiplier / divisor, 9 * multiplier / divisor, 0, 360 * 64);
10250 				painter.outlineColor = Color.black;
10251 				painter.fillColor = Color.black;
10252 				painter.drawPolygon(
10253 					Point(10, 4) * multiplier / divisor,
10254 					Point(6, 2) * multiplier / divisor,
10255 					Point(6, 6) * multiplier / divisor,
10256 					Point(10, 4) * multiplier / divisor,
10257 				);
10258 			break;
10259 			default:
10260 				painter.drawText(Point(0, 0), action.label, Point(width, height), TextAlignment.Center | TextAlignment.VerticalCenter);
10261 		}
10262 		return bounds;
10263 		});
10264 	}
10265 
10266 }
10267 
10268 
10269 /++
10270 	You can make one of thse yourself but it is generally easer to use [MainWindow.setMenuAndToolbarFromAnnotatedCode].
10271 +/
10272 class MenuBar : Widget {
10273 	MenuItem[] items;
10274 	Menu[] subMenus;
10275 
10276 	version(win32_widgets) {
10277 		HMENU handle;
10278 		///
10279 		this(Widget parent = null) {
10280 			super(parent);
10281 
10282 			handle = CreateMenu();
10283 			tabStop = false;
10284 		}
10285 	} else version(custom_widgets) {
10286 		///
10287 		this(Widget parent = null) {
10288 			tabStop = false; // these are selected some other way
10289 			super(parent);
10290 		}
10291 
10292 		mixin Padding!q{2};
10293 	} else static assert(false);
10294 
10295 	version(custom_widgets)
10296 	override void paint(WidgetPainter painter) {
10297 		draw3dFrame(this, painter, FrameStyle.risen, getComputedStyle().background.color);
10298 	}
10299 
10300 	///
10301 	MenuItem addItem(MenuItem item) {
10302 		this.addChild(item);
10303 		items ~= item;
10304 		version(win32_widgets) {
10305 			AppendMenuW(handle, MF_STRING, item.action is null ? 9000 : item.action.id, toWstringzInternal(item.label));
10306 		}
10307 		return item;
10308 	}
10309 
10310 
10311 	///
10312 	Menu addItem(Menu item) {
10313 
10314 		subMenus ~= item;
10315 
10316 		auto mbItem = new MenuItem(item.label, null);// this.parentWindow); // I'ma add the child down below so hopefully this isn't too insane
10317 
10318 		addChild(mbItem);
10319 		items ~= mbItem;
10320 
10321 		version(win32_widgets) {
10322 			AppendMenuW(handle, MF_STRING | MF_POPUP, cast(UINT) item.handle, toWstringzInternal(item.label));
10323 		} else version(custom_widgets) {
10324 			mbItem.defaultEventHandlers["mousedown"] = (Widget e, Event ev) {
10325 				item.popup(mbItem);
10326 			};
10327 		} else static assert(false);
10328 
10329 		return item;
10330 	}
10331 
10332 	override void recomputeChildLayout() {
10333 		.recomputeChildLayout!"width"(this);
10334 	}
10335 
10336 	override int maxHeight() { return defaultLineHeight + 4; }
10337 	override int minHeight() { return defaultLineHeight + 4; }
10338 }
10339 
10340 
10341 /**
10342 	Status bars appear at the bottom of a MainWindow.
10343 	They are made out of Parts, with a width and content.
10344 
10345 	They can have multiple parts or be in simple mode. FIXME: implement simple mode.
10346 
10347 
10348 	sb.parts[0].content = "Status bar text!";
10349 */
10350 class StatusBar : Widget {
10351 	private Part[] partsArray;
10352 	///
10353 	struct Parts {
10354 		@disable this();
10355 		this(StatusBar owner) { this.owner = owner; }
10356 		//@disable this(this);
10357 		///
10358 		@property int length() { return cast(int) owner.partsArray.length; }
10359 		private StatusBar owner;
10360 		private this(StatusBar owner, Part[] parts) {
10361 			this.owner.partsArray = parts;
10362 			this.owner = owner;
10363 		}
10364 		///
10365 		Part opIndex(int p) {
10366 			if(owner.partsArray.length == 0)
10367 				this ~= new StatusBar.Part(0);
10368 			return owner.partsArray[p];
10369 		}
10370 
10371 		///
10372 		Part opOpAssign(string op : "~" )(Part p) {
10373 			assert(owner.partsArray.length < 255);
10374 			p.owner = this.owner;
10375 			p.idx = cast(int) owner.partsArray.length;
10376 			owner.partsArray ~= p;
10377 
10378 			owner.recomputeChildLayout();
10379 
10380 			version(win32_widgets) {
10381 				int[256] pos;
10382 				int cpos;
10383 				foreach(idx, part; owner.partsArray) {
10384 					if(idx + 1 == owner.partsArray.length)
10385 						pos[idx] = -1;
10386 					else {
10387 						cpos += part.currentlyAssignedWidth;
10388 						pos[idx] = cpos;
10389 					}
10390 				}
10391 				SendMessageW(owner.hwnd, WM_USER + 4 /*SB_SETPARTS*/, owner.partsArray.length, cast(size_t) pos.ptr);
10392 			} else version(custom_widgets) {
10393 				owner.redraw();
10394 			} else static assert(false);
10395 
10396 			return p;
10397 		}
10398 	}
10399 
10400 	private Parts _parts;
10401 	///
10402 	final @property Parts parts() {
10403 		return _parts;
10404 	}
10405 
10406 	/++
10407 
10408 	+/
10409 	static class Part {
10410 		/++
10411 			History:
10412 				Added September 1, 2023 (dub v11.1)
10413 		+/
10414 		enum WidthUnits {
10415 			/++
10416 				Unscaled pixels as they appear on screen.
10417 
10418 				If you pass 0, it will treat it as a [Proportional] unit for compatibility with code written against older versions of minigui.
10419 			+/
10420 			DeviceDependentPixels,
10421 			/++
10422 				Pixels at the assumed DPI, but will be automatically scaled with the rest of the ui.
10423 			+/
10424 			DeviceIndependentPixels,
10425 			/++
10426 				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`).
10427 			+/
10428 			ApproximateCharacters,
10429 			/++
10430 				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.
10431 
10432 				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.
10433 			+/
10434 			Proportional
10435 		}
10436 		private WidthUnits units;
10437 		private int width;
10438 		private StatusBar owner;
10439 
10440 		private int currentlyAssignedWidth;
10441 
10442 		/++
10443 			History:
10444 				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.
10445 
10446 				It now allows you to provide your own value for [WidthUnits].
10447 
10448 				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`.
10449 		+/
10450 		this(int w, WidthUnits units = WidthUnits.Proportional) {
10451 			this.units = units;
10452 			this.width = w;
10453 		}
10454 
10455 		/// ditto
10456 		this(int w = 0) {
10457 			if(w == 0)
10458 				this(w, WidthUnits.Proportional);
10459 			else
10460 				this(w, WidthUnits.DeviceDependentPixels);
10461 		}
10462 
10463 		private int idx;
10464 		private string _content;
10465 		///
10466 		@property string content() { return _content; }
10467 		///
10468 		@property void content(string s) {
10469 			version(win32_widgets) {
10470 				_content = s;
10471 				WCharzBuffer bfr = WCharzBuffer(s);
10472 				SendMessageW(owner.hwnd, SB_SETTEXT, idx, cast(LPARAM) bfr.ptr);
10473 			} else version(custom_widgets) {
10474 				if(_content != s) {
10475 					_content = s;
10476 					owner.redraw();
10477 				}
10478 			} else static assert(false);
10479 		}
10480 	}
10481 	string simpleModeContent;
10482 	bool inSimpleMode;
10483 
10484 
10485 	///
10486 	this(Widget parent) {
10487 		super(null); // FIXME
10488 		_parts = Parts(this);
10489 		tabStop = false;
10490 		version(win32_widgets) {
10491 			parentWindow = parent.parentWindow;
10492 			createWin32Window(this, "msctls_statusbar32"w, "", 0);
10493 
10494 			RECT rect;
10495 			GetWindowRect(hwnd, &rect);
10496 			idealHeight = rect.bottom - rect.top;
10497 			assert(idealHeight);
10498 		} else version(custom_widgets) {
10499 		} else static assert(false);
10500 	}
10501 
10502 	override void recomputeChildLayout() {
10503 		int remainingLength = this.width;
10504 
10505 		int proportionalSum;
10506 		int proportionalCount;
10507 		foreach(idx, part; this.partsArray) {
10508 			with(Part.WidthUnits)
10509 			final switch(part.units) {
10510 				case DeviceDependentPixels:
10511 					part.currentlyAssignedWidth = part.width;
10512 					remainingLength -= part.currentlyAssignedWidth;
10513 				break;
10514 				case DeviceIndependentPixels:
10515 					part.currentlyAssignedWidth = scaleWithDpi(part.width);
10516 					remainingLength -= part.currentlyAssignedWidth;
10517 				break;
10518 				case ApproximateCharacters:
10519 					auto cs = getComputedStyle();
10520 					auto font = cs.font;
10521 
10522 					part.currentlyAssignedWidth = font.averageWidth * this.width;
10523 					remainingLength -= part.currentlyAssignedWidth;
10524 				break;
10525 				case Proportional:
10526 					proportionalSum += part.width;
10527 					proportionalCount ++;
10528 				break;
10529 			}
10530 		}
10531 
10532 		foreach(part; this.partsArray) {
10533 			if(part.units == Part.WidthUnits.Proportional) {
10534 				auto proportion = part.width == 0 ? proportionalSum / proportionalCount : part.width;
10535 				if(proportion == 0)
10536 					proportion = 1;
10537 
10538 				if(proportionalSum == 0)
10539 					proportionalSum = proportionalCount;
10540 
10541 				part.currentlyAssignedWidth = remainingLength * proportion / proportionalSum;
10542 			}
10543 		}
10544 
10545 		super.recomputeChildLayout();
10546 	}
10547 
10548 	version(win32_widgets)
10549 	override protected void dpiChanged() {
10550 		RECT rect;
10551 		GetWindowRect(hwnd, &rect);
10552 		idealHeight = rect.bottom - rect.top;
10553 		assert(idealHeight);
10554 	}
10555 
10556 	version(custom_widgets)
10557 	override void paint(WidgetPainter painter) {
10558 		auto cs = getComputedStyle();
10559 		this.draw3dFrame(painter, FrameStyle.sunk, cs.background.color);
10560 		int cpos = 0;
10561 		foreach(idx, part; this.partsArray) {
10562 			auto partWidth = part.currentlyAssignedWidth;
10563 			// part.width ? part.width : ((idx + 1 == this.partsArray.length) ? remainingLength : 100);
10564 			painter.setClipRectangle(Point(cpos, 0), partWidth, height);
10565 			draw3dFrame(cpos, 0, partWidth, height, painter, FrameStyle.sunk, cs.background.color);
10566 			painter.setClipRectangle(Point(cpos + 2, 2), partWidth - 4, height - 4);
10567 
10568 			painter.outlineColor = cs.foregroundColor();
10569 			painter.fillColor = cs.foregroundColor();
10570 
10571 			painter.drawText(Point(cpos + 4, 0), part.content, Point(width, height), TextAlignment.VerticalCenter);
10572 			cpos += partWidth;
10573 		}
10574 	}
10575 
10576 
10577 	version(win32_widgets) {
10578 		private int idealHeight;
10579 		override int maxHeight() { return idealHeight; }
10580 		override int minHeight() { return idealHeight; }
10581 	} else version(custom_widgets) {
10582 		override int maxHeight() { return defaultLineHeight + 4; }
10583 		override int minHeight() { return defaultLineHeight + 4; }
10584 	} else static assert(false);
10585 }
10586 
10587 /// Displays an in-progress indicator without known values
10588 version(none)
10589 class IndefiniteProgressBar : Widget {
10590 	version(win32_widgets)
10591 	this(Widget parent) {
10592 		super(parent);
10593 		createWin32Window(this, "msctls_progress32"w, "", 8 /* PBS_MARQUEE */);
10594 		tabStop = false;
10595 	}
10596 	override int minHeight() { return 10; }
10597 }
10598 
10599 /// A progress bar with a known endpoint and completion amount
10600 class ProgressBar : Widget {
10601 	/++
10602 		History:
10603 			Added March 16, 2022 (dub v10.7)
10604 	+/
10605 	this(int min, int max, Widget parent) {
10606 		this(parent);
10607 		setRange(cast(ushort) min, cast(ushort) max); // FIXME
10608 	}
10609 	this(Widget parent) {
10610 		version(win32_widgets) {
10611 			super(parent);
10612 			createWin32Window(this, "msctls_progress32"w, "", 0);
10613 			tabStop = false;
10614 		} else version(custom_widgets) {
10615 			super(parent);
10616 			max = 100;
10617 			step = 10;
10618 			tabStop = false;
10619 		} else static assert(0);
10620 	}
10621 
10622 	version(custom_widgets)
10623 	override void paint(WidgetPainter painter) {
10624 		auto cs = getComputedStyle();
10625 		this.draw3dFrame(painter, FrameStyle.sunk, cs.background.color);
10626 		painter.fillColor = cs.progressBarColor;
10627 		painter.drawRectangle(Point(0, 0), width * current / max, height);
10628 	}
10629 
10630 
10631 	version(custom_widgets) {
10632 		int current;
10633 		int max;
10634 		int step;
10635 	}
10636 
10637 	///
10638 	void advanceOneStep() {
10639 		version(win32_widgets)
10640 			SendMessageW(hwnd, PBM_STEPIT, 0, 0);
10641 		else version(custom_widgets)
10642 			addToPosition(step);
10643 		else static assert(false);
10644 	}
10645 
10646 	///
10647 	void setStepIncrement(int increment) {
10648 		version(win32_widgets)
10649 			SendMessageW(hwnd, PBM_SETSTEP, increment, 0);
10650 		else version(custom_widgets)
10651 			step = increment;
10652 		else static assert(false);
10653 	}
10654 
10655 	///
10656 	void addToPosition(int amount) {
10657 		version(win32_widgets)
10658 			SendMessageW(hwnd, PBM_DELTAPOS, amount, 0);
10659 		else version(custom_widgets)
10660 			setPosition(current + amount);
10661 		else static assert(false);
10662 	}
10663 
10664 	///
10665 	void setPosition(int pos) {
10666 		version(win32_widgets)
10667 			SendMessageW(hwnd, PBM_SETPOS, pos, 0);
10668 		else version(custom_widgets) {
10669 			current = pos;
10670 			if(current > max)
10671 				current = max;
10672 			redraw();
10673 		}
10674 		else static assert(false);
10675 	}
10676 
10677 	///
10678 	void setRange(ushort min, ushort max) {
10679 		version(win32_widgets)
10680 			SendMessageW(hwnd, PBM_SETRANGE, 0, MAKELONG(min, max));
10681 		else version(custom_widgets) {
10682 			this.max = max;
10683 		}
10684 		else static assert(false);
10685 	}
10686 
10687 	override int minHeight() { return 10; }
10688 }
10689 
10690 version(custom_widgets)
10691 private void extractWindowsStyleLabel(scope const char[] label, out string thisLabel, out dchar thisAccelerator) {
10692 	thisLabel.reserve(label.length);
10693 	bool justSawAmpersand;
10694 	foreach(ch; label) {
10695 		if(justSawAmpersand) {
10696 			justSawAmpersand = false;
10697 			if(ch == '&') {
10698 				goto plain;
10699 			}
10700 			thisAccelerator = ch;
10701 		} else {
10702 			if(ch == '&') {
10703 				justSawAmpersand = true;
10704 				continue;
10705 			}
10706 			plain:
10707 			thisLabel ~= ch;
10708 		}
10709 	}
10710 }
10711 
10712 /++
10713 	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.
10714 
10715 
10716 	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
10717 
10718 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
10719 
10720 	History:
10721 		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.
10722 +/
10723 class Fieldset : Widget {
10724 	// FIXME: on Windows,it doesn't draw the background on the label
10725 	// on X, it doesn't fix the clipping rectangle for it
10726 	version(win32_widgets)
10727 		override int paddingTop() { return defaultLineHeight; }
10728 	else version(custom_widgets)
10729 		override int paddingTop() { return defaultLineHeight + 2; }
10730 	else static assert(false);
10731 	override int paddingBottom() { return 6; }
10732 	override int paddingLeft() { return 6; }
10733 	override int paddingRight() { return 6; }
10734 
10735 	override int marginLeft() { return 6; }
10736 	override int marginRight() { return 6; }
10737 	override int marginTop() { return 2; }
10738 	override int marginBottom() { return 2; }
10739 
10740 	string legend;
10741 
10742 	version(custom_widgets) private dchar accelerator;
10743 
10744 	this(string legend, Widget parent) {
10745 		version(win32_widgets) {
10746 			super(parent);
10747 			this.legend = legend;
10748 			createWin32Window(this, "button"w, legend, BS_GROUPBOX);
10749 			tabStop = false;
10750 		} else version(custom_widgets) {
10751 			super(parent);
10752 			tabStop = false;
10753 
10754 			legend.extractWindowsStyleLabel(this.legend, this.accelerator);
10755 		} else static assert(0);
10756 	}
10757 
10758 	version(custom_widgets)
10759 	override void paint(WidgetPainter painter) {
10760 		auto dlh = defaultLineHeight;
10761 
10762 		painter.fillColor = Color.transparent;
10763 		auto cs = getComputedStyle();
10764 		painter.pen = Pen(cs.foregroundColor, 1);
10765 		painter.drawRectangle(Point(0, dlh / 2), width, height - dlh / 2);
10766 
10767 		auto tx = painter.textSize(legend);
10768 		painter.outlineColor = Color.transparent;
10769 
10770 		version(Windows) {
10771 			auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
10772 			painter.drawRectangle(Point(8, -tx.height/2), tx.width, tx.height);
10773 			SelectObject(painter.impl.hdc, b);
10774 		} else static if(UsingSimpledisplayX11) {
10775 			painter.fillColor = getComputedStyle().windowBackgroundColor;
10776 			painter.drawRectangle(Point(8, 0), tx.width, tx.height);
10777 		}
10778 		painter.outlineColor = cs.foregroundColor;
10779 		painter.drawText(Point(8, 0), legend);
10780 	}
10781 
10782 	override int maxHeight() {
10783 		auto m = paddingTop() + paddingBottom();
10784 		foreach(child; children) {
10785 			auto mh = child.maxHeight();
10786 			if(mh == int.max)
10787 				return int.max;
10788 			m += mh;
10789 			m += child.marginBottom();
10790 			m += child.marginTop();
10791 		}
10792 		m += 6;
10793 		if(m < minHeight)
10794 			return minHeight;
10795 		return m;
10796 	}
10797 
10798 	override int minHeight() {
10799 		auto m = paddingTop() + paddingBottom();
10800 		foreach(child; children) {
10801 			m += child.minHeight();
10802 			m += child.marginBottom();
10803 			m += child.marginTop();
10804 		}
10805 		return m + 6;
10806 	}
10807 
10808 	override int minWidth() {
10809 		return 6 + cast(int) this.legend.length * 7;
10810 	}
10811 }
10812 
10813 /++
10814 	$(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")
10815 	$(IMG //arsdnet.net/minigui-screenshots/linux/Fieldset.png, Same thing, but in the default Linux theme.)
10816 +/
10817 version(minigui_screenshots)
10818 @Screenshot("Fieldset")
10819 unittest {
10820 	auto window = new Window(200, 100);
10821 	auto set = new Fieldset("Baby will", window);
10822 	auto option1 = new Radiobox("Eat", set);
10823 	auto option2 = new Radiobox("Cry", set);
10824 	auto option3 = new Radiobox("Sleep", set);
10825 	window.loop();
10826 }
10827 
10828 /// Draws a line
10829 class HorizontalRule : Widget {
10830 	mixin Margin!q{ 2 };
10831 	override int minHeight() { return 2; }
10832 	override int maxHeight() { return 2; }
10833 
10834 	///
10835 	this(Widget parent) {
10836 		super(parent);
10837 	}
10838 
10839 	override void paint(WidgetPainter painter) {
10840 		auto cs = getComputedStyle();
10841 		painter.outlineColor = cs.darkAccentColor;
10842 		painter.drawLine(Point(0, 0), Point(width, 0));
10843 		painter.outlineColor = cs.lightAccentColor;
10844 		painter.drawLine(Point(0, 1), Point(width, 1));
10845 	}
10846 }
10847 
10848 version(minigui_screenshots)
10849 @Screenshot("HorizontalRule")
10850 /++
10851 	$(IMG //arsdnet.net/minigui-screenshots/linux/HorizontalRule.png, Same thing, but in the default Linux theme.)
10852 
10853 +/
10854 unittest {
10855 	auto window = new Window(200, 100);
10856 	auto above = new TextLabel("Above the line", TextAlignment.Left, window);
10857 	new HorizontalRule(window);
10858 	auto below = new TextLabel("Below the line", TextAlignment.Left, window);
10859 	window.loop();
10860 }
10861 
10862 /// ditto
10863 class VerticalRule : Widget {
10864 	mixin Margin!q{ 2 };
10865 	override int minWidth() { return 2; }
10866 	override int maxWidth() { return 2; }
10867 
10868 	///
10869 	this(Widget parent) {
10870 		super(parent);
10871 	}
10872 
10873 	override void paint(WidgetPainter painter) {
10874 		auto cs = getComputedStyle();
10875 		painter.outlineColor = cs.darkAccentColor;
10876 		painter.drawLine(Point(0, 0), Point(0, height));
10877 		painter.outlineColor = cs.lightAccentColor;
10878 		painter.drawLine(Point(1, 0), Point(1, height));
10879 	}
10880 }
10881 
10882 
10883 ///
10884 class Menu : Window {
10885 	void remove() {
10886 		foreach(i, child; parentWindow.children)
10887 			if(child is this) {
10888 				parentWindow._children = parentWindow._children[0 .. i] ~ parentWindow._children[i + 1 .. $];
10889 				break;
10890 			}
10891 		parentWindow.redraw();
10892 
10893 		parentWindow.releaseMouseCapture();
10894 	}
10895 
10896 	///
10897 	void addSeparator() {
10898 		version(win32_widgets)
10899 			AppendMenu(handle, MF_SEPARATOR, 0, null);
10900 		else version(custom_widgets)
10901 			auto hr = new HorizontalRule(this);
10902 		else static assert(0);
10903 	}
10904 
10905 	override int paddingTop() { return 4; }
10906 	override int paddingBottom() { return 4; }
10907 	override int paddingLeft() { return 2; }
10908 	override int paddingRight() { return 2; }
10909 
10910 	version(win32_widgets) {}
10911 	else version(custom_widgets) {
10912 		SimpleWindow dropDown;
10913 		Widget menuParent;
10914 		void popup(Widget parent, int offsetX = 0, int offsetY = int.min) {
10915 			this.menuParent = parent;
10916 
10917 			int w = 150;
10918 			int h = paddingTop + paddingBottom;
10919 			if(this.children.length) {
10920 				// hacking it to get the ideal height out of recomputeChildLayout
10921 				this.width = w;
10922 				this.height = h;
10923 				this.recomputeChildLayout();
10924 				h = this.children[$-1].y + this.children[$-1].height + this.children[$-1].marginBottom;
10925 				h += paddingBottom;
10926 
10927 				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
10928 			}
10929 
10930 			if(offsetY == int.min)
10931 				offsetY = parent.defaultLineHeight;
10932 
10933 			auto coord = parent.globalCoordinates();
10934 			dropDown.moveResize(coord.x + offsetX, coord.y + offsetY, w, h);
10935 			this.x = 0;
10936 			this.y = 0;
10937 			this.width = dropDown.width;
10938 			this.height = dropDown.height;
10939 			this.drawableWindow = dropDown;
10940 			this.recomputeChildLayout();
10941 
10942 			static if(UsingSimpledisplayX11)
10943 				XSync(XDisplayConnection.get, 0);
10944 
10945 			dropDown.visibilityChanged = (bool visible) {
10946 				if(visible) {
10947 					this.redraw();
10948 					dropDown.grabInput();
10949 				} else {
10950 					dropDown.releaseInputGrab();
10951 				}
10952 			};
10953 
10954 			dropDown.show();
10955 
10956 			clickListener = this.addEventListener((scope ClickEvent ev) {
10957 				unpopup();
10958 				// need to unlock asap just in case other user handlers block...
10959 				static if(UsingSimpledisplayX11)
10960 					flushGui();
10961 			}, true /* again for asap action */);
10962 		}
10963 
10964 		EventListener clickListener;
10965 	}
10966 	else static assert(false);
10967 
10968 	version(custom_widgets)
10969 	void unpopup() {
10970 		mouseLastOver = mouseLastDownOn = null;
10971 		dropDown.hide();
10972 		if(!menuParent.parentWindow.win.closed) {
10973 			if(auto maw = cast(MouseActivatedWidget) menuParent) {
10974 				maw.setDynamicState(DynamicState.depressed, false);
10975 				maw.setDynamicState(DynamicState.hover, false);
10976 				maw.redraw();
10977 			}
10978 			// menuParent.parentWindow.win.focus();
10979 		}
10980 		clickListener.disconnect();
10981 	}
10982 
10983 	MenuItem[] items;
10984 
10985 	///
10986 	MenuItem addItem(MenuItem item) {
10987 		addChild(item);
10988 		items ~= item;
10989 		version(win32_widgets) {
10990 			AppendMenuW(handle, MF_STRING, item.action is null ? 9000 : item.action.id, toWstringzInternal(item.label));
10991 		}
10992 		return item;
10993 	}
10994 
10995 	string label;
10996 
10997 	version(win32_widgets) {
10998 		HMENU handle;
10999 		///
11000 		this(string label, Widget parent) {
11001 			// not actually passing the parent since it effs up the drawing
11002 			super(cast(Widget) null);// parent);
11003 			this.label = label;
11004 			handle = CreatePopupMenu();
11005 		}
11006 	} else version(custom_widgets) {
11007 		///
11008 		this(string label, Widget parent) {
11009 
11010 			if(dropDown) {
11011 				dropDown.close();
11012 			}
11013 			dropDown = new SimpleWindow(
11014 				150, 4,
11015 				null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.dropdownMenu, WindowFlags.dontAutoShow, parent ? parent.parentWindow.win : null);
11016 
11017 			this.label = label;
11018 
11019 			super(dropDown);
11020 		}
11021 	} else static assert(false);
11022 
11023 	override int maxHeight() { return defaultLineHeight; }
11024 	override int minHeight() { return defaultLineHeight; }
11025 
11026 	version(custom_widgets)
11027 	override void paint(WidgetPainter painter) {
11028 		this.draw3dFrame(painter, FrameStyle.risen, getComputedStyle.background.color);
11029 	}
11030 }
11031 
11032 /++
11033 	A MenuItem belongs to a [Menu] - use [Menu.addItem] to add one - and calls an [Action] when it is clicked.
11034 +/
11035 class MenuItem : MouseActivatedWidget {
11036 	Menu submenu;
11037 
11038 	Action action;
11039 	string label;
11040 
11041 	override int paddingLeft() { return 4; }
11042 
11043 	override int maxHeight() { return defaultLineHeight + 4; }
11044 	override int minHeight() { return defaultLineHeight + 4; }
11045 	override int minWidth() { return defaultTextWidth(label) + 8 + scaleWithDpi(12); }
11046 	override int maxWidth() {
11047 		if(cast(MenuBar) parent) {
11048 			return minWidth();
11049 		}
11050 		return int.max;
11051 	}
11052 	/// This should ONLY be used if there is no associated action, for example, if the menu item is just a submenu.
11053 	this(string lbl, Widget parent = null) {
11054 		super(parent);
11055 		//label = lbl; // FIXME
11056 		foreach(char ch; lbl) // FIXME
11057 			if(ch != '&') // FIXME
11058 				label ~= ch; // FIXME
11059 		tabStop = false; // these are selected some other way
11060 	}
11061 
11062 	///
11063 	this(Action action, Widget parent = null) {
11064 		assert(action !is null);
11065 		this(action.label, parent);
11066 		this.action = action;
11067 		tabStop = false; // these are selected some other way
11068 	}
11069 
11070 	version(custom_widgets)
11071 	override void paint(WidgetPainter painter) {
11072 		auto cs = getComputedStyle();
11073 		if(dynamicState & DynamicState.depressed)
11074 			this.draw3dFrame(painter, FrameStyle.sunk, cs.background.color);
11075 		if(dynamicState & DynamicState.hover)
11076 			painter.outlineColor = cs.activeMenuItemColor;
11077 		else
11078 			painter.outlineColor = cs.foregroundColor;
11079 		painter.fillColor = Color.transparent;
11080 		painter.drawText(scaleWithDpi(Point(cast(MenuBar) this.parent ? 4 : 20, 0)), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
11081 		if(action && action.accelerator !is KeyEvent.init) {
11082 			painter.drawText(scaleWithDpi(Point(cast(MenuBar) this.parent ? 4 : 20, 0)), action.accelerator.toStr(), Point(width - 4, height), TextAlignment.Right | TextAlignment.VerticalCenter);
11083 
11084 		}
11085 	}
11086 
11087 	static class Style : Widget.Style {
11088 		override bool variesWithState(ulong dynamicStateFlags) {
11089 			return super.variesWithState(dynamicStateFlags) || (dynamicStateFlags & (DynamicState.depressed | DynamicState.hover));
11090 		}
11091 	}
11092 	mixin OverrideStyle!Style;
11093 
11094 	override void defaultEventHandler_triggered(Event event) {
11095 		if(action)
11096 		foreach(handler; action.triggered)
11097 			handler();
11098 
11099 		if(auto pmenu = cast(Menu) this.parent)
11100 			pmenu.remove();
11101 
11102 		super.defaultEventHandler_triggered(event);
11103 	}
11104 }
11105 
11106 version(win32_widgets)
11107 /// A "mouse activiated widget" is really just an abstract variant of button.
11108 class MouseActivatedWidget : Widget {
11109 	@property bool isChecked() {
11110 		assert(hwnd);
11111 		return SendMessageW(hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED;
11112 
11113 	}
11114 	@property void isChecked(bool state) {
11115 		assert(hwnd);
11116 		SendMessageW(hwnd, BM_SETCHECK, state ? BST_CHECKED : BST_UNCHECKED, 0);
11117 
11118 	}
11119 
11120 	override void handleWmCommand(ushort cmd, ushort id) {
11121 		if(cmd == 0) {
11122 			auto event = new Event(EventType.triggered, this);
11123 			event.dispatch();
11124 		}
11125 	}
11126 
11127 	this(Widget parent) {
11128 		super(parent);
11129 	}
11130 }
11131 else version(custom_widgets)
11132 /// ditto
11133 class MouseActivatedWidget : Widget {
11134 	@property bool isChecked() { return isChecked_; }
11135 	@property bool isChecked(bool b) { return isChecked_ = b; }
11136 
11137 	private bool isChecked_;
11138 
11139 	this(Widget parent) {
11140 		super(parent);
11141 
11142 		addEventListener((MouseDownEvent ev) {
11143 			if(ev.button == MouseButton.left) {
11144 				setDynamicState(DynamicState.depressed, true);
11145 				setDynamicState(DynamicState.hover, true);
11146 				redraw();
11147 			}
11148 		});
11149 
11150 		addEventListener((MouseUpEvent ev) {
11151 			if(ev.button == MouseButton.left) {
11152 				setDynamicState(DynamicState.depressed, false);
11153 				setDynamicState(DynamicState.hover, false);
11154 				redraw();
11155 			}
11156 		});
11157 
11158 		addEventListener((MouseMoveEvent mme) {
11159 			if(!(mme.state & ModifierState.leftButtonDown)) {
11160 				if(dynamicState_ & DynamicState.depressed) {
11161 					setDynamicState(DynamicState.depressed, false);
11162 					redraw();
11163 				}
11164 			}
11165 		});
11166 	}
11167 
11168 	override void defaultEventHandler_focus(Event ev) {
11169 		super.defaultEventHandler_focus(ev);
11170 		this.redraw();
11171 	}
11172 	override void defaultEventHandler_blur(Event ev) {
11173 		super.defaultEventHandler_blur(ev);
11174 		setDynamicState(DynamicState.depressed, false);
11175 		this.redraw();
11176 	}
11177 	override void defaultEventHandler_keydown(KeyDownEvent ev) {
11178 		super.defaultEventHandler_keydown(ev);
11179 		if(ev.key == Key.Space || ev.key == Key.Enter || ev.key == Key.PadEnter) {
11180 			setDynamicState(DynamicState.depressed, true);
11181 			setDynamicState(DynamicState.hover, true);
11182 			this.redraw();
11183 		}
11184 	}
11185 	override void defaultEventHandler_keyup(KeyUpEvent ev) {
11186 		super.defaultEventHandler_keyup(ev);
11187 		if(!(dynamicState & DynamicState.depressed))
11188 			return;
11189 		setDynamicState(DynamicState.depressed, false);
11190 		setDynamicState(DynamicState.hover, false);
11191 		this.redraw();
11192 
11193 		auto event = new Event(EventType.triggered, this);
11194 		event.sendDirectly();
11195 	}
11196 	override void defaultEventHandler_click(ClickEvent ev) {
11197 		super.defaultEventHandler_click(ev);
11198 		if(ev.button == MouseButton.left) {
11199 			auto event = new Event(EventType.triggered, this);
11200 			event.sendDirectly();
11201 		}
11202 	}
11203 
11204 }
11205 else static assert(false);
11206 
11207 /*
11208 /++
11209 	Like the tablet thing, it would have a label, a description, and a switch slider thingy.
11210 
11211 	Basically the same as a checkbox.
11212 +/
11213 class OnOffSwitch : MouseActivatedWidget {
11214 
11215 }
11216 */
11217 
11218 /++
11219 	History:
11220 		Added June 15, 2021 (dub v10.1)
11221 +/
11222 struct ImageLabel {
11223 	/++
11224 		Defines a label+image combo used by some widgets.
11225 
11226 		If you provide just a text label, that is all the widget will try to
11227 		display. Or just an image will display just that. If you provide both,
11228 		it may display both text and image side by side or display the image
11229 		and offer text on an input event depending on the widget.
11230 
11231 		History:
11232 			The `alignment` parameter was added on September 27, 2021
11233 	+/
11234 	this(string label, TextAlignment alignment = TextAlignment.Center) {
11235 		this.label = label;
11236 		this.displayFlags = DisplayFlags.displayText;
11237 		this.alignment = alignment;
11238 	}
11239 
11240 	/// ditto
11241 	this(string label, MemoryImage image, TextAlignment alignment = TextAlignment.Center) {
11242 		this.label = label;
11243 		this.image = image;
11244 		this.displayFlags = DisplayFlags.displayText | DisplayFlags.displayImage;
11245 		this.alignment = alignment;
11246 	}
11247 
11248 	/// ditto
11249 	this(MemoryImage image, TextAlignment alignment = TextAlignment.Center) {
11250 		this.image = image;
11251 		this.displayFlags = DisplayFlags.displayImage;
11252 		this.alignment = alignment;
11253 	}
11254 
11255 	/// ditto
11256 	this(string label, MemoryImage image, int displayFlags, TextAlignment alignment = TextAlignment.Center) {
11257 		this.label = label;
11258 		this.image = image;
11259 		this.alignment = alignment;
11260 		this.displayFlags = displayFlags;
11261 	}
11262 
11263 	string label;
11264 	MemoryImage image;
11265 
11266 	enum DisplayFlags {
11267 		displayText = 1 << 0,
11268 		displayImage = 1 << 1,
11269 	}
11270 
11271 	int displayFlags = DisplayFlags.displayText | DisplayFlags.displayImage;
11272 
11273 	TextAlignment alignment;
11274 }
11275 
11276 /++
11277 	A basic checked or not checked box with an attached label.
11278 
11279 
11280 	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
11281 
11282 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
11283 
11284 	History:
11285 		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.
11286 +/
11287 class Checkbox : MouseActivatedWidget {
11288 	version(win32_widgets) {
11289 		override int maxHeight() { return scaleWithDpi(16); }
11290 		override int minHeight() { return scaleWithDpi(16); }
11291 	} else version(custom_widgets) {
11292 		private enum buttonSize = 16;
11293 		override int maxHeight() { return mymax(defaultLineHeight, scaleWithDpi(buttonSize)); }
11294 		override int minHeight() { return maxHeight(); }
11295 	} else static assert(0);
11296 
11297 	override int marginLeft() { return 4; }
11298 
11299 	override int flexBasisWidth() { return 24 + cast(int) label.length * 7; }
11300 
11301 	/++
11302 		Just an alias because I keep typing checked out of web habit.
11303 
11304 		History:
11305 			Added May 31, 2021
11306 	+/
11307 	alias checked = isChecked;
11308 
11309 	private string label;
11310 	private dchar accelerator;
11311 
11312 	/++
11313 	+/
11314 	this(string label, Widget parent) {
11315 		this(ImageLabel(label), Appearance.checkbox, parent);
11316 	}
11317 
11318 	/// ditto
11319 	this(string label, Appearance appearance, Widget parent) {
11320 		this(ImageLabel(label), appearance, parent);
11321 	}
11322 
11323 	/++
11324 		Changes the look and may change the ideal size of the widget without changing its behavior. The precise look is platform-specific.
11325 
11326 		History:
11327 			Added June 29, 2021 (dub v10.2)
11328 	+/
11329 	enum Appearance {
11330 		checkbox, /// a normal checkbox
11331 		pushbutton, /// a button that is showed as pushed when checked and up when unchecked. Similar to the bold button in a toolbar in Wordpad.
11332 		//sliderswitch,
11333 	}
11334 	private Appearance appearance;
11335 
11336 	/// ditto
11337 	private this(ImageLabel label, Appearance appearance, Widget parent) {
11338 		super(parent);
11339 		version(win32_widgets) {
11340 			this.label = label.label;
11341 
11342 			uint extraStyle;
11343 			final switch(appearance) {
11344 				case Appearance.checkbox:
11345 				break;
11346 				case Appearance.pushbutton:
11347 					extraStyle |= BS_PUSHLIKE;
11348 				break;
11349 			}
11350 
11351 			createWin32Window(this, "button"w, label.label, BS_CHECKBOX | extraStyle);
11352 		} else version(custom_widgets) {
11353 			label.label.extractWindowsStyleLabel(this.label, this.accelerator);
11354 		} else static assert(0);
11355 	}
11356 
11357 	version(custom_widgets)
11358 	override void paint(WidgetPainter painter) {
11359 		auto cs = getComputedStyle();
11360 		if(isFocused()) {
11361 			painter.pen = Pen(Color.black, 1, Pen.Style.Dotted);
11362 			painter.fillColor = cs.windowBackgroundColor;
11363 			painter.drawRectangle(Point(0, 0), width, height);
11364 			painter.pen = Pen(Color.black, 1, Pen.Style.Solid);
11365 		} else {
11366 			painter.pen = Pen(cs.windowBackgroundColor, 1, Pen.Style.Solid);
11367 			painter.fillColor = cs.windowBackgroundColor;
11368 			painter.drawRectangle(Point(0, 0), width, height);
11369 		}
11370 
11371 
11372 		painter.outlineColor = Color.black;
11373 		painter.fillColor = Color.white;
11374 		enum rectOffset = 2;
11375 		painter.drawRectangle(scaleWithDpi(Point(rectOffset, rectOffset)), scaleWithDpi(buttonSize - rectOffset - rectOffset), scaleWithDpi(buttonSize - rectOffset - rectOffset));
11376 
11377 		if(isChecked) {
11378 			auto size = scaleWithDpi(2);
11379 			painter.pen = Pen(Color.black, size);
11380 			// I'm using height so the checkbox is square
11381 			enum padding = 3;
11382 			painter.drawLine(
11383 				scaleWithDpi(Point(rectOffset + padding, rectOffset + padding)),
11384 				scaleWithDpi(Point(buttonSize - padding - rectOffset, buttonSize - padding - rectOffset)) - Point(1 - size % 2, 1 - size % 2)
11385 			);
11386 			painter.drawLine(
11387 				scaleWithDpi(Point(buttonSize - padding - rectOffset, padding + rectOffset)) - Point(1 - size % 2, 0),
11388 				scaleWithDpi(Point(padding + rectOffset, buttonSize - padding - rectOffset)) - Point(0,1 -  size % 2)
11389 			);
11390 
11391 			painter.pen = Pen(Color.black, 1);
11392 		}
11393 
11394 		if(label !is null) {
11395 			painter.outlineColor = cs.foregroundColor();
11396 			painter.fillColor = cs.foregroundColor();
11397 
11398 			// i want the centerline of the text to be aligned with the centerline of the checkbox
11399 			/+
11400 			auto font = cs.font();
11401 			auto y = scaleWithDpi(rectOffset + buttonSize / 2) - font.height / 2;
11402 			painter.drawText(Point(scaleWithDpi(buttonSize + 4), y), label);
11403 			+/
11404 			painter.drawText(scaleWithDpi(Point(buttonSize + 4, rectOffset)), label, Point(width, height - scaleWithDpi(rectOffset)), TextAlignment.Left | TextAlignment.VerticalCenter);
11405 		}
11406 	}
11407 
11408 	override void defaultEventHandler_triggered(Event ev) {
11409 		isChecked = !isChecked;
11410 
11411 		this.emit!(ChangeEvent!bool)(&isChecked);
11412 
11413 		redraw();
11414 	}
11415 
11416 	/// Emits a change event with the checked state
11417 	mixin Emits!(ChangeEvent!bool);
11418 }
11419 
11420 /// Adds empty space to a layout.
11421 class VerticalSpacer : Widget {
11422 	///
11423 	this(Widget parent) {
11424 		super(parent);
11425 	}
11426 }
11427 
11428 /// ditto
11429 class HorizontalSpacer : Widget {
11430 	///
11431 	this(Widget parent) {
11432 		super(parent);
11433 		this.tabStop = false;
11434 	}
11435 }
11436 
11437 
11438 /++
11439 	Creates a radio button with an associated label. These are usually put inside a [Fieldset].
11440 
11441 
11442 	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
11443 
11444 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
11445 
11446 	History:
11447 		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.
11448 +/
11449 class Radiobox : MouseActivatedWidget {
11450 
11451 	version(win32_widgets) {
11452 		override int maxHeight() { return scaleWithDpi(16); }
11453 		override int minHeight() { return scaleWithDpi(16); }
11454 	} else version(custom_widgets) {
11455 		private enum buttonSize = 16;
11456 		override int maxHeight() { return mymax(defaultLineHeight, scaleWithDpi(buttonSize)); }
11457 		override int minHeight() { return maxHeight(); }
11458 	} else static assert(0);
11459 
11460 	override int marginLeft() { return 4; }
11461 
11462 	// FIXME: make a label getter
11463 	private string label;
11464 	private dchar accelerator;
11465 
11466 	/++
11467 
11468 	+/
11469 	this(string label, Widget parent) {
11470 		super(parent);
11471 		version(win32_widgets) {
11472 			this.label = label;
11473 			createWin32Window(this, "button"w, label, BS_AUTORADIOBUTTON);
11474 		} else version(custom_widgets) {
11475 			label.extractWindowsStyleLabel(this.label, this.accelerator);
11476 			height = 16;
11477 			width = height + 4 + cast(int) label.length * 16;
11478 		}
11479 	}
11480 
11481 	version(custom_widgets)
11482 	override void paint(WidgetPainter painter) {
11483 		auto cs = getComputedStyle();
11484 
11485 		if(isFocused) {
11486 			painter.fillColor = cs.windowBackgroundColor;
11487 			painter.pen = Pen(Color.black, 1, Pen.Style.Dotted);
11488 		} else {
11489 			painter.fillColor = cs.windowBackgroundColor;
11490 			painter.outlineColor = cs.windowBackgroundColor;
11491 		}
11492 		painter.drawRectangle(Point(0, 0), width, height);
11493 
11494 		painter.pen = Pen(Color.black, 1, Pen.Style.Solid);
11495 
11496 		painter.outlineColor = Color.black;
11497 		painter.fillColor = Color.white;
11498 		painter.drawEllipse(scaleWithDpi(Point(2, 2)), scaleWithDpi(Point(buttonSize - 2, buttonSize - 2)));
11499 		if(isChecked) {
11500 			painter.outlineColor = Color.black;
11501 			painter.fillColor = Color.black;
11502 			// I'm using height so the checkbox is square
11503 			auto size = scaleWithDpi(2);
11504 			painter.drawEllipse(scaleWithDpi(Point(5, 5)), scaleWithDpi(Point(buttonSize - 5, buttonSize - 5)) + Point(size % 2, size % 2));
11505 		}
11506 
11507 		painter.outlineColor = cs.foregroundColor();
11508 		painter.fillColor = cs.foregroundColor();
11509 
11510 		painter.drawText(scaleWithDpi(Point(buttonSize + 4, 0)), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
11511 	}
11512 
11513 
11514 	override void defaultEventHandler_triggered(Event ev) {
11515 		isChecked = true;
11516 
11517 		if(this.parent) {
11518 			foreach(child; this.parent.children) {
11519 				if(child is this) continue;
11520 				if(auto rb = cast(Radiobox) child) {
11521 					rb.isChecked = false;
11522 					rb.emit!(ChangeEvent!bool)(&rb.isChecked);
11523 					rb.redraw();
11524 				}
11525 			}
11526 		}
11527 
11528 		this.emit!(ChangeEvent!bool)(&this.isChecked);
11529 
11530 		redraw();
11531 	}
11532 
11533 	/// 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.
11534 	mixin Emits!(ChangeEvent!bool);
11535 }
11536 
11537 
11538 /++
11539 	Creates a push button with unbounded size. When it is clicked, it emits a `triggered` event.
11540 
11541 
11542 	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
11543 
11544 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
11545 
11546 	History:
11547 		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.
11548 +/
11549 class Button : MouseActivatedWidget {
11550 	override int heightStretchiness() { return 3; }
11551 	override int widthStretchiness() { return 3; }
11552 
11553 	/++
11554 		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.
11555 
11556 		History:
11557 			Added July 2, 2021
11558 	+/
11559 	public bool triggersOnMultiClick;
11560 
11561 	private string label_;
11562 	private TextAlignment alignment;
11563 	private dchar accelerator;
11564 
11565 	///
11566 	string label() { return label_; }
11567 	///
11568 	void label(string l) {
11569 		label_ = l;
11570 		version(win32_widgets) {
11571 			WCharzBuffer bfr = WCharzBuffer(l);
11572 			SetWindowTextW(hwnd, bfr.ptr);
11573 		} else version(custom_widgets) {
11574 			redraw();
11575 		}
11576 	}
11577 
11578 	override void defaultEventHandler_dblclick(DoubleClickEvent ev) {
11579 		super.defaultEventHandler_dblclick(ev);
11580 		if(triggersOnMultiClick) {
11581 			if(ev.button == MouseButton.left) {
11582 				auto event = new Event(EventType.triggered, this);
11583 				event.sendDirectly();
11584 			}
11585 		}
11586 	}
11587 
11588 	private Sprite sprite;
11589 	private int displayFlags;
11590 
11591 	/++
11592 		Creates a push button with the given label, which may be an image or some text.
11593 
11594 		Bugs:
11595 			If the image is bigger than the button, it may not be displayed in the right position on Linux.
11596 
11597 		History:
11598 			The [ImageLabel] overload was added on June 21, 2021 (dub v10.1).
11599 
11600 			The button with label and image will respect requests to show both on Windows as
11601 			of March 28, 2022 iff you provide a manifest file to opt into common controls v6.
11602 	+/
11603 	this(ImageLabel label, Widget parent) {
11604 		version(win32_widgets) {
11605 			// FIXME: use ideal button size instead
11606 			width = 50;
11607 			height = 30;
11608 			super(parent);
11609 
11610 			// BS_BITMAP is set when we want image only, so checking for exactly that combination
11611 			enum imgFlags = ImageLabel.DisplayFlags.displayImage | ImageLabel.DisplayFlags.displayText;
11612 			auto extraStyle = ((label.displayFlags & imgFlags) == ImageLabel.DisplayFlags.displayImage) ? BS_BITMAP : 0;
11613 
11614 			// the transparent thing can mess up borders in other cases, so only going to keep it for bitmap things where it might matter
11615 			createWin32Window(this, "button"w, label.label, BS_PUSHBUTTON | extraStyle, extraStyle == BS_BITMAP ? WS_EX_TRANSPARENT : 0 );
11616 
11617 			if(label.image) {
11618 				sprite = Sprite.fromMemoryImage(parentWindow.win, label.image, true);
11619 
11620 				SendMessageW(hwnd, BM_SETIMAGE, IMAGE_BITMAP, cast(LPARAM) sprite.nativeHandle);
11621 			}
11622 
11623 			this.label = label.label;
11624 		} else version(custom_widgets) {
11625 			width = 50;
11626 			height = 30;
11627 			super(parent);
11628 
11629 			label.label.extractWindowsStyleLabel(this.label_, this.accelerator);
11630 
11631 			if(label.image) {
11632 				this.sprite = Sprite.fromMemoryImage(parentWindow.win, label.image);
11633 				this.displayFlags = label.displayFlags;
11634 			}
11635 
11636 			this.alignment = label.alignment;
11637 		}
11638 	}
11639 
11640 	///
11641 	this(string label, Widget parent) {
11642 		this(ImageLabel(label), parent);
11643 	}
11644 
11645 	override int minHeight() { return defaultLineHeight + 4; }
11646 
11647 	static class Style : Widget.Style {
11648 		override WidgetBackground background() {
11649 			auto cs = widget.getComputedStyle(); // FIXME: this is potentially recursive
11650 
11651 			auto pressed = DynamicState.depressed | DynamicState.hover;
11652 			if((widget.dynamicState & pressed) == pressed) {
11653 				return WidgetBackground(cs.depressedButtonColor());
11654 			} else if(widget.dynamicState & DynamicState.hover) {
11655 				return WidgetBackground(cs.hoveringColor());
11656 			} else {
11657 				return WidgetBackground(cs.buttonColor());
11658 			}
11659 		}
11660 
11661 		override FrameStyle borderStyle() {
11662 			auto pressed = DynamicState.depressed | DynamicState.hover;
11663 			if((widget.dynamicState & pressed) == pressed) {
11664 				return FrameStyle.sunk;
11665 			} else {
11666 				return FrameStyle.risen;
11667 			}
11668 
11669 		}
11670 
11671 		override bool variesWithState(ulong dynamicStateFlags) {
11672 			return super.variesWithState(dynamicStateFlags) || (dynamicStateFlags & (DynamicState.depressed | DynamicState.hover));
11673 		}
11674 	}
11675 	mixin OverrideStyle!Style;
11676 
11677 	version(custom_widgets)
11678 	override void paint(WidgetPainter painter) {
11679 		painter.drawThemed(delegate Rectangle(const Rectangle bounds) {
11680 			if(sprite) {
11681 				sprite.drawAt(
11682 					painter,
11683 					bounds.upperLeft + Point((bounds.width - sprite.width) / 2, (bounds.height - sprite.height) / 2),
11684 					Point(0, 0)
11685 				);
11686 			} else {
11687 				painter.drawText(bounds.upperLeft, label, bounds.lowerRight, alignment | TextAlignment.VerticalCenter);
11688 			}
11689 			return bounds;
11690 		});
11691 	}
11692 
11693 	override int flexBasisWidth() {
11694 		version(win32_widgets) {
11695 			SIZE size;
11696 			SendMessage(hwnd, BCM_GETIDEALSIZE, 0, cast(LPARAM) &size);
11697 			if(size.cx == 0)
11698 				goto fallback;
11699 			return size.cx + scaleWithDpi(16);
11700 		}
11701 		fallback:
11702 			return scaleWithDpi(cast(int) label.length * 8 + 16);
11703 	}
11704 
11705 	override int flexBasisHeight() {
11706 		version(win32_widgets) {
11707 			SIZE size;
11708 			SendMessage(hwnd, BCM_GETIDEALSIZE, 0, cast(LPARAM) &size);
11709 			if(size.cy == 0)
11710 				goto fallback;
11711 			return size.cy + scaleWithDpi(6);
11712 		}
11713 		fallback:
11714 			return defaultLineHeight + 4;
11715 	}
11716 }
11717 
11718 /++
11719 	A button with a consistent size, suitable for user commands like OK and CANCEL.
11720 +/
11721 class CommandButton : Button {
11722 	this(string label, Widget parent) {
11723 		super(label, parent);
11724 	}
11725 
11726 	// FIXME: I think I can simply make this 0 stretchiness instead of max now that the flex basis is there
11727 
11728 	override int maxHeight() {
11729 		return defaultLineHeight + 4;
11730 	}
11731 
11732 	override int maxWidth() {
11733 		return defaultLineHeight * 4;
11734 	}
11735 
11736 	override int marginLeft() { return 12; }
11737 	override int marginRight() { return 12; }
11738 	override int marginTop() { return 12; }
11739 	override int marginBottom() { return 12; }
11740 }
11741 
11742 ///
11743 enum ArrowDirection {
11744 	left, ///
11745 	right, ///
11746 	up, ///
11747 	down ///
11748 }
11749 
11750 ///
11751 version(custom_widgets)
11752 class ArrowButton : Button {
11753 	///
11754 	this(ArrowDirection direction, Widget parent) {
11755 		super("", parent);
11756 		this.direction = direction;
11757 		triggersOnMultiClick = true;
11758 	}
11759 
11760 	private ArrowDirection direction;
11761 
11762 	override int minHeight() { return scaleWithDpi(16); }
11763 	override int maxHeight() { return scaleWithDpi(16); }
11764 	override int minWidth() { return scaleWithDpi(16); }
11765 	override int maxWidth() { return scaleWithDpi(16); }
11766 
11767 	override void paint(WidgetPainter painter) {
11768 		super.paint(painter);
11769 
11770 		auto cs = getComputedStyle();
11771 
11772 		painter.outlineColor = cs.foregroundColor;
11773 		painter.fillColor = cs.foregroundColor;
11774 
11775 		auto offset = Point((this.width - scaleWithDpi(16)) / 2, (this.height - scaleWithDpi(16)) / 2);
11776 
11777 		final switch(direction) {
11778 			case ArrowDirection.up:
11779 				painter.drawPolygon(
11780 					scaleWithDpi(Point(2, 10) + offset),
11781 					scaleWithDpi(Point(7, 5) + offset),
11782 					scaleWithDpi(Point(12, 10) + offset),
11783 					scaleWithDpi(Point(2, 10) + offset)
11784 				);
11785 			break;
11786 			case ArrowDirection.down:
11787 				painter.drawPolygon(
11788 					scaleWithDpi(Point(2, 6) + offset),
11789 					scaleWithDpi(Point(7, 11) + offset),
11790 					scaleWithDpi(Point(12, 6) + offset),
11791 					scaleWithDpi(Point(2, 6) + offset)
11792 				);
11793 			break;
11794 			case ArrowDirection.left:
11795 				painter.drawPolygon(
11796 					scaleWithDpi(Point(10, 2) + offset),
11797 					scaleWithDpi(Point(5, 7) + offset),
11798 					scaleWithDpi(Point(10, 12) + offset),
11799 					scaleWithDpi(Point(10, 2) + offset)
11800 				);
11801 			break;
11802 			case ArrowDirection.right:
11803 				painter.drawPolygon(
11804 					scaleWithDpi(Point(6, 2) + offset),
11805 					scaleWithDpi(Point(11, 7) + offset),
11806 					scaleWithDpi(Point(6, 12) + offset),
11807 					scaleWithDpi(Point(6, 2) + offset)
11808 				);
11809 			break;
11810 		}
11811 	}
11812 }
11813 
11814 private
11815 int[2] getChildPositionRelativeToParentOrigin(Widget c) nothrow {
11816 	int x, y;
11817 	Widget par = c;
11818 	while(par) {
11819 		x += par.x;
11820 		y += par.y;
11821 		par = par.parent;
11822 	}
11823 	return [x, y];
11824 }
11825 
11826 version(win32_widgets)
11827 private
11828 int[2] getChildPositionRelativeToParentHwnd(Widget c) nothrow {
11829 // MapWindowPoints?
11830 	int x, y;
11831 	Widget par = c;
11832 	while(par) {
11833 		x += par.x;
11834 		y += par.y;
11835 		par = par.parent;
11836 		if(par !is null && par.useNativeDrawing())
11837 			break;
11838 	}
11839 	return [x, y];
11840 }
11841 
11842 ///
11843 class ImageBox : Widget {
11844 	private MemoryImage image_;
11845 
11846 	override int widthStretchiness() { return 1; }
11847 	override int heightStretchiness() { return 1; }
11848 	override int widthShrinkiness() { return 1; }
11849 	override int heightShrinkiness() { return 1; }
11850 
11851 	override int flexBasisHeight() {
11852 		return image_.height;
11853 	}
11854 
11855 	override int flexBasisWidth() {
11856 		return image_.width;
11857 	}
11858 
11859 	///
11860 	public void setImage(MemoryImage image){
11861 		this.image_ = image;
11862 		if(this.parentWindow && this.parentWindow.win) {
11863 			if(sprite)
11864 				sprite.dispose();
11865 			sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_, true));
11866 		}
11867 		redraw();
11868 	}
11869 
11870 	/// How to fit the image in the box if they aren't an exact match in size?
11871 	enum HowToFit {
11872 		center, /// centers the image, cropping around all the edges as needed
11873 		crop, /// always draws the image in the upper left, cropping the lower right if needed
11874 		// stretch, /// not implemented
11875 	}
11876 
11877 	private Sprite sprite;
11878 	private HowToFit howToFit_;
11879 
11880 	private Color backgroundColor_;
11881 
11882 	///
11883 	this(MemoryImage image, HowToFit howToFit, Color backgroundColor, Widget parent) {
11884 		this.image_ = image;
11885 		this.tabStop = false;
11886 		this.howToFit_ = howToFit;
11887 		this.backgroundColor_ = backgroundColor;
11888 		super(parent);
11889 		updateSprite();
11890 	}
11891 
11892 	/// ditto
11893 	this(MemoryImage image, HowToFit howToFit, Widget parent) {
11894 		this(image, howToFit, Color.transparent, parent);
11895 	}
11896 
11897 	private void updateSprite() {
11898 		if(sprite is null && this.parentWindow && this.parentWindow.win) {
11899 			sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_, true));
11900 		}
11901 	}
11902 
11903 	override void paint(WidgetPainter painter) {
11904 		updateSprite();
11905 		if(backgroundColor_.a) {
11906 			painter.fillColor = backgroundColor_;
11907 			painter.drawRectangle(Point(0, 0), width, height);
11908 		}
11909 		if(howToFit_ == HowToFit.crop)
11910 			sprite.drawAt(painter, Point(0, 0));
11911 		else if(howToFit_ == HowToFit.center) {
11912 			sprite.drawAt(painter, Point((width - image_.width) / 2, (height - image_.height) / 2));
11913 		}
11914 	}
11915 }
11916 
11917 ///
11918 class TextLabel : Widget {
11919 	override int minHeight() { return borderBoxForContentBox(Rectangle(Point(0, 0), Size(0, defaultTextHeight()))).height; }
11920 	override int maxHeight() { return minHeight; }
11921 	override int minWidth() { return 32; }
11922 
11923 	override int flexBasisHeight() { return minHeight(); }
11924 	override int flexBasisWidth() { return defaultTextWidth(label); }
11925 
11926 	string label_;
11927 
11928 	/++
11929 		Indicates which other control this label is here for. Similar to HTML `for` attribute.
11930 
11931 		In practice this means a click on the label will focus the `labelFor`. In future versions
11932 		it will also set screen reader hints but that is not yet implemented.
11933 
11934 		History:
11935 			Added October 3, 2021 (dub v10.4)
11936 	+/
11937 	Widget labelFor;
11938 
11939 	///
11940 	@scriptable
11941 	string label() { return label_; }
11942 
11943 	///
11944 	@scriptable
11945 	void label(string l) {
11946 		label_ = l;
11947 		version(win32_widgets) {
11948 			WCharzBuffer bfr = WCharzBuffer(l);
11949 			SetWindowTextW(hwnd, bfr.ptr);
11950 		} else version(custom_widgets)
11951 			redraw();
11952 	}
11953 
11954 	override void defaultEventHandler_click(scope ClickEvent ce) {
11955 		if(this.labelFor !is null)
11956 			this.labelFor.focus();
11957 	}
11958 
11959 	/++
11960 		WARNING: this currently sets TextAlignment.Right as the default. That will change in a future version.
11961 		For future-proofing of your code, if you rely on TextAlignment.Right, you MUST specify that explicitly.
11962 	+/
11963 	this(string label, TextAlignment alignment, Widget parent) {
11964 		this.label_ = label;
11965 		this.alignment = alignment;
11966 		this.tabStop = false;
11967 		super(parent);
11968 
11969 		version(win32_widgets)
11970 		createWin32Window(this, "static"w, label, (alignment & TextAlignment.Center) ? SS_CENTER : 0, (alignment & TextAlignment.Right) ? WS_EX_RIGHT : WS_EX_LEFT);
11971 	}
11972 
11973 	/// ditto
11974 	this(string label, Widget parent) {
11975 		this(label, TextAlignment.Right, parent);
11976 	}
11977 
11978 	TextAlignment alignment;
11979 
11980 	version(custom_widgets)
11981 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
11982 		painter.outlineColor = getComputedStyle().foregroundColor;
11983 		painter.drawText(bounds.upperLeft, this.label, bounds.lowerRight, alignment);
11984 		return bounds;
11985 	}
11986 
11987 }
11988 
11989 version(custom_widgets)
11990 	private struct etc {
11991 		mixin ExperimentalTextComponent;
11992 	}
11993 
11994 version(win32_widgets)
11995 	alias EditableTextWidgetParent = Widget; ///
11996 else version(custom_widgets) {
11997 	version(trash_text) {
11998 		alias EditableTextWidgetParent = ScrollableWidget; ///
11999 	} else {
12000 		alias EditableTextWidgetParent = Widget;
12001 		version=use_new_text_system;
12002 		import arsd.textlayouter;
12003 	}
12004 } else static assert(0);
12005 
12006 version(use_new_text_system)
12007 class TextDisplayHelper : Widget {
12008 	protected TextLayouter l;
12009 	protected ScrollMessageWidget smw;
12010 
12011 	private const(TextLayouter.State)*[] undoStack;
12012 	private const(TextLayouter.State)*[] redoStack;
12013 
12014 	bool readonly;
12015 	bool caretNavigation; // scroll lock can flip this
12016 	bool singleLine;
12017 	bool acceptsTabInput;
12018 
12019 	private Menu ctx;
12020 	override Menu contextMenu(int x, int y) {
12021 		if(ctx is null) {
12022 			ctx = new Menu("Actions", this);
12023 			ctx.addItem(new MenuItem(new Action("&Undo", GenericIcons.Undo, &undo)));
12024 			ctx.addItem(new MenuItem(new Action("&Redo", GenericIcons.Redo, &redo)));
12025 			ctx.addSeparator();
12026 			ctx.addItem(new MenuItem(new Action("Cu&t", GenericIcons.Cut, &cut)));
12027 			ctx.addItem(new MenuItem(new Action("&Copy", GenericIcons.Copy, &copy)));
12028 			ctx.addItem(new MenuItem(new Action("&Paste", GenericIcons.Paste, &paste)));
12029 			ctx.addItem(new MenuItem(new Action("&Delete", 0, &deleteContentOfSelection)));
12030 			ctx.addSeparator();
12031 			ctx.addItem(new MenuItem(new Action("Select &All", 0, &selectAll)));
12032 		}
12033 		return ctx;
12034 	}
12035 
12036 	override void defaultEventHandler_blur(Event ev) {
12037 		super.defaultEventHandler_blur(ev);
12038 		if(l.wasMutated()) {
12039 			auto evt = new ChangeEvent!string(this, &this.content);
12040 			evt.dispatch();
12041 			l.clearWasMutatedFlag();
12042 		}
12043 	}
12044 
12045 	private string content() {
12046 		return l.getTextString();
12047 	}
12048 
12049 	void undo() {
12050 		if(undoStack.length) {
12051 			auto state = undoStack[$-1];
12052 			undoStack = undoStack[0 .. $-1];
12053 			undoStack.assumeSafeAppend();
12054 			redoStack ~= l.saveState();
12055 			l.restoreState(state);
12056 			adjustScrollbarSizes();
12057 			scrollForCaret();
12058 			redraw();
12059 			stateCheckpoint = true;
12060 		}
12061 	}
12062 
12063 	void redo() {
12064 		if(redoStack.length) {
12065 			doStateCheckpoint();
12066 			auto state = redoStack[$-1];
12067 			redoStack = redoStack[0 .. $-1];
12068 			redoStack.assumeSafeAppend();
12069 			l.restoreState(state);
12070 			adjustScrollbarSizes();
12071 			scrollForCaret();
12072 			redraw();
12073 			stateCheckpoint = true;
12074 		}
12075 	}
12076 
12077 	void cut() {
12078 		with(l.selection()) {
12079 			if(!isEmpty()) {
12080 				setClipboardText(parentWindow.win, getContentString());
12081 				doStateCheckpoint();
12082 				replaceContent("");
12083 				adjustScrollbarSizes();
12084 				scrollForCaret();
12085 				this.redraw();
12086 			}
12087 		}
12088 
12089 	}
12090 
12091 	void copy() {
12092 		with(l.selection()) {
12093 			if(!isEmpty()) {
12094 				setClipboardText(parentWindow.win, getContentString());
12095 				this.redraw();
12096 			}
12097 		}
12098 	}
12099 
12100 	void paste() {
12101 		getClipboardText(parentWindow.win, (txt) {
12102 			doStateCheckpoint();
12103 			l.selection.replaceContent(txt);
12104 			adjustScrollbarSizes();
12105 			scrollForCaret();
12106 			this.redraw();
12107 		});
12108 	}
12109 
12110 	void deleteContentOfSelection() {
12111 		doStateCheckpoint();
12112 		l.selection.replaceContent("");
12113 		l.selection.setUserXCoordinate();
12114 		adjustScrollbarSizes();
12115 		scrollForCaret();
12116 		redraw();
12117 	}
12118 
12119 	void selectAll() {
12120 		with(l.selection) {
12121 			moveToStartOfDocument();
12122 			setAnchor();
12123 			moveToEndOfDocument();
12124 			setFocus();
12125 		}
12126 		redraw();
12127 	}
12128 
12129 	protected bool stateCheckpoint = true;
12130 
12131 	protected void doStateCheckpoint() {
12132 		if(stateCheckpoint) {
12133 			undoStack ~= l.saveState();
12134 			stateCheckpoint = false;
12135 		}
12136 	}
12137 
12138 	protected void adjustScrollbarSizes() {
12139 		// FIXME: will want a content area helper function instead of doing all these subtractions myself
12140 		auto borderWidth = 2;
12141 		this.smw.setTotalArea(l.width, l.height);
12142 		this.smw.setViewableArea(
12143 			this.width - this.paddingLeft - this.paddingRight - borderWidth * 2,
12144 			this.height - this.paddingTop - this.paddingBottom - borderWidth * 2);
12145 	}
12146 
12147 	protected void scrollForCaret() {
12148 		// writeln(l.width, "x", l.height); writeln(this.width - this.paddingLeft - this.paddingRight, " ", this.height - this.paddingTop - this.paddingBottom);
12149 		smw.scrollIntoView(l.selection.focusBoundingBox());
12150 	}
12151 
12152 	// FIXME: this should be a theme changed event listener instead
12153 	private BaseVisualTheme currentTheme;
12154 	override void recomputeChildLayout() {
12155 		if(currentTheme is null)
12156 			currentTheme = WidgetPainter.visualTheme;
12157 		if(WidgetPainter.visualTheme !is currentTheme) {
12158 			currentTheme = WidgetPainter.visualTheme;
12159 			auto ds = this.l.defaultStyle;
12160 			if(auto ms = cast(MyTextStyle) ds) {
12161 				auto cs = getComputedStyle();
12162 				auto font = cs.font();
12163 				if(font !is null)
12164 					ms.font_ = font;
12165 				else {
12166 					auto osc = new OperatingSystemFont();
12167 					osc.loadDefault;
12168 					ms.font_ = osc;
12169 				}
12170 			}
12171 		}
12172 		super.recomputeChildLayout();
12173 	}
12174 
12175 	private Point adjustForSingleLine(Point p) {
12176 		if(singleLine)
12177 			return Point(p.x, this.height / 2);
12178 		else
12179 			return p;
12180 	}
12181 
12182 	private bool wordWrapEnabled_;
12183 
12184 	this(TextLayouter l, ScrollMessageWidget parent) {
12185 		this.smw = parent;
12186 
12187 		smw.addDefaultWheelListeners(16, 16, 8);
12188 		smw.movementPerButtonClick(16, 16);
12189 
12190 		this.defaultPadding = Rectangle(2, 2, 2, 2);
12191 
12192 		this.l = l;
12193 		super(parent);
12194 
12195 		smw.addEventListener((scope ScrollEvent se) {
12196 			this.redraw();
12197 		});
12198 
12199 		bool mouseDown;
12200 
12201 		this.addEventListener((scope ResizeEvent re) {
12202 			// FIXME: I should add a method to give this client area width thing
12203 			if(wordWrapEnabled_)
12204 				this.l.wordWrapWidth = this.width - this.paddingLeft - this.paddingRight;
12205 
12206 			adjustScrollbarSizes();
12207 			scrollForCaret();
12208 
12209 			this.redraw();
12210 		});
12211 
12212 		this.addEventListener((scope KeyDownEvent kde) {
12213 			switch(kde.key) {
12214 				case Key.Up, Key.Down, Key.Left, Key.Right:
12215 				case Key.Home, Key.End:
12216 					stateCheckpoint = true;
12217 					bool setPosition = false;
12218 					switch(kde.key) {
12219 						case Key.Up: l.selection.moveUp(); break;
12220 						case Key.Down: l.selection.moveDown(); break;
12221 						case Key.Left: l.selection.moveLeft(); setPosition = true; break;
12222 						case Key.Right: l.selection.moveRight(); setPosition = true; break;
12223 						case Key.Home: l.selection.moveToStartOfLine(); setPosition = true; break;
12224 						case Key.End: l.selection.moveToEndOfLine(); setPosition = true; break;
12225 						default: assert(0);
12226 					}
12227 
12228 					if(kde.shiftKey)
12229 						l.selection.setFocus();
12230 					else
12231 						l.selection.setAnchor();
12232 					if(setPosition)
12233 						l.selection.setUserXCoordinate();
12234 					scrollForCaret();
12235 					redraw();
12236 				break;
12237 				case Key.PageUp, Key.PageDown:
12238 					// FIXME
12239 					scrollForCaret();
12240 				break;
12241 				case Key.Delete:
12242 					if(l.selection.isEmpty()) {
12243 						l.selection.setAnchor();
12244 						l.selection.moveRight();
12245 						l.selection.setFocus();
12246 					}
12247 					deleteContentOfSelection();
12248 					adjustScrollbarSizes();
12249 					scrollForCaret();
12250 				break;
12251 				case Key.Insert:
12252 				break;
12253 				case Key.A:
12254 					if(kde.ctrlKey)
12255 						selectAll();
12256 				break;
12257 				case Key.F:
12258 					// find
12259 				break;
12260 				case Key.Z:
12261 					if(kde.ctrlKey)
12262 						undo();
12263 				break;
12264 				case Key.R:
12265 					if(kde.ctrlKey)
12266 						redo();
12267 				break;
12268 				case Key.X:
12269 					if(kde.ctrlKey)
12270 						cut();
12271 				break;
12272 				case Key.C:
12273 					if(kde.ctrlKey)
12274 						copy();
12275 				break;
12276 				case Key.V:
12277 					if(kde.ctrlKey)
12278 						paste();
12279 				break;
12280 				case Key.F1:
12281 					with(l.selection()) {
12282 						moveToStartOfLine();
12283 						setAnchor();
12284 						moveToEndOfLine();
12285 						moveToIncludeAdjacentEndOfLineMarker();
12286 						setFocus();
12287 						replaceContent("");
12288 					}
12289 
12290 					redraw();
12291 				break;
12292 				/*
12293 				case Key.F2:
12294 					l.selection().changeStyle((old) => l.registerStyle(new MyTextStyle(
12295 						//(cast(MyTextStyle) old).font,
12296 						font2,
12297 						Color.red)));
12298 					redraw();
12299 				break;
12300 				*/
12301 				case Key.Tab:
12302 					// we process the char event, so don't want to change focus on it
12303 					if(acceptsTabInput)
12304 						kde.preventDefault();
12305 				break;
12306 				default:
12307 			}
12308 		});
12309 
12310 		Point downAt;
12311 
12312 		static if(UsingSimpledisplayX11)
12313 		this.addEventListener((scope ClickEvent ce) {
12314 			if(ce.button == MouseButton.middle) {
12315 				parentWindow.win.getPrimarySelection((txt) {
12316 					l.selection.replaceContent(txt);
12317 					redraw();
12318 				});
12319 			}
12320 		});
12321 
12322 		this.addEventListener((scope MouseDownEvent ce) {
12323 			if(ce.button == MouseButton.left) {
12324 				downAt = Point(ce.clientX - this.paddingLeft, ce.clientY - this.paddingTop);
12325 				l.selection.moveTo(adjustForSingleLine(smw.position + downAt));
12326 				l.selection.setAnchor();
12327 				mouseDown = true;
12328 				parentWindow.captureMouse(this);
12329 				this.redraw();
12330 			} else if(ce.button == MouseButton.right) {
12331 				this.showContextMenu(ce.clientX, ce.clientY);
12332 			}
12333 			//writeln(ce.clientX, ", ", ce.clientY, " = ", l.offsetOfClick(Point(ce.clientX, ce.clientY)));
12334 		});
12335 
12336 		Timer autoscrollTimer;
12337 		int autoscrollDirection;
12338 		int autoscrollAmount;
12339 
12340 		void autoscroll() {
12341 			switch(autoscrollDirection) {
12342 				case 0: smw.scrollUp(autoscrollAmount); break;
12343 				case 1: smw.scrollDown(autoscrollAmount); break;
12344 				case 2: smw.scrollLeft(autoscrollAmount); break;
12345 				case 3: smw.scrollRight(autoscrollAmount); break;
12346 				default: assert(0);
12347 			}
12348 
12349 			this.redraw();
12350 		}
12351 
12352 		void setAutoscrollTimer(int direction, int amount) {
12353 			if(autoscrollTimer is null) {
12354 				autoscrollTimer = new Timer(1000 / 60, &autoscroll);
12355 			}
12356 
12357 			autoscrollDirection = direction;
12358 			autoscrollAmount = amount;
12359 		}
12360 
12361 		void stopAutoscrollTimer() {
12362 			if(autoscrollTimer !is null) {
12363 				autoscrollTimer.dispose();
12364 				autoscrollTimer = null;
12365 			}
12366 			autoscrollAmount = 0;
12367 			autoscrollDirection = 0;
12368 		}
12369 
12370 		this.addEventListener((scope MouseMoveEvent ce) {
12371 			if(mouseDown) {
12372 				auto movedTo = Point(ce.clientX - this.paddingLeft, ce.clientY - this.paddingTop);
12373 
12374 				// FIXME: when scrolling i actually do want a timer.
12375 				// i also want a zone near the sides of the window where i can auto scroll
12376 
12377 				auto scrollMultiplier = scaleWithDpi(16);
12378 				auto scrollDivisor = scaleWithDpi(16); // if you go more than 64px up it will scroll faster
12379 
12380 				if(!singleLine && movedTo.y < 4) {
12381 					setAutoscrollTimer(0, scrollMultiplier * -(movedTo.y-4) / scrollDivisor);
12382 				} else
12383 				if(!singleLine && (movedTo.y + 6) > this.height) {
12384 					setAutoscrollTimer(1, scrollMultiplier * (movedTo.y + 6 - this.height) / scrollDivisor);
12385 				} else
12386 				if(movedTo.x < 4) {
12387 					setAutoscrollTimer(2, scrollMultiplier * -(movedTo.x-4) / scrollDivisor);
12388 				} else
12389 				if((movedTo.x + 6) > this.width) {
12390 					setAutoscrollTimer(3, scrollMultiplier * (movedTo.x + 6 - this.width) / scrollDivisor);
12391 				} else
12392 					stopAutoscrollTimer();
12393 
12394 				l.selection.moveTo(adjustForSingleLine(smw.position + movedTo));
12395 				l.selection.setFocus();
12396 				this.redraw();
12397 			}
12398 		});
12399 
12400 		this.addEventListener((scope MouseUpEvent ce) {
12401 			// FIXME: assert primary selection
12402 			if(mouseDown && ce.button == MouseButton.left) {
12403 				stateCheckpoint = true;
12404 				//l.selection.moveTo(adjustForSingleLine(smw.position + Point(ce.clientX - this.paddingLeft, ce.clientY - this.paddingTop)));
12405 				//l.selection.setFocus();
12406 				mouseDown = false;
12407 				parentWindow.releaseMouseCapture();
12408 				stopAutoscrollTimer();
12409 				this.redraw();
12410 			}
12411 			//writeln(ce.clientX, ", ", ce.clientY, " = ", l.offsetOfClick(Point(ce.clientX, ce.clientY)));
12412 		});
12413 
12414 		this.addEventListener((scope CharEvent ce) {
12415 			if(readonly)
12416 				return;
12417 			if(ce.character < 32 && ce.character != '\t' && ce.character != '\n' && ce.character != '\b')
12418 				return; // skip the ctrl+x characters we don't care about as plain text
12419 
12420 			if(singleLine && ce.character == '\n')
12421 				return;
12422 			if(!acceptsTabInput && ce.character == '\t')
12423 				return;
12424 
12425 			doStateCheckpoint();
12426 
12427 			char[4] buffer;
12428 			import std.utf; // FIXME: i should remove this. compile time not significant but the logs get spammed with phobos' import web
12429 			auto stride = encode(buffer, ce.character);
12430 			l.selection.replaceContent(buffer[0 .. stride]);
12431 			l.selection.setUserXCoordinate();
12432 			adjustScrollbarSizes();
12433 			scrollForCaret();
12434 			redraw();
12435 		});
12436 	}
12437 
12438 	static class Style : Widget.Style {
12439 		override WidgetBackground background() {
12440 			return WidgetBackground(WidgetPainter.visualTheme.widgetBackgroundColor);
12441 		}
12442 
12443 		override Color foregroundColor() {
12444 			return WidgetPainter.visualTheme.foregroundColor;
12445 		}
12446 
12447 		override FrameStyle borderStyle() {
12448 			return FrameStyle.sunk;
12449 		}
12450 
12451 		override MouseCursor cursor() {
12452 			return GenericCursor.Text;
12453 		}
12454 	}
12455 	mixin OverrideStyle!Style;
12456 
12457 	override int minHeight() { return borderBoxForContentBox(Rectangle(Point(0, 0), Size(0, defaultTextHeight))).height; }
12458 	override int maxHeight() {
12459 		if(singleLine)
12460 			return minHeight;
12461 		else
12462 			return super.maxHeight();
12463 	}
12464 
12465 	void drawTextSegment(WidgetPainter painter, Point upperLeft, scope const(char)[] text) {
12466 		painter.drawText(upperLeft, text);
12467 	}
12468 
12469 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
12470 		//painter.setFont(font);
12471 
12472 		auto cs = getComputedStyle();
12473 		auto defaultColor = cs.foregroundColor;
12474 
12475 		auto old = painter.setClipRectangle(bounds);
12476 		scope(exit) painter.setClipRectangle(old);
12477 
12478 		l.getDrawableText(delegate bool(txt, style, info, carets...) {
12479 			//writeln("Segment: ", txt);
12480 			assert(style !is null);
12481 
12482 			auto myStyle = cast(MyTextStyle) style;
12483 			assert(myStyle !is null);
12484 
12485 			painter.setFont(myStyle.font);
12486 			// defaultColor = myStyle.color; // FIXME: so wrong
12487 
12488 			if(info.selections && info.boundingBox.width > 0) {
12489 				auto color = this.isFocused ? cs.selectionBackgroundColor : Color(128, 128, 128); // FIXME don't hardcode
12490 				painter.fillColor = color;
12491 				painter.outlineColor = color;
12492 				painter.drawRectangle(Rectangle(info.boundingBox.upperLeft - smw.position() + bounds.upperLeft, info.boundingBox.size));
12493 				painter.outlineColor = cs.selectionForegroundColor;
12494 				//painter.fillColor = Color.white;
12495 			} else {
12496 				painter.outlineColor = defaultColor;
12497 			}
12498 
12499 			if(this.isFocused)
12500 			foreach(idx, caret; carets) {
12501 				if(idx == 0)
12502 					painter.notifyCursorPosition(caret.boundingBox.left - smw.position.x + bounds.left, caret.boundingBox.top - smw.position.y + bounds.top, caret.boundingBox.width, caret.boundingBox.height);
12503 				painter.drawLine(
12504 					caret.boundingBox.upperLeft + bounds.upperLeft - smw.position(),
12505 					bounds.upperLeft + Point(caret.boundingBox.left, caret.boundingBox.bottom) - smw.position()
12506 				);
12507 			}
12508 
12509 			if(txt.stripInternal.length) {
12510 				drawTextSegment(painter, info.boundingBox.upperLeft - smw.position() + bounds.upperLeft, txt.stripRightInternal);
12511 			}
12512 
12513 			if(info.boundingBox.upperLeft.y - smw.position().y > this.height) {
12514 				return false;
12515 			} else {
12516 				return true;
12517 			}
12518 		}, Rectangle(smw.position(), bounds.size));
12519 
12520 		/+
12521 		int place = 0;
12522 		int y = 75;
12523 		foreach(width; widths) {
12524 			painter.fillColor = Color.red;
12525 			painter.drawRectangle(Point(place, y), Size(width, 75));
12526 			//y += 15;
12527 			place += width;
12528 		}
12529 		+/
12530 
12531 		return bounds;
12532 	}
12533 
12534 	static class MyTextStyle : TextStyle {
12535 		OperatingSystemFont font_;
12536 		this(OperatingSystemFont font, bool passwordMode = false) {
12537 			this.font_ = font;
12538 		}
12539 
12540 		override OperatingSystemFont font() {
12541 			return font_;
12542 		}
12543 	}
12544 }
12545 
12546 /+
12547 version(use_new_text_system)
12548 class TextWidget : Widget {
12549 	TextLayouter l;
12550 	ScrollMessageWidget smw;
12551 	TextDisplayHelper helper;
12552 	this(TextLayouter l, Widget parent) {
12553 		this.l = l;
12554 		super(parent);
12555 
12556 		smw = new ScrollMessageWidget(this);
12557 		//smw.horizontalScrollBar.hide;
12558 		//smw.verticalScrollBar.hide;
12559 		smw.addDefaultWheelListeners(16, 16, 8);
12560 		smw.movementPerButtonClick(16, 16);
12561 		helper = new TextDisplayHelper(l, smw);
12562 
12563 		// no need to do this here since there's gonna be a resize
12564 		// event immediately before any drawing
12565 		// smw.setTotalArea(l.width, l.height);
12566 		smw.setViewableArea(
12567 			this.width - this.paddingLeft - this.paddingRight,
12568 			this.height - this.paddingTop - this.paddingBottom);
12569 
12570 		/+
12571 		writeln(l.width, "x", l.height);
12572 		+/
12573 	}
12574 }
12575 +/
12576 
12577 
12578 
12579 
12580 /+
12581 	This awful thing has to be rewritten. And it needs to takecare of parentWindow.inputProxy.setIMEPopupLocation too
12582 +/
12583 
12584 /// Contains the implementation of text editing
12585 abstract class EditableTextWidget : EditableTextWidgetParent {
12586 	this(Widget parent) {
12587 		super(parent);
12588 
12589 		version(custom_widgets)
12590 			setupCustomTextEditing();
12591 	}
12592 
12593 	private bool wordWrapEnabled_;
12594 	void wordWrapEnabled(bool enabled) {
12595 		version(win32_widgets) {
12596 			SendMessageW(hwnd, EM_FMTLINES, enabled ? 1 : 0, 0);
12597 		} else version(custom_widgets) {
12598 			wordWrapEnabled_ = enabled;
12599 			version(use_new_text_system)
12600 			textLayout.wordWrapWidth = enabled ? this.width : 0; // FIXME
12601 		} else static assert(false);
12602 	}
12603 
12604 	override int minWidth() { return scaleWithDpi(16); }
12605 	override int widthStretchiness() { return 7; }
12606 
12607 	version(use_new_text_system)
12608 	override int maxHeight() { return tdh.maxHeight; }
12609 
12610 	version(use_new_text_system)
12611 	override void focus() { if(tdh) tdh.focus(); else super.focus(); }
12612 
12613 	void selectAll() {
12614 		version(win32_widgets)
12615 			SendMessage(hwnd, EM_SETSEL, 0, -1);
12616 		else version(custom_widgets) {
12617 			version(use_new_text_system)
12618 				tdh.selectAll();
12619 			else
12620 				textLayout.selectAll();
12621 			redraw();
12622 		}
12623 	}
12624 
12625 	version(use_new_text_system)
12626 		TextDisplayHelper tdh;
12627 
12628 	@property string content() {
12629 		version(win32_widgets) {
12630 			wchar[4096] bufferstack;
12631 			wchar[] buffer;
12632 			auto len = GetWindowTextLength(hwnd);
12633 			if(len < bufferstack.length)
12634 				buffer = bufferstack[0 .. len + 1];
12635 			else
12636 				buffer = new wchar[](len + 1);
12637 
12638 			auto l = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
12639 			if(l >= 0)
12640 				return makeUtf8StringFromWindowsString(buffer[0 .. l]);
12641 			else
12642 				return null;
12643 		} else version(custom_widgets) {
12644 			version(use_new_text_system) {
12645 				return textLayout.getTextString();
12646 			} else
12647 				return textLayout.getPlainText();
12648 		} else static assert(false);
12649 	}
12650 	@property void content(string s) {
12651 		version(win32_widgets) {
12652 			WCharzBuffer bfr = WCharzBuffer(s, WindowsStringConversionFlags.convertNewLines);
12653 			SetWindowTextW(hwnd, bfr.ptr);
12654 		} else version(custom_widgets) {
12655 			version(use_new_text_system) {
12656 				selectAll();
12657 				textLayout.selection.replaceContent(s);
12658 
12659 				tdh.adjustScrollbarSizes();
12660 				// these don't seem to help
12661 				// tdh.smw.setPosition(0, 0);
12662 				// tdh.scrollForCaret();
12663 
12664 				redraw();
12665 			} else {
12666 				textLayout.clear();
12667 				textLayout.addText(s);
12668 
12669 				{
12670 				// FIXME: it should be able to get this info easier
12671 				auto painter = draw();
12672 				textLayout.redoLayout(painter);
12673 				}
12674 				auto cbb = textLayout.contentBoundingBox();
12675 				setContentSize(cbb.width, cbb.height);
12676 				/*
12677 				textLayout.addText(ForegroundColor.red, s);
12678 				textLayout.addText(ForegroundColor.blue, TextFormat.underline, "http://dpldocs.info/");
12679 				textLayout.addText(" is the best!");
12680 				*/
12681 				redraw();
12682 			}
12683 		}
12684 		else static assert(false);
12685 	}
12686 
12687 	void addText(string txt) {
12688 		version(custom_widgets) {
12689 			version(use_new_text_system) {
12690 				textLayout.appendText(txt);
12691 				tdh.adjustScrollbarSizes();
12692 				redraw();
12693 			} else {
12694 				textLayout.addText(txt);
12695 
12696 				{
12697 				// FIXME: it should be able to get this info easier
12698 				auto painter = draw();
12699 				textLayout.redoLayout(painter);
12700 				}
12701 				auto cbb = textLayout.contentBoundingBox();
12702 				setContentSize(cbb.width, cbb.height);
12703 			}
12704 		} else version(win32_widgets) {
12705 			// get the current selection
12706 			DWORD StartPos, EndPos;
12707 			SendMessageW( hwnd, EM_GETSEL, cast(WPARAM)(&StartPos), cast(LPARAM)(&EndPos) );
12708 
12709 			// move the caret to the end of the text
12710 			int outLength = GetWindowTextLengthW(hwnd);
12711 			SendMessageW( hwnd, EM_SETSEL, outLength, outLength );
12712 
12713 			// insert the text at the new caret position
12714 			WCharzBuffer bfr = WCharzBuffer(txt, WindowsStringConversionFlags.convertNewLines);
12715 			SendMessageW( hwnd, EM_REPLACESEL, TRUE, cast(LPARAM) bfr.ptr );
12716 
12717 			// restore the previous selection
12718 			SendMessageW( hwnd, EM_SETSEL, StartPos, EndPos );
12719 		} else static assert(0);
12720 	}
12721 
12722 	version(custom_widgets)
12723 	version(trash_text)
12724 	override void paintFrameAndBackground(WidgetPainter painter) {
12725 		this.draw3dFrame(painter, FrameStyle.sunk, Color.white);
12726 	}
12727 
12728 	version(use_new_text_system)
12729 	TextDisplayHelper textDisplayHelperFactory(TextLayouter textLayout, ScrollMessageWidget smw) {
12730 		return new TextDisplayHelper(textLayout, smw);
12731 	}
12732 
12733 	version(use_new_text_system)
12734 	TextStyle defaultTextStyle() {
12735 		return new TextDisplayHelper.MyTextStyle(getUsedFont());
12736 	}
12737 
12738 	version(use_new_text_system)
12739 	private OperatingSystemFont getUsedFont() {
12740 		auto cs = getComputedStyle();
12741 		auto font = cs.font;
12742 		if(font is null) {
12743 			font = new OperatingSystemFont;
12744 			font.loadDefault();
12745 		}
12746 		return font;
12747 	}
12748 
12749 	version(win32_widgets) { /* will do it with Windows calls in the classes */ }
12750 	else version(custom_widgets) {
12751 		// FIXME
12752 		version(use_new_text_system) {
12753 			TextLayouter textLayout;
12754 
12755 			void setupCustomTextEditing() {
12756 				textLayout = new TextLayouter(defaultTextStyle());
12757 				auto smw = new ScrollMessageWidget(this);
12758 				if(!showingHorizontalScroll)
12759 					smw.horizontalScrollBar.hide();
12760 				if(!showingVerticalScroll)
12761 					smw.verticalScrollBar.hide();
12762 				this.tabStop = false;
12763 				smw.tabStop = false;
12764 				tdh = textDisplayHelperFactory(textLayout, smw);
12765 
12766 				this.parentWindow.addEventListener((scope DpiChangedEvent dce) {
12767 					if(textLayout) {
12768 						if(auto style = cast(TextDisplayHelper.MyTextStyle) textLayout.defaultStyle()) {
12769 							// the dpi change can change the font, so this informs the layouter that it has changed too
12770 							style.font_ = getUsedFont();
12771 
12772 							// arsd.core.writeln(this.parentWindow.win.actualDpi);
12773 						}
12774 					}
12775 				});
12776 			}
12777 
12778 		} else {
12779 
12780 			static if(SimpledisplayTimerAvailable)
12781 				Timer caretTimer;
12782 			etc.TextLayout textLayout;
12783 
12784 			void setupCustomTextEditing() {
12785 				textLayout = new etc.TextLayout(Rectangle(4, 2, width - 8, height - 4));
12786 				textLayout.selectionXorColor = getComputedStyle().activeListXorColor;
12787 			}
12788 
12789 			override void paint(WidgetPainter painter) {
12790 				if(parentWindow.win.closed) return;
12791 
12792 				textLayout.boundingBox = Rectangle(4, 2, width - 8, height - 4);
12793 
12794 				/*
12795 				painter.outlineColor = Color.white;
12796 				painter.fillColor = Color.white;
12797 				painter.drawRectangle(Point(4, 4), contentWidth, contentHeight);
12798 				*/
12799 
12800 				painter.outlineColor = Color.black;
12801 				// painter.drawText(Point(4, 4), content, Point(width - 4, height - 4));
12802 
12803 				textLayout.caretShowingOnScreen = false;
12804 
12805 				textLayout.drawInto(painter, !parentWindow.win.closed && isFocused());
12806 			}
12807 		}
12808 
12809 		static class Style : Widget.Style {
12810 			override FrameStyle borderStyle() {
12811 				return FrameStyle.sunk;
12812 			}
12813 			override MouseCursor cursor() {
12814 				return GenericCursor.Text;
12815 			}
12816 		}
12817 		mixin OverrideStyle!Style;
12818 	}
12819 	else static assert(false);
12820 
12821 	version(trash_text)
12822 	version(custom_widgets)
12823 	override void defaultEventHandler_mousedown(MouseDownEvent ev) {
12824 		super.defaultEventHandler_mousedown(ev);
12825 		if(parentWindow.win.closed) return;
12826 		if(ev.button == MouseButton.left) {
12827 			if(textLayout.selectNone())
12828 				redraw();
12829 			textLayout.moveCaretToPixelCoordinates(ev.clientX, ev.clientY);
12830 			this.focus();
12831 			//this.parentWindow.win.grabInput();
12832 		} else if(ev.button == MouseButton.middle) {
12833 			static if(UsingSimpledisplayX11) {
12834 				getPrimarySelection(parentWindow.win, (in char[] txt) {
12835 					textLayout.insert(txt);
12836 					redraw();
12837 
12838 					auto cbb = textLayout.contentBoundingBox();
12839 					setContentSize(cbb.width, cbb.height);
12840 				});
12841 			}
12842 		}
12843 	}
12844 
12845 	version(trash_text)
12846 	version(custom_widgets)
12847 	override void defaultEventHandler_mouseup(MouseUpEvent ev) {
12848 		//this.parentWindow.win.releaseInputGrab();
12849 		super.defaultEventHandler_mouseup(ev);
12850 	}
12851 
12852 	version(trash_text)
12853 	version(custom_widgets)
12854 	override void defaultEventHandler_mousemove(MouseMoveEvent ev) {
12855 		super.defaultEventHandler_mousemove(ev);
12856 		if(ev.state & ModifierState.leftButtonDown) {
12857 			textLayout.selectToPixelCoordinates(ev.clientX, ev.clientY);
12858 			redraw();
12859 		}
12860 	}
12861 
12862 	version(trash_text)
12863 	version(custom_widgets)
12864 	override void defaultEventHandler_focus(Event ev) {
12865 		super.defaultEventHandler_focus(ev);
12866 		if(parentWindow.win.closed) return;
12867 		auto painter = this.draw();
12868 		textLayout.drawCaret(painter);
12869 
12870 		static if(SimpledisplayTimerAvailable)
12871 		if(caretTimer) {
12872 			caretTimer.destroy();
12873 			caretTimer = null;
12874 		}
12875 
12876 		bool blinkingCaret = true;
12877 		static if(UsingSimpledisplayX11)
12878 			if(!Image.impl.xshmAvailable)
12879 				blinkingCaret = false; // if on a remote connection, don't waste bandwidth on an expendable blink
12880 
12881 		if(blinkingCaret)
12882 		static if(SimpledisplayTimerAvailable)
12883 		caretTimer = new Timer(500, {
12884 			if(parentWindow.win.closed) {
12885 				caretTimer.destroy();
12886 				return;
12887 			}
12888 			if(isFocused()) {
12889 				auto painter = this.draw();
12890 				textLayout.drawCaret(painter);
12891 			} else if(textLayout.caretShowingOnScreen) {
12892 				auto painter = this.draw();
12893 				textLayout.eraseCaret(painter);
12894 			}
12895 		});
12896 	}
12897 
12898 	version(trash_text) {
12899 		private string lastContentBlur;
12900 
12901 		override void defaultEventHandler_blur(Event ev) {
12902 			super.defaultEventHandler_blur(ev);
12903 			if(parentWindow.win.closed) return;
12904 			version(custom_widgets) {
12905 				auto painter = this.draw();
12906 				textLayout.eraseCaret(painter);
12907 				static if(SimpledisplayTimerAvailable)
12908 				if(caretTimer) {
12909 					caretTimer.destroy();
12910 					caretTimer = null;
12911 				}
12912 			}
12913 
12914 			if(this.content != lastContentBlur) {
12915 				auto evt = new ChangeEvent!string(this, &this.content);
12916 				evt.dispatch();
12917 				lastContentBlur = this.content;
12918 			}
12919 		}
12920 	}
12921 
12922 	version(win32_widgets) {
12923 		private string lastContentBlur;
12924 
12925 		override void defaultEventHandler_blur(Event ev) {
12926 			super.defaultEventHandler_blur(ev);
12927 
12928 			if(this.content != lastContentBlur) {
12929 				auto evt = new ChangeEvent!string(this, &this.content);
12930 				evt.dispatch();
12931 				lastContentBlur = this.content;
12932 			}
12933 		}
12934 	}
12935 
12936 
12937 	version(trash_text)
12938 	version(custom_widgets)
12939 	override void defaultEventHandler_char(CharEvent ev) {
12940 		super.defaultEventHandler_char(ev);
12941 		textLayout.insert(ev.character);
12942 		redraw();
12943 
12944 		// FIXME: too inefficient
12945 		auto cbb = textLayout.contentBoundingBox();
12946 		setContentSize(cbb.width, cbb.height);
12947 	}
12948 	version(trash_text)
12949 	version(custom_widgets)
12950 	override void defaultEventHandler_keydown(KeyDownEvent ev) {
12951 		//super.defaultEventHandler_keydown(ev);
12952 		switch(ev.key) {
12953 			case Key.Delete:
12954 				textLayout.delete_();
12955 				redraw();
12956 			break;
12957 			case Key.Left:
12958 				textLayout.moveLeft();
12959 				redraw();
12960 			break;
12961 			case Key.Right:
12962 				textLayout.moveRight();
12963 				redraw();
12964 			break;
12965 			case Key.Up:
12966 				textLayout.moveUp();
12967 				redraw();
12968 			break;
12969 			case Key.Down:
12970 				textLayout.moveDown();
12971 				redraw();
12972 			break;
12973 			case Key.Home:
12974 				textLayout.moveHome();
12975 				redraw();
12976 			break;
12977 			case Key.End:
12978 				textLayout.moveEnd();
12979 				redraw();
12980 			break;
12981 			case Key.PageUp:
12982 				foreach(i; 0 .. 32)
12983 				textLayout.moveUp();
12984 				redraw();
12985 			break;
12986 			case Key.PageDown:
12987 				foreach(i; 0 .. 32)
12988 				textLayout.moveDown();
12989 				redraw();
12990 			break;
12991 
12992 			default:
12993 				 {} // intentionally blank, let "char" handle it
12994 		}
12995 		/*
12996 		if(ev.key == Key.Backspace) {
12997 			textLayout.backspace();
12998 			redraw();
12999 		}
13000 		*/
13001 		ensureVisibleInScroll(textLayout.caretBoundingBox());
13002 	}
13003 
13004 	version(use_new_text_system) {
13005 		bool showingVerticalScroll() { return true; }
13006 		bool showingHorizontalScroll() { return true; }
13007 	}
13008 }
13009 
13010 ///
13011 class LineEdit : EditableTextWidget {
13012 	// FIXME: hack
13013 	version(custom_widgets) {
13014 	override bool showingVerticalScroll() { return false; }
13015 	override bool showingHorizontalScroll() { return false; }
13016 	}
13017 
13018 	override int flexBasisWidth() { return 250; }
13019 
13020 	///
13021 	this(Widget parent) {
13022 		super(parent);
13023 		version(win32_widgets) {
13024 			createWin32Window(this, "edit"w, "",
13025 				0, WS_EX_CLIENTEDGE);//|WS_HSCROLL|ES_AUTOHSCROLL);
13026 		} else version(custom_widgets) {
13027 			version(trash_text) {
13028 				setupCustomTextEditing();
13029 				addEventListener(delegate(CharEvent ev) {
13030 					if(ev.character == '\n')
13031 						ev.preventDefault();
13032 				});
13033 			}
13034 		} else static assert(false);
13035 	}
13036 
13037 	version(use_new_text_system)
13038 	override TextDisplayHelper textDisplayHelperFactory(TextLayouter textLayout, ScrollMessageWidget smw) {
13039 		auto tdh = new TextDisplayHelper(textLayout, smw);
13040 		tdh.singleLine = true;
13041 		return tdh;
13042 	}
13043 
13044 	version(win32_widgets) {
13045 		mixin Padding!q{2};
13046 		override int minHeight() { return borderBoxForContentBox(Rectangle(Point(0, 0), Size(0, defaultLineHeight))).height; }
13047 		override int maxHeight() { return minHeight; }
13048 	}
13049 
13050 	/+
13051 	@property void passwordMode(bool p) {
13052 		SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) | ES_PASSWORD);
13053 	}
13054 	+/
13055 }
13056 
13057 /++
13058 	A [LineEdit] that displays `*` in place of the actual characters.
13059 
13060 	Alas, Windows requires the window to be created differently to use this style,
13061 	so it had to be a new class instead of a toggle on and off on an existing object.
13062 
13063 	FIXME: this is not yet implemented on Linux, it will work the same as a TextEdit there for now.
13064 
13065 	History:
13066 		Added January 24, 2021
13067 +/
13068 class PasswordEdit : EditableTextWidget {
13069 	version(custom_widgets) {
13070 	override bool showingVerticalScroll() { return false; }
13071 	override bool showingHorizontalScroll() { return false; }
13072 	}
13073 
13074 	override int flexBasisWidth() { return 250; }
13075 
13076 	version(use_new_text_system)
13077 	override TextStyle defaultTextStyle() {
13078 		auto cs = getComputedStyle();
13079 
13080 		auto osf = new class OperatingSystemFont {
13081 			this() {
13082 				super(cs.font);
13083 			}
13084 			override int stringWidth(scope const(char)[] text, SimpleWindow window = null) {
13085 				int count = 0;
13086 				foreach(dchar ch; text)
13087 					count++;
13088 				return count * super.stringWidth("*", window);
13089 			}
13090 		};
13091 
13092 		return new TextDisplayHelper.MyTextStyle(osf);
13093 	}
13094 
13095 	version(use_new_text_system)
13096 	override TextDisplayHelper textDisplayHelperFactory(TextLayouter textLayout, ScrollMessageWidget smw) {
13097 		static class TDH : TextDisplayHelper {
13098 			this(TextLayouter textLayout, ScrollMessageWidget smw) {
13099 				singleLine = true;
13100 				super(textLayout, smw);
13101 			}
13102 
13103 			override void drawTextSegment(WidgetPainter painter, Point upperLeft, scope const(char)[] text) {
13104 				char[256] buffer = void;
13105 				int bufferLength = 0;
13106 				foreach(dchar ch; text)
13107 					buffer[bufferLength++] = '*';
13108 				painter.drawText(upperLeft, buffer[0..bufferLength]);
13109 			}
13110 		}
13111 
13112 		return new TDH(textLayout, smw);
13113 	}
13114 
13115 	///
13116 	this(Widget parent) {
13117 		super(parent);
13118 		version(win32_widgets) {
13119 			createWin32Window(this, "edit"w, "",
13120 				ES_PASSWORD, WS_EX_CLIENTEDGE);//|WS_HSCROLL|ES_AUTOHSCROLL);
13121 		} else version(custom_widgets) {
13122 			version(trash_text)
13123 			setupCustomTextEditing();
13124 			addEventListener(delegate(CharEvent ev) {
13125 				if(ev.character == '\n')
13126 					ev.preventDefault();
13127 			});
13128 		} else static assert(false);
13129 	}
13130 	version(win32_widgets) {
13131 		mixin Padding!q{2};
13132 		override int minHeight() { return borderBoxForContentBox(Rectangle(Point(0, 0), Size(0, defaultLineHeight))).height; }
13133 		override int maxHeight() { return minHeight; }
13134 	}
13135 }
13136 
13137 
13138 ///
13139 class TextEdit : EditableTextWidget {
13140 	///
13141 	this(Widget parent) {
13142 		super(parent);
13143 		version(win32_widgets) {
13144 			createWin32Window(this, "edit"w, "",
13145 				0|WS_VSCROLL|WS_HSCROLL|ES_MULTILINE|ES_WANTRETURN|ES_AUTOHSCROLL|ES_AUTOVSCROLL, WS_EX_CLIENTEDGE);
13146 		} else version(custom_widgets) {
13147 			version(trash_text)
13148 			setupCustomTextEditing();
13149 		} else static assert(false);
13150 	}
13151 	override int maxHeight() { return int.max; }
13152 	override int heightStretchiness() { return 7; }
13153 
13154 	override int flexBasisWidth() { return 250; }
13155 	override int flexBasisHeight() { return 25; }
13156 }
13157 
13158 
13159 /++
13160 
13161 +/
13162 version(none)
13163 class RichTextDisplay : Widget {
13164 	@property void content(string c) {}
13165 	void appendContent(string c) {}
13166 }
13167 
13168 ///
13169 class MessageBox : Window {
13170 	private string message;
13171 	MessageBoxButton buttonPressed = MessageBoxButton.None;
13172 	///
13173 	this(string message, string[] buttons = ["OK"], MessageBoxButton[] buttonIds = [MessageBoxButton.OK]) {
13174 		super(300, 100);
13175 
13176 		assert(buttons.length);
13177 		assert(buttons.length ==  buttonIds.length);
13178 
13179 		this.message = message;
13180 
13181 		auto label = new TextLabel(message, TextAlignment.Center, this);
13182 
13183 		auto hl = new HorizontalLayout(this);
13184 		auto spacer = new HorizontalSpacer(hl); // to right align
13185 
13186 		foreach(idx, buttonText; buttons) {
13187 			auto button = new CommandButton(buttonText, hl);
13188 
13189 			button.addEventListener(EventType.triggered, ((size_t idx) { return () {
13190 				this.buttonPressed = buttonIds[idx];
13191 				win.close();
13192 			}; })(idx));
13193 
13194 			if(idx == 0)
13195 				button.focus();
13196 		}
13197 
13198 		if(buttons.length == 1)
13199 			auto spacer2 = new HorizontalSpacer(hl); // to center it
13200 
13201 		win.resize(scaleWithDpi(300), this.minHeight());
13202 
13203 		win.show();
13204 		redraw();
13205 	}
13206 
13207 	mixin Padding!q{16};
13208 }
13209 
13210 ///
13211 enum MessageBoxStyle {
13212 	OK, ///
13213 	OKCancel, ///
13214 	RetryCancel, ///
13215 	YesNo, ///
13216 	YesNoCancel, ///
13217 	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.
13218 }
13219 
13220 ///
13221 enum MessageBoxIcon {
13222 	None, ///
13223 	Info, ///
13224 	Warning, ///
13225 	Error ///
13226 }
13227 
13228 /// Identifies the button the user pressed on a message box.
13229 enum MessageBoxButton {
13230 	None, /// The user closed the message box without clicking any of the buttons.
13231 	OK, ///
13232 	Cancel, ///
13233 	Retry, ///
13234 	Yes, ///
13235 	No, ///
13236 	Continue ///
13237 }
13238 
13239 
13240 /++
13241 	Displays a modal message box, blocking until the user dismisses it.
13242 
13243 	Returns: the button pressed.
13244 +/
13245 MessageBoxButton messageBox(string title, string message, MessageBoxStyle style = MessageBoxStyle.OK, MessageBoxIcon icon = MessageBoxIcon.None) {
13246 	version(win32_widgets) {
13247 		WCharzBuffer t = WCharzBuffer(title);
13248 		WCharzBuffer m = WCharzBuffer(message);
13249 		UINT type;
13250 		with(MessageBoxStyle)
13251 		final switch(style) {
13252 			case OK: type |= MB_OK; break;
13253 			case OKCancel: type |= MB_OKCANCEL; break;
13254 			case RetryCancel: type |= MB_RETRYCANCEL; break;
13255 			case YesNo: type |= MB_YESNO; break;
13256 			case YesNoCancel: type |= MB_YESNOCANCEL; break;
13257 			case RetryCancelContinue: type |= MB_CANCELTRYCONTINUE; break;
13258 		}
13259 		with(MessageBoxIcon)
13260 		final switch(icon) {
13261 			case None: break;
13262 			case Info: type |= MB_ICONINFORMATION; break;
13263 			case Warning: type |= MB_ICONWARNING; break;
13264 			case Error: type |= MB_ICONERROR; break;
13265 		}
13266 		switch(MessageBoxW(null, m.ptr, t.ptr, type)) {
13267 			case IDOK: return MessageBoxButton.OK;
13268 			case IDCANCEL: return MessageBoxButton.Cancel;
13269 			case IDTRYAGAIN, IDRETRY: return MessageBoxButton.Retry;
13270 			case IDYES: return MessageBoxButton.Yes;
13271 			case IDNO: return MessageBoxButton.No;
13272 			case IDCONTINUE: return MessageBoxButton.Continue;
13273 			default: return MessageBoxButton.None;
13274 		}
13275 	} else {
13276 		string[] buttons;
13277 		MessageBoxButton[] buttonIds;
13278 		with(MessageBoxStyle)
13279 		final switch(style) {
13280 			case OK:
13281 				buttons = ["OK"];
13282 				buttonIds = [MessageBoxButton.OK];
13283 			break;
13284 			case OKCancel:
13285 				buttons = ["OK", "Cancel"];
13286 				buttonIds = [MessageBoxButton.OK, MessageBoxButton.Cancel];
13287 			break;
13288 			case RetryCancel:
13289 				buttons = ["Retry", "Cancel"];
13290 				buttonIds = [MessageBoxButton.Retry, MessageBoxButton.Cancel];
13291 			break;
13292 			case YesNo:
13293 				buttons = ["Yes", "No"];
13294 				buttonIds = [MessageBoxButton.Yes, MessageBoxButton.No];
13295 			break;
13296 			case YesNoCancel:
13297 				buttons = ["Yes", "No", "Cancel"];
13298 				buttonIds = [MessageBoxButton.Yes, MessageBoxButton.No, MessageBoxButton.Cancel];
13299 			break;
13300 			case RetryCancelContinue:
13301 				buttons = ["Try Again", "Cancel", "Continue"];
13302 				buttonIds = [MessageBoxButton.Retry, MessageBoxButton.Cancel, MessageBoxButton.Continue];
13303 			break;
13304 		}
13305 		auto mb = new MessageBox(message, buttons, buttonIds);
13306 		EventLoop el = EventLoop.get;
13307 		el.run(() { return !mb.win.closed; });
13308 		return mb.buttonPressed;
13309 	}
13310 }
13311 
13312 /// ditto
13313 int messageBox(string message, MessageBoxStyle style = MessageBoxStyle.OK, MessageBoxIcon icon = MessageBoxIcon.None) {
13314 	return messageBox(null, message, style, icon);
13315 }
13316 
13317 
13318 
13319 ///
13320 alias void delegate(Widget handlerAttachedTo, Event event) EventHandler;
13321 
13322 /++
13323 	This is an opaque type you can use to disconnect an event handler when you're no longer interested.
13324 
13325 	History:
13326 		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.
13327 +/
13328 struct EventListener {
13329 	private Widget widget;
13330 	private string event;
13331 	private EventHandler handler;
13332 	private bool useCapture;
13333 
13334 	///
13335 	void disconnect() {
13336 		widget.removeEventListener(this);
13337 	}
13338 }
13339 
13340 /++
13341 	The purpose of this enum was to give a compile-time checked version of various standard event strings.
13342 
13343 	Now, I recommend you use a statically typed event object instead.
13344 
13345 	See_Also: [Event]
13346 +/
13347 enum EventType : string {
13348 	click = "click", ///
13349 
13350 	mouseenter = "mouseenter", ///
13351 	mouseleave = "mouseleave", ///
13352 	mousein = "mousein", ///
13353 	mouseout = "mouseout", ///
13354 	mouseup = "mouseup", ///
13355 	mousedown = "mousedown", ///
13356 	mousemove = "mousemove", ///
13357 
13358 	keydown = "keydown", ///
13359 	keyup = "keyup", ///
13360 	char_ = "char", ///
13361 
13362 	focus = "focus", ///
13363 	blur = "blur", ///
13364 
13365 	triggered = "triggered", ///
13366 
13367 	change = "change", ///
13368 }
13369 
13370 /++
13371 	Represents an event that is currently being processed.
13372 
13373 
13374 	Minigui's event model is based on the web browser. An event has a name, a target,
13375 	and an associated data object. It starts from the window and works its way down through
13376 	the target through all intermediate [Widget]s, triggering capture phase handlers as it goes,
13377 	then goes back up again all the way back to the window, triggering bubble phase handlers. At
13378 	the end, if [Event.preventDefault] has not been called, it calls the target widget's default
13379 	handlers for the event (please note that default handlers will be called even if [Event.stopPropagation]
13380 	was called; that just stops it from calling other handlers in the widget tree, but the default happens
13381 	whenever propagation is done, not only if it gets to the end of the chain).
13382 
13383 	This model has several nice points:
13384 
13385 	$(LIST
13386 		* It is easy to delegate dynamic handlers to a parent. You can have a parent container
13387 		  with event handlers set, then add/remove children as much as you want without needing
13388 		  to manage the event handlers on them - the parent alone can manage everything.
13389 
13390 		* It is easy to create new custom events in your application.
13391 
13392 		* It is familiar to many web developers.
13393 	)
13394 
13395 	There's a few downsides though:
13396 
13397 	$(LIST
13398 		* There's not a lot of type safety.
13399 
13400 		* You don't get a static list of what events a widget can emit.
13401 
13402 		* Tracing where an event got cancelled along the chain can get difficult; the downside of
13403 		  the central delegation benefit is it can be lead to debugging of action at a distance.
13404 	)
13405 
13406 	In May 2021, I started to adjust this model to minigui takes better advantage of D over Javascript
13407 	while keeping the benefits - and most compatibility with - the existing model. The main idea is
13408 	to simply use a D object type which provides a static interface as well as a built-in event name.
13409 	Then, a new static interface allows you to see what an event can emit and attach handlers to it
13410 	similarly to C#, which just forwards to the JS style api. They're fully compatible so you can still
13411 	delegate to a parent and use custom events as well as using the runtime dynamic access, in addition
13412 	to having a little more help from the D compiler and documentation generator.
13413 
13414 	Your code would change like this:
13415 
13416 	---
13417 	// old
13418 	widget.addEventListener("keydown", (Event ev) { ... }, /* optional arg */ useCapture );
13419 
13420 	// new
13421 	widget.addEventListener((KeyDownEvent ev) { ... }, /* optional arg */ useCapture );
13422 	---
13423 
13424 	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.
13425 
13426 	All you have to do is replace the string with a specific Event subclass. It will figure out the event string from the class.
13427 
13428 	Alternatively, you can cast the Event yourself to the appropriate subclass, but it is easier to let the library do it for you!
13429 
13430 	Thus the family of functions are:
13431 
13432 	[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.
13433 
13434 	[Widget.addDirectEventListener] is addEventListener, but only calls the handler if target == this. Useful for something you can't afford to delegate.
13435 
13436 	[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.
13437 
13438 	Let's implement a custom widget that can emit a ChangeEvent describing its `checked` property:
13439 
13440 	---
13441 	class MyCheckbox : Widget {
13442 		/// This gives a chance to document it and generates a convenience function to send it and attach handlers.
13443 		/// It is NOT actually required but should be used whenever possible.
13444 		mixin Emits!(ChangeEvent!bool);
13445 
13446 		this(Widget parent) {
13447 			super(parent);
13448 			setDefaultEventHandler((ClickEvent) { checked = !checked; });
13449 		}
13450 
13451 		private bool _checked;
13452 		@property bool checked() { return _checked; }
13453 		@property void checked(bool set) {
13454 			_checked = set;
13455 			emit!(ChangeEvent!bool)(&checked);
13456 		}
13457 	}
13458 	---
13459 
13460 	## Creating Your Own Events
13461 
13462 	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.
13463 
13464 	---
13465 	class MyEvent : Event {
13466 		this(Widget target) { super(EventString, target); }
13467 		mixin Register; // adds EventString and other reflection information
13468 	}
13469 	---
13470 
13471 	Then declare that it is sent with the [Emits] mixin, so you can use [Widget.emit] to dispatch it.
13472 
13473 	History:
13474 		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.
13475 
13476 		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.
13477 +/
13478 /+
13479 
13480 	## General Conventions
13481 
13482 	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.
13483 
13484 
13485 	## Qt-style signals and slots
13486 
13487 	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.
13488 
13489 	The intention is for events to be used when
13490 
13491 	---
13492 	class Demo : Widget {
13493 		this() {
13494 			myPropertyChanged = Signal!int(this);
13495 		}
13496 		@property myProperty(int v) {
13497 			myPropertyChanged.emit(v);
13498 		}
13499 
13500 		Signal!int myPropertyChanged; // i need to get `this` off it and inspect the name...
13501 		// but it can just genuinely not care about `this` since that's not really passed.
13502 	}
13503 
13504 	class Foo : Widget {
13505 		// the slot uda is not necessary, but it helps the script and ui builder find it.
13506 		@slot void setValue(int v) { ... }
13507 	}
13508 
13509 	demo.myPropertyChanged.connect(&foo.setValue);
13510 	---
13511 
13512 	The Signal type has a disabled default constructor, meaning your widget constructor must pass `this` to it in its constructor.
13513 
13514 	Some events may also wish to implement the Signal interface. These use particular arguments to call a method automatically.
13515 
13516 	class StringChangeEvent : ChangeEvent, Signal!string {
13517 		mixin SignalImpl
13518 	}
13519 
13520 +/
13521 class Event : ReflectableProperties {
13522 	/// Creates an event without populating any members and without sending it. See [dispatch]
13523 	this(string eventName, Widget emittedBy) {
13524 		this.eventName = eventName;
13525 		this.srcElement = emittedBy;
13526 	}
13527 
13528 
13529 	/// Implementations for the [ReflectableProperties] interface/
13530 	void getPropertiesList(scope void delegate(string name) sink) const {}
13531 	/// ditto
13532 	void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink) { }
13533 	/// ditto
13534 	SetPropertyResult setPropertyFromString(string name, scope const(char)[] str, bool strIsJson) {
13535 		return SetPropertyResult.notPermitted;
13536 	}
13537 
13538 
13539 	/+
13540 	/++
13541 		This is an internal implementation detail of [Register] and is subject to be changed or removed at any time without notice.
13542 
13543 		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.
13544 	+/
13545 	protected final void sinkJsonString(string memberName, scope const(char)[] value, scope void delegate(string name, scope const(char)[] value) finalSink) {
13546 		if(value.length == 0) {
13547 			finalSink(memberName, `""`);
13548 			return;
13549 		}
13550 
13551 		char[1024] bufferBacking;
13552 		char[] buffer = bufferBacking;
13553 		int bufferPosition;
13554 
13555 		void sink(char ch) {
13556 			if(bufferPosition >= buffer.length)
13557 				buffer.length = buffer.length + 1024;
13558 			buffer[bufferPosition++] = ch;
13559 		}
13560 
13561 		sink('"');
13562 
13563 		foreach(ch; value) {
13564 			switch(ch) {
13565 				case '\\':
13566 					sink('\\'); sink('\\');
13567 				break;
13568 				case '"':
13569 					sink('\\'); sink('"');
13570 				break;
13571 				case '\n':
13572 					sink('\\'); sink('n');
13573 				break;
13574 				case '\r':
13575 					sink('\\'); sink('r');
13576 				break;
13577 				case '\t':
13578 					sink('\\'); sink('t');
13579 				break;
13580 				default:
13581 					sink(ch);
13582 			}
13583 		}
13584 
13585 		sink('"');
13586 
13587 		finalSink(memberName, buffer[0 .. bufferPosition]);
13588 	}
13589 	+/
13590 
13591 	/+
13592 	enum EventInitiator {
13593 		system,
13594 		minigui,
13595 		user
13596 	}
13597 
13598 	immutable EventInitiator; initiatedBy;
13599 	+/
13600 
13601 	/++
13602 		Events should generally follow the propagation model, but there's some exceptions
13603 		to that rule. If so, they should override this to return false. In that case, only
13604 		bubbling event handlers on the target itself and capturing event handlers on the containing
13605 		window will be called. (That is, [dispatch] will call [sendDirectly] instead of doing the normal
13606 		capture -> target -> bubble process.)
13607 
13608 		History:
13609 			Added May 12, 2021
13610 	+/
13611 	bool propagates() const pure nothrow @nogc @safe {
13612 		return true;
13613 	}
13614 
13615 	/++
13616 		hints as to whether preventDefault will actually do anything. not entirely reliable.
13617 
13618 		History:
13619 			Added May 14, 2021
13620 	+/
13621 	bool cancelable() const pure nothrow @nogc @safe {
13622 		return true;
13623 	}
13624 
13625 	/++
13626 		You can mix this into child class to register some boilerplate. It includes the `EventString`
13627 		member, a constructor, and implementations of the dynamic get data interfaces.
13628 
13629 		If you fail to do this, your event will probably not have full compatibility but it might still work for you.
13630 
13631 
13632 		You can override the default EventString by simply providing your own in the form of
13633 		`enum string EventString = "some.name";` The default is the name of your class and its parent entity
13634 		which provides some namespace protection against conflicts in other libraries while still being fairly
13635 		easy to use.
13636 
13637 		If you provide your own constructor, it will override the default constructor provided here. A constructor
13638 		must call `super(EventString, passed_widget_target)` at some point. The `passed_widget_target` must be the
13639 		first argument to your constructor.
13640 
13641 		History:
13642 			Added May 13, 2021.
13643 	+/
13644 	protected static mixin template Register() {
13645 		public enum string EventString = __traits(identifier, __traits(parent, typeof(this))) ~ "." ~  __traits(identifier, typeof(this));
13646 		this(Widget target) { super(EventString, target); }
13647 
13648 		mixin ReflectableProperties.RegisterGetters;
13649 	}
13650 
13651 	/++
13652 		This is the widget that emitted the event.
13653 
13654 
13655 		The aliased names come from Javascript for ease of web developers to transition in, but they're all synonyms.
13656 
13657 		History:
13658 			The `source` name was added on May 14, 2021. It is a little weird that `source` and `target` are synonyms,
13659 			but that's a side effect of it doing both capture and bubble handlers and people are used to it from the web
13660 			so I don't intend to remove these aliases.
13661 	+/
13662 	Widget source;
13663 	/// ditto
13664 	alias source target;
13665 	/// ditto
13666 	alias source srcElement;
13667 
13668 	Widget relatedTarget; /// Note: likely to be deprecated at some point.
13669 
13670 	/// Prevents the default event handler (if there is one) from being called
13671 	void preventDefault() {
13672 		lastDefaultPrevented = true;
13673 		defaultPrevented = true;
13674 	}
13675 
13676 	/// Stops the event propagation immediately.
13677 	void stopPropagation() {
13678 		propagationStopped = true;
13679 	}
13680 
13681 	private bool defaultPrevented;
13682 	private bool propagationStopped;
13683 	private string eventName;
13684 
13685 	private bool isBubbling;
13686 
13687 	/// 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.
13688 	protected void adjustScrolling() { }
13689 	/// ditto
13690 	protected void adjustClientCoordinates(int deltaX, int deltaY) { }
13691 
13692 	/++
13693 		this sends it only to the target. If you want propagation, use dispatch() instead.
13694 
13695 		This should be made private!!!
13696 
13697 	+/
13698 	void sendDirectly() {
13699 		if(srcElement is null)
13700 			return;
13701 
13702 		// 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.
13703 
13704 		//debug if(eventName != "mousemove" && target !is null && target.parentWindow && target.parentWindow.devTools)
13705 			//target.parentWindow.devTools.log("Event ", eventName, " dispatched directly to ", srcElement);
13706 
13707 		adjustScrolling();
13708 
13709 		if(auto e = target.parentWindow) {
13710 			if(auto handlers = "*" in e.capturingEventHandlers)
13711 			foreach(handler; *handlers)
13712 				if(handler) handler(e, this);
13713 			if(auto handlers = eventName in e.capturingEventHandlers)
13714 			foreach(handler; *handlers)
13715 				if(handler) handler(e, this);
13716 		}
13717 
13718 		auto e = srcElement;
13719 
13720 		if(auto handlers = eventName in e.bubblingEventHandlers)
13721 		foreach(handler; *handlers)
13722 			if(handler) handler(e, this);
13723 
13724 		if(auto handlers = "*" in e.bubblingEventHandlers)
13725 		foreach(handler; *handlers)
13726 			if(handler) handler(e, this);
13727 
13728 		// there's never a default for a catch-all event
13729 		if(!defaultPrevented)
13730 			if(eventName in e.defaultEventHandlers)
13731 				e.defaultEventHandlers[eventName](e, this);
13732 	}
13733 
13734 	/// this dispatches the element using the capture -> target -> bubble process
13735 	void dispatch() {
13736 		if(srcElement is null)
13737 			return;
13738 
13739 		if(!propagates) {
13740 			sendDirectly;
13741 			return;
13742 		}
13743 
13744 		//debug if(eventName != "mousemove" && target !is null && target.parentWindow && target.parentWindow.devTools)
13745 			//target.parentWindow.devTools.log("Event ", eventName, " dispatched to ", srcElement);
13746 
13747 		adjustScrolling();
13748 		// first capture, then bubble
13749 
13750 		Widget[] chain;
13751 		Widget curr = srcElement;
13752 		while(curr) {
13753 			auto l = curr;
13754 			chain ~= l;
13755 			curr = curr.parent;
13756 		}
13757 
13758 		isBubbling = false;
13759 
13760 		foreach_reverse(e; chain) {
13761 			if(auto handlers = "*" in e.capturingEventHandlers)
13762 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
13763 
13764 			if(propagationStopped)
13765 				break;
13766 
13767 			if(auto handlers = eventName in e.capturingEventHandlers)
13768 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
13769 
13770 			// the default on capture should really be to always do nothing
13771 
13772 			//if(!defaultPrevented)
13773 			//	if(eventName in e.defaultEventHandlers)
13774 			//		e.defaultEventHandlers[eventName](e.element, this);
13775 
13776 			if(propagationStopped)
13777 				break;
13778 		}
13779 
13780 		int adjustX;
13781 		int adjustY;
13782 
13783 		isBubbling = true;
13784 		if(!propagationStopped)
13785 		foreach(e; chain) {
13786 			if(auto handlers = eventName in e.bubblingEventHandlers)
13787 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
13788 
13789 			if(propagationStopped)
13790 				break;
13791 
13792 			if(auto handlers = "*" in e.bubblingEventHandlers)
13793 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
13794 
13795 			if(propagationStopped)
13796 				break;
13797 
13798 			if(e.encapsulatedChildren()) {
13799 				adjustClientCoordinates(adjustX, adjustY);
13800 				target = e;
13801 			} else {
13802 				adjustX += e.x;
13803 				adjustY += e.y;
13804 			}
13805 		}
13806 
13807 		if(!defaultPrevented)
13808 		foreach(e; chain) {
13809 			if(eventName in e.defaultEventHandlers)
13810 				e.defaultEventHandlers[eventName](e, this);
13811 		}
13812 	}
13813 
13814 
13815 	/* old compatibility things */
13816 	deprecated("Use some subclass of KeyEventBase instead of plain Event in your handler going forward. WARNING these may crash on non-key events!")
13817 	final @property {
13818 		Key key() { return (cast(KeyEventBase) this).key; }
13819 		KeyEvent originalKeyEvent() { return (cast(KeyEventBase) this).originalKeyEvent; }
13820 
13821 		bool ctrlKey() { return (cast(KeyEventBase) this).ctrlKey; }
13822 		bool altKey() { return (cast(KeyEventBase) this).altKey; }
13823 		bool shiftKey() { return (cast(KeyEventBase) this).shiftKey; }
13824 	}
13825 
13826 	deprecated("Use some subclass of MouseEventBase instead of Event in your handler going forward. WARNING these may crash on non-mouse events!")
13827 	final @property {
13828 		int clientX() { return (cast(MouseEventBase) this).clientX; }
13829 		int clientY() { return (cast(MouseEventBase) this).clientY; }
13830 
13831 		int viewportX() { return (cast(MouseEventBase) this).viewportX; }
13832 		int viewportY() { return (cast(MouseEventBase) this).viewportY; }
13833 
13834 		int button() { return (cast(MouseEventBase) this).button; }
13835 		int buttonLinear() { return (cast(MouseEventBase) this).buttonLinear; }
13836 	}
13837 
13838 	deprecated("Use either a KeyEventBase or a MouseEventBase instead of Event in your handler going forward")
13839 	final @property {
13840 		int state() {
13841 			if(auto meb = cast(MouseEventBase) this)
13842 				return meb.state;
13843 			if(auto keb = cast(KeyEventBase) this)
13844 				return keb.state;
13845 			assert(0);
13846 		}
13847 	}
13848 
13849 	deprecated("Use a CharEvent instead of Event in your handler going forward")
13850 	final @property {
13851 		dchar character() {
13852 			if(auto ce = cast(CharEvent) this)
13853 				return ce.character;
13854 			return dchar.init;
13855 		}
13856 	}
13857 
13858 	// for change events
13859 	@property {
13860 		///
13861 		int intValue() { return 0; }
13862 		///
13863 		string stringValue() { return null; }
13864 	}
13865 }
13866 
13867 /++
13868 	This lets you statically verify you send the events you claim you send and gives you a hook to document them.
13869 
13870 	Please note that a widget may send events not listed as Emits. You can always construct and dispatch
13871 	dynamic and custom events, but the static list helps ensure you get them right.
13872 
13873 	If this is declared, you can use [Widget.emit] to send the event.
13874 
13875 	All events work the same way though, following the capture->widget->bubble model described under [Event].
13876 
13877 	History:
13878 		Added May 4, 2021
13879 +/
13880 mixin template Emits(EventType) {
13881 	import arsd.minigui : EventString;
13882 	static if(is(EventType : Event) && !is(EventType == Event))
13883 		mixin("private EventType[0] emits_" ~ EventStringIdentifier!EventType ~";");
13884 	else
13885 		static assert(0, "You can only emit subclasses of Event");
13886 }
13887 
13888 /// ditto
13889 mixin template Emits(string eventString) {
13890 	mixin("private Event[0] emits_" ~ eventString ~";");
13891 }
13892 
13893 /*
13894 class SignalEvent(string name) : Event {
13895 
13896 }
13897 */
13898 
13899 /++
13900 	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".
13901 
13902 
13903 	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.
13904 
13905 	History:
13906 		Added on May 13, 2021. Prior to that, you'd most likely `addEventListener(EventType.triggered, ...)` to handle similar things.
13907 +/
13908 class CommandEvent : Event {
13909 	enum EventString = "command";
13910 	this(Widget source, string CommandString = EventString) {
13911 		super(CommandString, source);
13912 	}
13913 }
13914 
13915 /++
13916 	A [CommandEvent] is typically actually an instance of these to hold the strongly-typed arguments.
13917 +/
13918 class CommandEventWithArgs(Args...) : CommandEvent {
13919 	this(Widget source, string CommandString, Args args) { super(source, CommandString); this.args = args; }
13920 	Args args;
13921 }
13922 
13923 /++
13924 	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.
13925 
13926 	See [CommandEvent] for more information.
13927 
13928 	Returns:
13929 		The [EventListener] you can use to remove the handler.
13930 +/
13931 EventListener consumesCommand(string CommandString, WidgetType, Args...)(WidgetType w, void delegate(Args) handler) {
13932 	return w.addEventListener(CommandString, (Event ev) {
13933 		if(ev.target is w)
13934 			return; // it does not consume its own commands!
13935 		if(auto cev = cast(CommandEventWithArgs!Args) ev) {
13936 			handler(cev.args);
13937 			ev.stopPropagation();
13938 		}
13939 	});
13940 }
13941 
13942 /++
13943 	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.
13944 +/
13945 void emitCommand(string CommandString, WidgetType, Args...)(WidgetType w, Args args) {
13946 	auto event = new CommandEventWithArgs!Args(w, CommandString, args);
13947 	event.dispatch();
13948 }
13949 
13950 class ResizeEvent : Event {
13951 	enum EventString = "resize";
13952 
13953 	this(Widget target) { super(EventString, target); }
13954 
13955 	override bool propagates() const { return false; }
13956 }
13957 
13958 /++
13959 	ClosingEvent is fired when a user is attempting to close a window. You can `preventDefault` to cancel the close.
13960 
13961 	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.
13962 
13963 	History:
13964 		Added June 21, 2021 (dub v10.1)
13965 +/
13966 class ClosingEvent : Event {
13967 	enum EventString = "closing";
13968 
13969 	this(Widget target) { super(EventString, target); }
13970 
13971 	override bool propagates() const { return false; }
13972 	override bool cancelable() const { return true; }
13973 }
13974 
13975 /// ditto
13976 class ClosedEvent : Event {
13977 	enum EventString = "closed";
13978 
13979 	this(Widget target) { super(EventString, target); }
13980 
13981 	override bool propagates() const { return false; }
13982 	override bool cancelable() const { return false; }
13983 }
13984 
13985 ///
13986 class BlurEvent : Event {
13987 	enum EventString = "blur";
13988 
13989 	// FIXME: related target?
13990 	this(Widget target) { super(EventString, target); }
13991 
13992 	override bool propagates() const { return false; }
13993 }
13994 
13995 ///
13996 class FocusEvent : Event {
13997 	enum EventString = "focus";
13998 
13999 	// FIXME: related target?
14000 	this(Widget target) { super(EventString, target); }
14001 
14002 	override bool propagates() const { return false; }
14003 }
14004 
14005 /++
14006 	FocusInEvent is a FocusEvent that propagates, while FocusOutEvent is a BlurEvent that propagates.
14007 
14008 	History:
14009 		Added July 3, 2021
14010 +/
14011 class FocusInEvent : Event {
14012 	enum EventString = "focusin";
14013 
14014 	// FIXME: related target?
14015 	this(Widget target) { super(EventString, target); }
14016 
14017 	override bool cancelable() const { return false; }
14018 }
14019 
14020 /// ditto
14021 class FocusOutEvent : Event {
14022 	enum EventString = "focusout";
14023 
14024 	// FIXME: related target?
14025 	this(Widget target) { super(EventString, target); }
14026 
14027 	override bool cancelable() const { return false; }
14028 }
14029 
14030 ///
14031 class ScrollEvent : Event {
14032 	enum EventString = "scroll";
14033 	this(Widget target) { super(EventString, target); }
14034 
14035 	override bool cancelable() const { return false; }
14036 }
14037 
14038 /++
14039 	Indicates that a character has been typed by the user. Normally dispatched to the currently focused widget.
14040 
14041 	History:
14042 		Added May 2, 2021. Previously, this was simply a "char" event and `character` as a member of the [Event] base class.
14043 +/
14044 class CharEvent : Event {
14045 	enum EventString = "char";
14046 	this(Widget target, dchar ch) {
14047 		character = ch;
14048 		super(EventString, target);
14049 	}
14050 
14051 	immutable dchar character;
14052 }
14053 
14054 /++
14055 	You should generally use a `ChangeEvent!Type` instead of this directly. See [ChangeEvent] for more information.
14056 +/
14057 abstract class ChangeEventBase : Event {
14058 	enum EventString = "change";
14059 	this(Widget target) {
14060 		super(EventString, target);
14061 	}
14062 
14063 	/+
14064 		// idk where or how exactly i want to do this.
14065 		// i might come back to it later.
14066 
14067 	// If a widget itself broadcasts one of theses itself, it stops propagation going down
14068 	// this way the source doesn't get too confused (think of a nested scroll widget)
14069 	//
14070 	// the idea is like the scroll bar emits a command event saying like "scroll left one line"
14071 	// then you consume that command and change you scroll x position to whatever. then you do
14072 	// some kind of change event that is broadcast back to the children and any horizontal scroll
14073 	// listeners are now able to update, without having an explicit connection between them.
14074 	void broadcastToChildren(string fieldName) {
14075 
14076 	}
14077 	+/
14078 }
14079 
14080 /++
14081 	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.
14082 
14083 
14084 	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).
14085 
14086 	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);`
14087 
14088 	Since it is emitted after the value has already changed, [preventDefault] is unlikely to do anything.
14089 
14090 	History:
14091 		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.
14092 +/
14093 class ChangeEvent(T) : ChangeEventBase {
14094 	this(Widget target, T delegate() getNewValue) {
14095 		assert(getNewValue !is null);
14096 		this.getNewValue = getNewValue;
14097 		super(target);
14098 	}
14099 
14100 	private T delegate() getNewValue;
14101 
14102 	/++
14103 		Gets the new value that just changed.
14104 	+/
14105 	@property T value() {
14106 		return getNewValue();
14107 	}
14108 
14109 	/// compatibility method for old generic Events
14110 	static if(is(immutable T == immutable int))
14111 		override int intValue() { return value; }
14112 	/// ditto
14113 	static if(is(immutable T == immutable string))
14114 		override string stringValue() { return value; }
14115 }
14116 
14117 /++
14118 	Contains shared properties for [KeyDownEvent]s and [KeyUpEvent]s.
14119 
14120 
14121 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
14122 
14123 	History:
14124 		Added May 2, 2021. Previously, its properties were members of the [Event] base class.
14125 +/
14126 abstract class KeyEventBase : Event {
14127 	this(string name, Widget target) {
14128 		super(name, target);
14129 	}
14130 
14131 	// for key events
14132 	Key key; ///
14133 
14134 	KeyEvent originalKeyEvent;
14135 
14136 	/++
14137 		Indicates the current state of the given keyboard modifier keys.
14138 
14139 		History:
14140 			Added to events on April 15, 2020.
14141 	+/
14142 	bool ctrlKey;
14143 
14144 	/// ditto
14145 	bool altKey;
14146 
14147 	/// ditto
14148 	bool shiftKey;
14149 
14150 	/++
14151 		The raw bitflags that are parsed out into [ctrlKey], [altKey], and [shiftKey].
14152 
14153 		See [arsd.simpledisplay.ModifierState] for other possible flags.
14154 	+/
14155 	int state;
14156 
14157 	mixin Register;
14158 }
14159 
14160 /++
14161 	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].
14162 
14163 
14164 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
14165 
14166 	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.
14167 
14168 	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.
14169 
14170 	See_Also: [KeyUpEvent], [CharEvent]
14171 
14172 	History:
14173 		Added May 2, 2021. Previously, it was only seen as the base [Event] class on "keydown" event listeners.
14174 +/
14175 class KeyDownEvent : KeyEventBase {
14176 	enum EventString = "keydown";
14177 	this(Widget target) { super(EventString, target); }
14178 }
14179 
14180 /++
14181 	Indicates that the user has released a key on the keyboard. For available properties, see [KeyEventBase].
14182 
14183 
14184 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
14185 
14186 	See_Also: [KeyDownEvent], [CharEvent]
14187 
14188 	History:
14189 		Added May 2, 2021. Previously, it was only seen as the base [Event] class on "keyup" event listeners.
14190 +/
14191 class KeyUpEvent : KeyEventBase {
14192 	enum EventString = "keyup";
14193 	this(Widget target) { super(EventString, target); }
14194 }
14195 
14196 /++
14197 	Contains shared properties for various mouse events;
14198 
14199 
14200 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
14201 
14202 	History:
14203 		Added May 2, 2021. Previously, its properties were members of the [Event] base class.
14204 +/
14205 abstract class MouseEventBase : Event {
14206 	this(string name, Widget target) {
14207 		super(name, target);
14208 	}
14209 
14210 	// for mouse events
14211 	int clientX; /// The mouse event location relative to the target widget
14212 	int clientY; /// ditto
14213 
14214 	int viewportX; /// The mouse event location relative to the window origin
14215 	int viewportY; /// ditto
14216 
14217 	int button; /// See: [MouseEvent.button]
14218 	int buttonLinear; /// See: [MouseEvent.buttonLinear]
14219 
14220 	/++
14221 		Indicates the current state of the given keyboard modifier keys.
14222 
14223 		History:
14224 			Added to mouse events on September 28, 2010.
14225 	+/
14226 	bool ctrlKey;
14227 
14228 	/// ditto
14229 	bool altKey;
14230 
14231 	/// ditto
14232 	bool shiftKey;
14233 
14234 
14235 
14236 	int state; ///
14237 
14238 	/++
14239 		for consistent names with key event.
14240 
14241 		History:
14242 			Added September 28, 2021 (dub v10.3)
14243 	+/
14244 	alias modifierState = state;
14245 
14246 	/++
14247 		Mouse wheel movement sends down/up/click events just like other buttons clicking. This method is to help you filter that out.
14248 
14249 		History:
14250 			Added May 15, 2021
14251 	+/
14252 	bool isMouseWheel() {
14253 		return button == MouseButton.wheelUp || button == MouseButton.wheelDown;
14254 	}
14255 
14256 	// private
14257 	override void adjustClientCoordinates(int deltaX, int deltaY) {
14258 		clientX += deltaX;
14259 		clientY += deltaY;
14260 	}
14261 
14262 	override void adjustScrolling() {
14263 	version(custom_widgets) { // TEMP
14264 		viewportX = clientX;
14265 		viewportY = clientY;
14266 		if(auto se = cast(ScrollableWidget) srcElement) {
14267 			clientX += se.scrollOrigin.x;
14268 			clientY += se.scrollOrigin.y;
14269 		} else if(auto se = cast(ScrollableContainerWidget) srcElement) {
14270 			//clientX += se.scrollX_;
14271 			//clientY += se.scrollY_;
14272 		}
14273 	}
14274 	}
14275 
14276 	mixin Register;
14277 }
14278 
14279 /++
14280 	Indicates that the user has worked with the mouse over your widget. For available properties, see [MouseEventBase].
14281 
14282 
14283 	$(WARNING
14284 		Important: MouseDownEvent, MouseUpEvent, ClickEvent, and DoubleClickEvent are all sent for all mouse buttons and
14285 		for wheel movement! You should check the [MouseEventBase.button|button] property in most your handlers to get correct
14286 		behavior.
14287 	)
14288 
14289 	[MouseDownEvent] is sent when the user presses a mouse button. It is also sent on mouse wheel movement.
14290 
14291 	[MouseUpEvent] is sent when the user releases a mouse button.
14292 
14293 	[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.)
14294 
14295 	[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.
14296 
14297 	[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.
14298 
14299 	[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.
14300 
14301 	[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.
14302 
14303 	[MouseEnterEvent] is sent when the mouse enters the bounding box of a widget.
14304 
14305 	[MouseLeaveEvent] is sent when the mouse leaves the bounding box of a widget.
14306 
14307 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
14308 
14309 	Rationale:
14310 
14311 		If you only want to do drag, mousedown/up works just fine being consistently sent.
14312 
14313 		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).
14314 
14315 		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.
14316 
14317 	History:
14318 		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.
14319 +/
14320 class MouseUpEvent : MouseEventBase {
14321 	enum EventString = "mouseup"; ///
14322 	this(Widget target) { super(EventString, target); }
14323 }
14324 /// ditto
14325 class MouseDownEvent : MouseEventBase {
14326 	enum EventString = "mousedown"; ///
14327 	this(Widget target) { super(EventString, target); }
14328 }
14329 /// ditto
14330 class MouseMoveEvent : MouseEventBase {
14331 	enum EventString = "mousemove"; ///
14332 	this(Widget target) { super(EventString, target); }
14333 }
14334 /// ditto
14335 class ClickEvent : MouseEventBase {
14336 	enum EventString = "click"; ///
14337 	this(Widget target) { super(EventString, target); }
14338 }
14339 /// ditto
14340 class DoubleClickEvent : MouseEventBase {
14341 	enum EventString = "dblclick"; ///
14342 	this(Widget target) { super(EventString, target); }
14343 }
14344 /// ditto
14345 class MouseOverEvent : Event {
14346 	enum EventString = "mouseover"; ///
14347 	this(Widget target) { super(EventString, target); }
14348 }
14349 /// ditto
14350 class MouseOutEvent : Event {
14351 	enum EventString = "mouseout"; ///
14352 	this(Widget target) { super(EventString, target); }
14353 }
14354 /// ditto
14355 class MouseEnterEvent : Event {
14356 	enum EventString = "mouseenter"; ///
14357 	this(Widget target) { super(EventString, target); }
14358 
14359 	override bool propagates() const { return false; }
14360 }
14361 /// ditto
14362 class MouseLeaveEvent : Event {
14363 	enum EventString = "mouseleave"; ///
14364 	this(Widget target) { super(EventString, target); }
14365 
14366 	override bool propagates() const { return false; }
14367 }
14368 
14369 private bool isAParentOf(Widget a, Widget b) {
14370 	if(a is null || b is null)
14371 		return false;
14372 
14373 	while(b !is null) {
14374 		if(a is b)
14375 			return true;
14376 		b = b.parent;
14377 	}
14378 
14379 	return false;
14380 }
14381 
14382 private struct WidgetAtPointResponse {
14383 	Widget widget;
14384 
14385 	// x, y relative to the widget in the response.
14386 	int x;
14387 	int y;
14388 }
14389 
14390 private WidgetAtPointResponse widgetAtPoint(Widget starting, int x, int y) {
14391 	assert(starting !is null);
14392 
14393 	starting.addScrollPosition(x, y);
14394 
14395 	auto child = starting.getChildAtPosition(x, y);
14396 	while(child) {
14397 		if(child.hidden)
14398 			continue;
14399 		starting = child;
14400 		x -= child.x;
14401 		y -= child.y;
14402 		auto r = starting.widgetAtPoint(x, y);//starting.getChildAtPosition(x, y);
14403 		child = r.widget;
14404 		if(child is starting)
14405 			break;
14406 	}
14407 	return WidgetAtPointResponse(starting, x, y);
14408 }
14409 
14410 version(win32_widgets) {
14411 private:
14412 	import core.sys.windows.commctrl;
14413 
14414 	pragma(lib, "comctl32");
14415 	shared static this() {
14416 		// http://msdn.microsoft.com/en-us/library/windows/desktop/bb775507(v=vs.85).aspx
14417 		INITCOMMONCONTROLSEX ic;
14418 		ic.dwSize = cast(DWORD) ic.sizeof;
14419 		ic.dwICC = ICC_UPDOWN_CLASS | ICC_WIN95_CLASSES | ICC_BAR_CLASSES | ICC_PROGRESS_CLASS | ICC_COOL_CLASSES | ICC_STANDARD_CLASSES | ICC_USEREX_CLASSES;
14420 		if(!InitCommonControlsEx(&ic)) {
14421 			//writeln("ICC failed");
14422 		}
14423 	}
14424 
14425 
14426 	// everything from here is just win32 headers copy pasta
14427 private:
14428 extern(Windows):
14429 
14430 	alias HANDLE HMENU;
14431 	HMENU CreateMenu();
14432 	bool SetMenu(HWND, HMENU);
14433 	HMENU CreatePopupMenu();
14434 	enum MF_POPUP = 0x10;
14435 	enum MF_STRING = 0;
14436 
14437 
14438 	BOOL InitCommonControlsEx(const INITCOMMONCONTROLSEX*);
14439 	struct INITCOMMONCONTROLSEX {
14440 		DWORD dwSize;
14441 		DWORD dwICC;
14442 	}
14443 	enum HINST_COMMCTRL = cast(HINSTANCE) (-1);
14444 enum {
14445         IDB_STD_SMALL_COLOR,
14446         IDB_STD_LARGE_COLOR,
14447         IDB_VIEW_SMALL_COLOR = 4,
14448         IDB_VIEW_LARGE_COLOR = 5
14449 }
14450 enum {
14451         STD_CUT,
14452         STD_COPY,
14453         STD_PASTE,
14454         STD_UNDO,
14455         STD_REDOW,
14456         STD_DELETE,
14457         STD_FILENEW,
14458         STD_FILEOPEN,
14459         STD_FILESAVE,
14460         STD_PRINTPRE,
14461         STD_PROPERTIES,
14462         STD_HELP,
14463         STD_FIND,
14464         STD_REPLACE,
14465         STD_PRINT // = 14
14466 }
14467 
14468 alias HANDLE HIMAGELIST;
14469 	HIMAGELIST ImageList_Create(int, int, UINT, int, int);
14470 	int ImageList_Add(HIMAGELIST, HBITMAP, HBITMAP);
14471         BOOL ImageList_Destroy(HIMAGELIST);
14472 
14473 uint MAKELONG(ushort a, ushort b) {
14474         return cast(uint) ((b << 16) | a);
14475 }
14476 
14477 
14478 struct TBBUTTON {
14479 	int   iBitmap;
14480 	int   idCommand;
14481 	BYTE  fsState;
14482 	BYTE  fsStyle;
14483 	version(Win64)
14484 	BYTE[6] bReserved;
14485 	else
14486 	BYTE[2]  bReserved;
14487 	DWORD dwData;
14488 	INT_PTR   iString;
14489 }
14490 
14491 	enum {
14492 		TB_ADDBUTTONSA   = WM_USER + 20,
14493 		TB_INSERTBUTTONA = WM_USER + 21,
14494 		TB_GETIDEALSIZE = WM_USER + 99,
14495 	}
14496 
14497 struct SIZE {
14498 	LONG cx;
14499 	LONG cy;
14500 }
14501 
14502 
14503 enum {
14504 	TBSTATE_CHECKED       = 1,
14505 	TBSTATE_PRESSED       = 2,
14506 	TBSTATE_ENABLED       = 4,
14507 	TBSTATE_HIDDEN        = 8,
14508 	TBSTATE_INDETERMINATE = 16,
14509 	TBSTATE_WRAP          = 32
14510 }
14511 
14512 
14513 
14514 enum {
14515 	ILC_COLOR    = 0,
14516 	ILC_COLOR4   = 4,
14517 	ILC_COLOR8   = 8,
14518 	ILC_COLOR16  = 16,
14519 	ILC_COLOR24  = 24,
14520 	ILC_COLOR32  = 32,
14521 	ILC_COLORDDB = 254,
14522 	ILC_MASK     = 1,
14523 	ILC_PALETTE  = 2048
14524 }
14525 
14526 
14527 alias TBBUTTON*       PTBBUTTON, LPTBBUTTON;
14528 
14529 
14530 enum {
14531 	TB_ENABLEBUTTON          = WM_USER + 1,
14532 	TB_CHECKBUTTON,
14533 	TB_PRESSBUTTON,
14534 	TB_HIDEBUTTON,
14535 	TB_INDETERMINATE, //     = WM_USER + 5,
14536 	TB_ISBUTTONENABLED       = WM_USER + 9,
14537 	TB_ISBUTTONCHECKED,
14538 	TB_ISBUTTONPRESSED,
14539 	TB_ISBUTTONHIDDEN,
14540 	TB_ISBUTTONINDETERMINATE, // = WM_USER + 13,
14541 	TB_SETSTATE              = WM_USER + 17,
14542 	TB_GETSTATE              = WM_USER + 18,
14543 	TB_ADDBITMAP             = WM_USER + 19,
14544 	TB_DELETEBUTTON          = WM_USER + 22,
14545 	TB_GETBUTTON,
14546 	TB_BUTTONCOUNT,
14547 	TB_COMMANDTOINDEX,
14548 	TB_SAVERESTOREA,
14549 	TB_CUSTOMIZE,
14550 	TB_ADDSTRINGA,
14551 	TB_GETITEMRECT,
14552 	TB_BUTTONSTRUCTSIZE,
14553 	TB_SETBUTTONSIZE,
14554 	TB_SETBITMAPSIZE,
14555 	TB_AUTOSIZE, //          = WM_USER + 33,
14556 	TB_GETTOOLTIPS           = WM_USER + 35,
14557 	TB_SETTOOLTIPS           = WM_USER + 36,
14558 	TB_SETPARENT             = WM_USER + 37,
14559 	TB_SETROWS               = WM_USER + 39,
14560 	TB_GETROWS,
14561 	TB_GETBITMAPFLAGS,
14562 	TB_SETCMDID,
14563 	TB_CHANGEBITMAP,
14564 	TB_GETBITMAP,
14565 	TB_GETBUTTONTEXTA,
14566 	TB_REPLACEBITMAP, //     = WM_USER + 46,
14567 	TB_GETBUTTONSIZE         = WM_USER + 58,
14568 	TB_SETBUTTONWIDTH        = WM_USER + 59,
14569 	TB_GETBUTTONTEXTW        = WM_USER + 75,
14570 	TB_SAVERESTOREW          = WM_USER + 76,
14571 	TB_ADDSTRINGW            = WM_USER + 77,
14572 }
14573 
14574 extern(Windows)
14575 BOOL EnumChildWindows(HWND, WNDENUMPROC, LPARAM);
14576 
14577 alias extern(Windows) BOOL function (HWND, LPARAM) WNDENUMPROC;
14578 
14579 
14580 	enum {
14581 		TB_SETINDENT = WM_USER + 47,
14582 		TB_SETIMAGELIST,
14583 		TB_GETIMAGELIST,
14584 		TB_LOADIMAGES,
14585 		TB_GETRECT,
14586 		TB_SETHOTIMAGELIST,
14587 		TB_GETHOTIMAGELIST,
14588 		TB_SETDISABLEDIMAGELIST,
14589 		TB_GETDISABLEDIMAGELIST,
14590 		TB_SETSTYLE,
14591 		TB_GETSTYLE,
14592 		//TB_GETBUTTONSIZE,
14593 		//TB_SETBUTTONWIDTH,
14594 		TB_SETMAXTEXTROWS,
14595 		TB_GETTEXTROWS // = WM_USER + 61
14596 	}
14597 
14598 enum {
14599 	CCM_FIRST            = 0x2000,
14600 	CCM_LAST             = CCM_FIRST + 0x200,
14601 	CCM_SETBKCOLOR       = 8193,
14602 	CCM_SETCOLORSCHEME   = 8194,
14603 	CCM_GETCOLORSCHEME   = 8195,
14604 	CCM_GETDROPTARGET    = 8196,
14605 	CCM_SETUNICODEFORMAT = 8197,
14606 	CCM_GETUNICODEFORMAT = 8198,
14607 	CCM_SETVERSION       = 0x2007,
14608 	CCM_GETVERSION       = 0x2008,
14609 	CCM_SETNOTIFYWINDOW  = 0x2009
14610 }
14611 
14612 
14613 enum {
14614 	PBM_SETRANGE     = WM_USER + 1,
14615 	PBM_SETPOS,
14616 	PBM_DELTAPOS,
14617 	PBM_SETSTEP,
14618 	PBM_STEPIT,   // = WM_USER + 5
14619 	PBM_SETRANGE32   = 1030,
14620 	PBM_GETRANGE,
14621 	PBM_GETPOS,
14622 	PBM_SETBARCOLOR, // = 1033
14623 	PBM_SETBKCOLOR   = CCM_SETBKCOLOR
14624 }
14625 
14626 enum {
14627 	PBS_SMOOTH   = 1,
14628 	PBS_VERTICAL = 4
14629 }
14630 
14631 enum {
14632         ICC_LISTVIEW_CLASSES = 1,
14633         ICC_TREEVIEW_CLASSES = 2,
14634         ICC_BAR_CLASSES      = 4,
14635         ICC_TAB_CLASSES      = 8,
14636         ICC_UPDOWN_CLASS     = 16,
14637         ICC_PROGRESS_CLASS   = 32,
14638         ICC_HOTKEY_CLASS     = 64,
14639         ICC_ANIMATE_CLASS    = 128,
14640         ICC_WIN95_CLASSES    = 255,
14641         ICC_DATE_CLASSES     = 256,
14642         ICC_USEREX_CLASSES   = 512,
14643         ICC_COOL_CLASSES     = 1024,
14644 	ICC_STANDARD_CLASSES = 0x00004000,
14645 }
14646 
14647 	enum WM_USER = 1024;
14648 }
14649 
14650 version(win32_widgets)
14651 	pragma(lib, "comdlg32");
14652 
14653 
14654 ///
14655 enum GenericIcons : ushort {
14656 	None, ///
14657 	// these happen to match the win32 std icons numerically if you just subtract one from the value
14658 	Cut, ///
14659 	Copy, ///
14660 	Paste, ///
14661 	Undo, ///
14662 	Redo, ///
14663 	Delete, ///
14664 	New, ///
14665 	Open, ///
14666 	Save, ///
14667 	PrintPreview, ///
14668 	Properties, ///
14669 	Help, ///
14670 	Find, ///
14671 	Replace, ///
14672 	Print, ///
14673 }
14674 
14675 enum FileDialogType {
14676 	Automatic,
14677 	Open,
14678 	Save
14679 }
14680 string previousFileReferenced;
14681 
14682 /++
14683 	Used in automatic menu functions to indicate that the user should be able to browse for a file.
14684 
14685 	Params:
14686 		storage = an alias to a `static string` variable that stores the last file referenced. It will
14687 		use this to pre-fill the dialog with a suggestion.
14688 
14689 		Please note that it MUST be `static` or you will get compile errors.
14690 
14691 		filters = the filters param to [getFileName]
14692 
14693 		type = the type if dialog to show. If `FileDialogType.Automatic`, it the driver code will
14694 		guess based on the function name. If it has the word "Save" or "Export" in it, it will show
14695 		a save dialog box. Otherwise, it will show an open dialog box.
14696 +/
14697 struct FileName(alias storage = previousFileReferenced, string[] filters = null, FileDialogType type = FileDialogType.Automatic) {
14698 	string name;
14699 	alias name this;
14700 }
14701 
14702 /++
14703 	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.
14704 
14705 	History:
14706 		onCancel was added November 6, 2021.
14707 
14708 		The dialog itself on Linux was modified on December 2, 2021 to include
14709 		a directory picker in addition to the command line completion view.
14710 
14711 		The `initialDirectory` argument was added November 9, 2022 (dub v10.10)
14712 	Future_directions:
14713 		I want to add some kind of custom preview and maybe thumbnail thing in the future,
14714 		at least on Linux, maybe on Windows too.
14715 +/
14716 void getOpenFileName(
14717 	void delegate(string) onOK,
14718 	string prefilledName = null,
14719 	string[] filters = null,
14720 	void delegate() onCancel = null,
14721 	string initialDirectory = null,
14722 )
14723 {
14724 	return getFileName(true, onOK, prefilledName, filters, onCancel, initialDirectory);
14725 }
14726 
14727 /// ditto
14728 void getSaveFileName(
14729 	void delegate(string) onOK,
14730 	string prefilledName = null,
14731 	string[] filters = null,
14732 	void delegate() onCancel = null,
14733 	string initialDirectory = null,
14734 )
14735 {
14736 	return getFileName(false, onOK, prefilledName, filters, onCancel, initialDirectory);
14737 }
14738 
14739 void getFileName(
14740 	bool openOrSave,
14741 	void delegate(string) onOK,
14742 	string prefilledName = null,
14743 	string[] filters = null, // format here is like ["Text files\0*.txt;*.text", "Image files\0*.png;*.jpg"]
14744 	void delegate() onCancel = null,
14745 	string initialDirectory = null,
14746 )
14747 {
14748 
14749 	version(win32_widgets) {
14750 		import core.sys.windows.commdlg;
14751 	/*
14752 	Ofn.lStructSize = sizeof(OPENFILENAME);
14753 	Ofn.hwndOwner = hWnd;
14754 	Ofn.lpstrFilter = szFilter;
14755 	Ofn.lpstrFile= szFile;
14756 	Ofn.nMaxFile = sizeof(szFile)/ sizeof(*szFile);
14757 	Ofn.lpstrFileTitle = szFileTitle;
14758 	Ofn.nMaxFileTitle = sizeof(szFileTitle);
14759 	Ofn.lpstrInitialDir = (LPSTR)NULL;
14760 	Ofn.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT;
14761 	Ofn.lpstrTitle = szTitle;
14762 	 */
14763 
14764 
14765 		wchar[1024] file = 0;
14766 		wchar[1024] filterBuffer = 0;
14767 		makeWindowsString(prefilledName, file[]);
14768 		OPENFILENAME ofn;
14769 		ofn.lStructSize = ofn.sizeof;
14770 		if(filters.length) {
14771 			string filter;
14772 			foreach(i, f; filters) {
14773 				filter ~= f;
14774 				filter ~= "\0";
14775 			}
14776 			filter ~= "\0";
14777 			ofn.lpstrFilter = makeWindowsString(filter, filterBuffer[], 0 /* already terminated */).ptr;
14778 		}
14779 		ofn.lpstrFile = file.ptr;
14780 		ofn.nMaxFile = file.length;
14781 
14782 		wchar[1024] initialDir = 0;
14783 		if(initialDirectory !is null) {
14784 			makeWindowsString(initialDirectory, initialDir[]);
14785 			ofn.lpstrInitialDir = file.ptr;
14786 		}
14787 
14788 		if(openOrSave ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn))
14789 		{
14790 			string okString = makeUtf8StringFromWindowsString(ofn.lpstrFile);
14791 			if(okString.length && okString[$-1] == '\0')
14792 				okString = okString[0..$-1];
14793 			onOK(okString);
14794 		} else {
14795 			if(onCancel)
14796 				onCancel();
14797 		}
14798 	} else version(custom_widgets) {
14799 		if(filters.length == 0)
14800 			filters = ["All Files\0*.*"];
14801 		auto picker = new FilePicker(prefilledName, filters, initialDirectory);
14802 		picker.onOK = onOK;
14803 		picker.onCancel = onCancel;
14804 		picker.show();
14805 	}
14806 }
14807 
14808 version(custom_widgets)
14809 private
14810 class FilePicker : Dialog {
14811 	void delegate(string) onOK;
14812 	void delegate() onCancel;
14813 	LineEdit lineEdit;
14814 
14815 	// returns common prefix
14816 	string loadFiles(string cwd, string[] filters...) {
14817 		string[] files;
14818 		string[] dirs;
14819 
14820 		string commonPrefix;
14821 
14822 		getFiles(cwd, (string name, bool isDirectory) {
14823 			if(name == ".")
14824 				return; // skip this as unnecessary
14825 			if(isDirectory)
14826 				dirs ~= name;
14827 			else {
14828 				foreach(filter; filters)
14829 				if(
14830 					filter.length <= 1 ||
14831 					filter == "*.*" ||
14832 					(filter[0] == '*' && name.endsWith(filter[1 .. $])) ||
14833 					(filter[$-1] == '*' && name.startsWith(filter[0 .. $ - 1]))
14834 				)
14835 				{
14836 					files ~= name;
14837 
14838 					if(filter.length > 0 && filter[$-1] == '*') {
14839 						if(commonPrefix is null) {
14840 							commonPrefix = name;
14841 						} else {
14842 							foreach(idx, char i; name) {
14843 								if(idx >= commonPrefix.length || i != commonPrefix[idx]) {
14844 									commonPrefix = commonPrefix[0 .. idx];
14845 									break;
14846 								}
14847 							}
14848 						}
14849 					}
14850 
14851 					break;
14852 				}
14853 			}
14854 		});
14855 
14856 		extern(C) static int comparator(scope const void* a, scope const void* b) {
14857 			auto sa = *cast(string*) a;
14858 			auto sb = *cast(string*) b;
14859 
14860 			for(int i = 0; i < sa.length; i++) {
14861 				if(i == sb.length)
14862 					return 1;
14863 				return sa[i] - sb[i];
14864 			}
14865 
14866 			return 0;
14867 		}
14868 
14869 		nonPhobosSort(files, &comparator);
14870 		nonPhobosSort(dirs, &comparator);
14871 
14872 		listWidget.clear();
14873 		dirWidget.clear();
14874 		foreach(name; dirs)
14875 			dirWidget.addOption(name);
14876 		foreach(name; files)
14877 			listWidget.addOption(name);
14878 
14879 		return commonPrefix;
14880 	}
14881 
14882 	ListWidget listWidget;
14883 	ListWidget dirWidget;
14884 
14885 	string currentDirectory;
14886 	string[] processedFilters;
14887 
14888 	//string[] filters = null, // format here is like ["Text files\0*.txt;*.text", "Image files\n*.png;*.jpg"]
14889 	this(string prefilledName, string[] filters, string initialDirectory, Window owner = null) {
14890 		super(300, 200, "Choose File..."); // owner);
14891 
14892 		foreach(filter; filters) {
14893 			while(filter.length && filter[0] != 0) {
14894 				filter = filter[1 .. $];
14895 			}
14896 			if(filter.length)
14897 				filter = filter[1 .. $]; // trim off the 0
14898 
14899 			while(filter.length) {
14900 				int idx = 0;
14901 				while(idx < filter.length && filter[idx] != ';') {
14902 					idx++;
14903 				}
14904 
14905 				processedFilters ~= filter[0 .. idx];
14906 				if(idx < filter.length)
14907 					idx++; // skip the ;
14908 				filter = filter[idx .. $];
14909 			}
14910 		}
14911 
14912 		currentDirectory = initialDirectory is null ? "." : initialDirectory;
14913 
14914 		{
14915 			auto hl = new HorizontalLayout(this);
14916 			dirWidget = new ListWidget(hl);
14917 			listWidget = new ListWidget(hl);
14918 
14919 			// double click events normally trigger something else but
14920 			// here user might be clicking kinda fast and we'd rather just
14921 			// keep it
14922 			dirWidget.addEventListener((scope DoubleClickEvent dev) {
14923 				auto ce = new ChangeEvent!void(dirWidget, () {});
14924 				ce.dispatch();
14925 			});
14926 
14927 			dirWidget.addEventListener((scope ChangeEvent!void sce) {
14928 				string v;
14929 				foreach(o; dirWidget.options)
14930 					if(o.selected) {
14931 						v = o.label;
14932 						break;
14933 					}
14934 				if(v.length) {
14935 					currentDirectory ~= "/" ~ v;
14936 					loadFiles(currentDirectory, processedFilters);
14937 				}
14938 			});
14939 
14940 			// double click here, on the other hand, selects the file
14941 			// and moves on
14942 			listWidget.addEventListener((scope DoubleClickEvent dev) {
14943 				OK();
14944 			});
14945 		}
14946 
14947 		lineEdit = new LineEdit(this);
14948 		lineEdit.focus();
14949 		lineEdit.addEventListener(delegate(CharEvent event) {
14950 			if(event.character == '\t' || event.character == '\n')
14951 				event.preventDefault();
14952 		});
14953 
14954 		listWidget.addEventListener(EventType.change, () {
14955 			foreach(o; listWidget.options)
14956 				if(o.selected)
14957 					lineEdit.content = o.label;
14958 		});
14959 
14960 		loadFiles(currentDirectory, processedFilters);
14961 
14962 		lineEdit.addEventListener((KeyDownEvent event) {
14963 			if(event.key == Key.Tab) {
14964 
14965 				auto current = lineEdit.content;
14966 				if(current.length >= 2 && current[0 ..2] == "./")
14967 					current = current[2 .. $];
14968 
14969 				auto commonPrefix = loadFiles(".", current ~ "*");
14970 
14971 				if(commonPrefix.length)
14972 					lineEdit.content = commonPrefix;
14973 
14974 				// FIXME: if that is a directory, add the slash? or even go inside?
14975 
14976 				event.preventDefault();
14977 			}
14978 		});
14979 
14980 		lineEdit.content = prefilledName;
14981 
14982 		auto hl = new HorizontalLayout(60, this);
14983 		auto cancelButton = new Button("Cancel", hl);
14984 		auto okButton = new Button("OK", hl);
14985 
14986 		cancelButton.addEventListener(EventType.triggered, &Cancel);
14987 		okButton.addEventListener(EventType.triggered, &OK);
14988 
14989 		this.addEventListener((KeyDownEvent event) {
14990 			if(event.key == Key.Enter || event.key == Key.PadEnter) {
14991 				event.preventDefault();
14992 				OK();
14993 			}
14994 			if(event.key == Key.Escape)
14995 				Cancel();
14996 		});
14997 
14998 	}
14999 
15000 	override void OK() {
15001 		if(lineEdit.content.length) {
15002 			string accepted;
15003 			auto c = lineEdit.content;
15004 			if(c.length && c[0] == '/')
15005 				accepted = c;
15006 			else
15007 				accepted = currentDirectory ~ "/" ~ lineEdit.content;
15008 
15009 			if(isDir(accepted)) {
15010 				// FIXME: would be kinda nice to support ~ and collapse these paths too
15011 				// FIXME: would also be nice to actually show the "Looking in..." directory and maybe the filters but later.
15012 				currentDirectory = accepted;
15013 				loadFiles(currentDirectory, processedFilters);
15014 				lineEdit.content = "";
15015 				return;
15016 			}
15017 
15018 			if(onOK)
15019 				onOK(accepted);
15020 		}
15021 		close();
15022 	}
15023 
15024 	override void Cancel() {
15025 		if(onCancel)
15026 			onCancel();
15027 		close();
15028 	}
15029 }
15030 
15031 private bool isDir(string name) {
15032 	version(Windows) {
15033 		auto ws = WCharzBuffer(name);
15034 		auto ret = GetFileAttributesW(ws.ptr);
15035 		if(ret == INVALID_FILE_ATTRIBUTES)
15036 			return false;
15037 		return (ret & FILE_ATTRIBUTE_DIRECTORY) != 0;
15038 	} else version(Posix) {
15039 		import core.sys.posix.sys.stat;
15040 		stat_t buf;
15041 		auto ret = stat((name ~ '\0').ptr, &buf);
15042 		if(ret == -1)
15043 			return false; // I could probably check more specific errors tbh
15044 		return (buf.st_mode & S_IFMT) == S_IFDIR;
15045 	} else return false;
15046 }
15047 
15048 /*
15049 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775947%28v=vs.85%29.aspx#check_boxes
15050 http://msdn.microsoft.com/en-us/library/windows/desktop/ms633574%28v=vs.85%29.aspx
15051 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775943%28v=vs.85%29.aspx
15052 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775951%28v=vs.85%29.aspx
15053 http://msdn.microsoft.com/en-us/library/windows/desktop/ms632680%28v=vs.85%29.aspx
15054 http://msdn.microsoft.com/en-us/library/windows/desktop/ms644996%28v=vs.85%29.aspx#message_box
15055 http://www.sbin.org/doc/Xlib/chapt_03.html
15056 
15057 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760433%28v=vs.85%29.aspx
15058 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760446%28v=vs.85%29.aspx
15059 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760443%28v=vs.85%29.aspx
15060 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760476%28v=vs.85%29.aspx
15061 */
15062 
15063 
15064 // These are all for setMenuAndToolbarFromAnnotatedCode
15065 /// This item in the menu will be preceded by a separator line
15066 /// Group: generating_from_code
15067 struct separator {}
15068 deprecated("It was misspelled, use separator instead") alias seperator = separator;
15069 /// Program-wide keyboard shortcut to trigger the action
15070 /// Group: generating_from_code
15071 struct accelerator { string keyString; }
15072 /// tells which menu the action will be on
15073 /// Group: generating_from_code
15074 struct menu { string name; }
15075 /// Describes which toolbar section the action appears on
15076 /// Group: generating_from_code
15077 struct toolbar { string groupName; }
15078 ///
15079 /// Group: generating_from_code
15080 struct icon { ushort id; }
15081 ///
15082 /// Group: generating_from_code
15083 struct label { string label; }
15084 ///
15085 /// Group: generating_from_code
15086 struct hotkey { dchar ch; }
15087 ///
15088 /// Group: generating_from_code
15089 struct tip { string tip; }
15090 
15091 
15092 /++
15093 	Observes and allows inspection of an object via automatic gui
15094 +/
15095 /// Group: generating_from_code
15096 ObjectInspectionWindow objectInspectionWindow(T)(T t) if(is(T == class)) {
15097 	return new ObjectInspectionWindowImpl!(T)(t);
15098 }
15099 
15100 class ObjectInspectionWindow : Window {
15101 	this(int a, int b, string c) {
15102 		super(a, b, c);
15103 	}
15104 
15105 	abstract void readUpdatesFromObject();
15106 }
15107 
15108 class ObjectInspectionWindowImpl(T) : ObjectInspectionWindow {
15109 	T t;
15110 	this(T t) {
15111 		this.t = t;
15112 
15113 		super(300, 400, "ObjectInspectionWindow - " ~ T.stringof);
15114 
15115 		foreach(memberName; __traits(derivedMembers, T)) {{
15116 			alias member = I!(__traits(getMember, t, memberName))[0];
15117 			alias type = typeof(member);
15118 			static if(is(type == int)) {
15119 				auto le = new LabeledLineEdit(memberName ~ ": ", this);
15120 				//le.addEventListener("char", (Event ev) {
15121 					//if((ev.character < '0' || ev.character > '9') && ev.character != '-')
15122 						//ev.preventDefault();
15123 				//});
15124 				le.addEventListener(EventType.change, (Event ev) {
15125 					__traits(getMember, t, memberName) = cast(type) stringToLong(ev.stringValue);
15126 				});
15127 
15128 				updateMemberDelegates[memberName] = () {
15129 					le.content = toInternal!string(__traits(getMember, t, memberName));
15130 				};
15131 			}
15132 		}}
15133 	}
15134 
15135 	void delegate()[string] updateMemberDelegates;
15136 
15137 	override void readUpdatesFromObject() {
15138 		foreach(k, v; updateMemberDelegates)
15139 			v();
15140 	}
15141 }
15142 
15143 /++
15144 	Creates a dialog based on a data structure.
15145 
15146 	---
15147 	dialog((YourStructure value) {
15148 		// the user filled in the struct and clicked OK,
15149 		// you can check the members now
15150 	});
15151 	---
15152 
15153 	Params:
15154 		initialData = the initial value to show in the dialog. It will not modify this unless
15155 		it is a class then it might, no promises.
15156 
15157 	History:
15158 		The overload that lets you specify `initialData` was added on December 30, 2021 (dub v10.5)
15159 +/
15160 /// Group: generating_from_code
15161 void dialog(T)(void delegate(T) onOK, void delegate() onCancel = null, string title = T.stringof) {
15162 	dialog(T.init, onOK, onCancel, title);
15163 }
15164 /// ditto
15165 void dialog(T)(T initialData, void delegate(T) onOK, void delegate() onCancel = null, string title = T.stringof) {
15166 	auto dg = new AutomaticDialog!T(initialData, onOK, onCancel, title);
15167 	dg.show();
15168 }
15169 
15170 private static template I(T...) { alias I = T; }
15171 
15172 
15173 private string beautify(string name, char space = ' ', bool allLowerCase = false) {
15174 	if(name == "id")
15175 		return allLowerCase ? name : "ID";
15176 
15177 	char[160] buffer;
15178 	int bufferIndex = 0;
15179 	bool shouldCap = true;
15180 	bool shouldSpace;
15181 	bool lastWasCap;
15182 	foreach(idx, char ch; name) {
15183 		if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
15184 
15185 		if((ch >= 'A' && ch <= 'Z') || ch == '_') {
15186 			if(lastWasCap) {
15187 				// two caps in a row, don't change. Prolly acronym.
15188 			} else {
15189 				if(idx)
15190 					shouldSpace = true; // new word, add space
15191 			}
15192 
15193 			lastWasCap = true;
15194 		} else {
15195 			lastWasCap = false;
15196 		}
15197 
15198 		if(shouldSpace) {
15199 			buffer[bufferIndex++] = space;
15200 			if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
15201 			shouldSpace = false;
15202 		}
15203 		if(shouldCap) {
15204 			if(ch >= 'a' && ch <= 'z')
15205 				ch -= 32;
15206 			shouldCap = false;
15207 		}
15208 		if(allLowerCase && ch >= 'A' && ch <= 'Z')
15209 			ch += 32;
15210 		buffer[bufferIndex++] = ch;
15211 	}
15212 	return buffer[0 .. bufferIndex].idup;
15213 }
15214 
15215 /++
15216 	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.
15217 +/
15218 class AutomaticDialog(T) : Dialog {
15219 	T t;
15220 
15221 	void delegate(T) onOK;
15222 	void delegate() onCancel;
15223 
15224 	override int paddingTop() { return defaultLineHeight; }
15225 	override int paddingBottom() { return defaultLineHeight; }
15226 	override int paddingRight() { return defaultLineHeight; }
15227 	override int paddingLeft() { return defaultLineHeight; }
15228 
15229 	this(T initialData, void delegate(T) onOK, void delegate() onCancel, string title) {
15230 		assert(onOK !is null);
15231 
15232 		t = initialData;
15233 
15234 		static if(is(T == class)) {
15235 			if(t is null)
15236 				t = new T();
15237 		}
15238 		this.onOK = onOK;
15239 		this.onCancel = onCancel;
15240 		super(400, cast(int)(__traits(allMembers, T).length * 2) * (defaultLineHeight + scaleWithDpi(4 + 2)) + defaultLineHeight + scaleWithDpi(56), title);
15241 
15242 		static if(is(T == class))
15243 			this.addDataControllerWidget(t);
15244 		else
15245 			this.addDataControllerWidget(&t);
15246 
15247 		auto hl = new HorizontalLayout(this);
15248 		auto stretch = new HorizontalSpacer(hl); // to right align
15249 		auto ok = new CommandButton("OK", hl);
15250 		auto cancel = new CommandButton("Cancel", hl);
15251 		ok.addEventListener(EventType.triggered, &OK);
15252 		cancel.addEventListener(EventType.triggered, &Cancel);
15253 
15254 		this.addEventListener((KeyDownEvent ev) {
15255 			if(ev.key == Key.Enter || ev.key == Key.PadEnter) {
15256 				ok.focus();
15257 				OK();
15258 				ev.preventDefault();
15259 			}
15260 			if(ev.key == Key.Escape) {
15261 				Cancel();
15262 				ev.preventDefault();
15263 			}
15264 		});
15265 
15266 		this.addEventListener((scope ClosedEvent ce) {
15267 			if(onCancel)
15268 				onCancel();
15269 		});
15270 
15271 		//this.children[0].focus();
15272 	}
15273 
15274 	override void OK() {
15275 		onOK(t);
15276 		close();
15277 	}
15278 
15279 	override void Cancel() {
15280 		if(onCancel)
15281 			onCancel();
15282 		close();
15283 	}
15284 }
15285 
15286 private template baseClassCount(Class) {
15287 	private int helper() {
15288 		int count = 0;
15289 		static if(is(Class bases == super)) {
15290 			foreach(base; bases)
15291 				static if(is(base == class))
15292 					count += 1 + baseClassCount!base;
15293 		}
15294 		return count;
15295 	}
15296 
15297 	enum int baseClassCount = helper();
15298 }
15299 
15300 private long stringToLong(string s) {
15301 	long ret;
15302 	if(s.length == 0)
15303 		return ret;
15304 	bool negative = s[0] == '-';
15305 	if(negative)
15306 		s = s[1 .. $];
15307 	foreach(ch; s) {
15308 		if(ch >= '0' && ch <= '9') {
15309 			ret *= 10;
15310 			ret += ch - '0';
15311 		}
15312 	}
15313 	if(negative)
15314 		ret = -ret;
15315 	return ret;
15316 }
15317 
15318 
15319 interface ReflectableProperties {
15320 	/++
15321 		Iterates the event's properties as strings. Note that keys may be repeated and a get property request may
15322 		call your sink with `null`. It it does, it means the key either doesn't request or cannot be represented by
15323 		json in the current implementation.
15324 
15325 		This is auto-implemented for you if you mixin [RegisterGetters] in your child classes and only have
15326 		properties of type `bool`, `int`, `double`, or `string`. For other ones, you will need to do it yourself
15327 		as of the June 2, 2021 release.
15328 
15329 		History:
15330 			Added June 2, 2021.
15331 
15332 		See_Also: [getPropertyAsString], [setPropertyFromString]
15333 	+/
15334 	void getPropertiesList(scope void delegate(string name) sink) const;// @nogc pure nothrow;
15335 	/++
15336 		Requests a property to be delivered to you as a string, through your `sink` delegate.
15337 
15338 		If the `value` is null, it means the property could not be retreived. If `valueIsJson`, it should
15339 		be interpreted as json, otherwise, it is just a plain string.
15340 
15341 		The sink should always be called exactly once for each call (it is basically a return value, but it might
15342 		use a local buffer it maintains instead of allocating a return value).
15343 
15344 		History:
15345 			Added June 2, 2021.
15346 
15347 		See_Also: [getPropertiesList], [setPropertyFromString]
15348 	+/
15349 	void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink);
15350 	/++
15351 		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.
15352 
15353 		History:
15354 			Added June 2, 2021.
15355 
15356 		See_Also: [getPropertiesList], [getPropertyAsString], [SetPropertyResult]
15357 	+/
15358 	SetPropertyResult setPropertyFromString(string name, scope const(char)[] str, bool strIsJson);
15359 
15360 	/// [setPropertyFromString] possible return values
15361 	enum SetPropertyResult {
15362 		success = 0, /// the property has been successfully set to the request value
15363 		notPermitted = -1, /// the property exists but it cannot be changed at this time
15364 		notImplemented = -2, /// the set function is not implemented for the given property (which may or may not exist)
15365 		noSuchProperty = -3, /// there is no property by that name
15366 		wrongFormat = -4, /// the string was given in the wrong format, e.g. passing "two" for an int value
15367 		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)
15368 	}
15369 
15370 	/++
15371 		You can mix this in to get an implementation in child classes. This does [setPropertyFromString].
15372 
15373 		Your original base class, however, must implement its own methods. I recommend doing the initial ones by hand.
15374 
15375 		For [Widget] and [Event], the library provides [Widget.Register] and [Event.Register] that call these for you, so you should
15376 		rarely need to use these building blocks directly.
15377 	+/
15378 	mixin template RegisterSetters() {
15379 		override SetPropertyResult setPropertyFromString(string name, scope const(char)[] value, bool valueIsJson) {
15380 			switch(name) {
15381 				foreach(memberName; __traits(derivedMembers, typeof(this))) {
15382 					case memberName:
15383 						static if(is(typeof(__traits(getMember, this, memberName)) : const bool)) {
15384 							if(value != "true" && value != "false")
15385 								return SetPropertyResult.wrongFormat;
15386 							__traits(getMember, this, memberName) = value == "true" ? true : false;
15387 							return SetPropertyResult.success;
15388 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const long)) {
15389 							import core.stdc.stdlib;
15390 							char[128] zero = 0;
15391 							if(buffer.length + 1 >= zero.length)
15392 								return SetPropertyResult.wrongFormat;
15393 							zero[0 .. buffer.length] = buffer[];
15394 							__traits(getMember, this, memberName) = strtol(buffer.ptr, null, 10);
15395 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const double)) {
15396 							import core.stdc.stdlib;
15397 							char[128] zero = 0;
15398 							if(buffer.length + 1 >= zero.length)
15399 								return SetPropertyResult.wrongFormat;
15400 							zero[0 .. buffer.length] = buffer[];
15401 							__traits(getMember, this, memberName) = strtod(buffer.ptr, null, 10);
15402 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const string)) {
15403 							__traits(getMember, this, memberName) = value.idup;
15404 						} else {
15405 							return SetPropertyResult.notImplemented;
15406 						}
15407 
15408 				}
15409 				default:
15410 					return super.setPropertyFromString(name, value, valueIsJson);
15411 			}
15412 		}
15413 	}
15414 
15415 	/++
15416 		You can mix this in to get an implementation in child classes. This does [getPropertyAsString] and [getPropertiesList].
15417 
15418 		Your original base class, however, must implement its own methods. I recommend doing the initial ones by hand.
15419 
15420 		For [Widget] and [Event], the library provides [Widget.Register] and [Event.Register] that call these for you, so you should
15421 		rarely need to use these building blocks directly.
15422 	+/
15423 	mixin template RegisterGetters() {
15424 		override void getPropertiesList(scope void delegate(string name) sink) const {
15425 			super.getPropertiesList(sink);
15426 
15427 			foreach(memberName; __traits(derivedMembers, typeof(this))) {
15428 				sink(memberName);
15429 			}
15430 		}
15431 		override void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink) {
15432 			switch(name) {
15433 				foreach(memberName; __traits(derivedMembers, typeof(this))) {
15434 					case memberName:
15435 						static if(is(typeof(__traits(getMember, this, memberName)) : const bool)) {
15436 							sink(name, __traits(getMember, this, memberName) ? "true" : "false", true);
15437 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const long)) {
15438 							import core.stdc.stdio;
15439 							char[32] buffer;
15440 							auto len = snprintf(buffer.ptr, buffer.length, "%lld", cast(long) __traits(getMember, this, memberName));
15441 							sink(name, buffer[0 .. len], true);
15442 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const double)) {
15443 							import core.stdc.stdio;
15444 							char[32] buffer;
15445 							auto len = snprintf(buffer.ptr, buffer.length, "%f", cast(double) __traits(getMember, this, memberName));
15446 							sink(name, buffer[0 .. len], true);
15447 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const string)) {
15448 							sink(name, __traits(getMember, this, memberName), false);
15449 							//sinkJsonString(memberName, __traits(getMember, this, memberName), sink);
15450 						} else {
15451 							sink(name, null, true);
15452 						}
15453 
15454 					return;
15455 				}
15456 				default:
15457 					return super.getPropertyAsString(name, sink);
15458 			}
15459 		}
15460 	}
15461 }
15462 
15463 private struct Stack(T) {
15464 	this(int maxSize) {
15465 		internalLength = 0;
15466 		arr = initialBuffer[];
15467 	}
15468 
15469 	///.
15470 	void push(T t) {
15471 		if(internalLength >= arr.length) {
15472 			auto oldarr = arr;
15473 			if(arr.length < 4096)
15474 				arr = new T[arr.length * 2];
15475 			else
15476 				arr = new T[arr.length + 4096];
15477 			arr[0 .. oldarr.length] = oldarr[];
15478 		}
15479 
15480 		arr[internalLength] = t;
15481 		internalLength++;
15482 	}
15483 
15484 	///.
15485 	T pop() {
15486 		assert(internalLength);
15487 		internalLength--;
15488 		return arr[internalLength];
15489 	}
15490 
15491 	///.
15492 	T peek() {
15493 		assert(internalLength);
15494 		return arr[internalLength - 1];
15495 	}
15496 
15497 	///.
15498 	@property bool empty() {
15499 		return internalLength ? false : true;
15500 	}
15501 
15502 	///.
15503 	private T[] arr;
15504 	private size_t internalLength;
15505 	private T[64] initialBuffer;
15506 	// 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),
15507 	// using this saves us a bunch of trips to the GC. In my last profiling, I got about a 50x improvement in the push()
15508 	// function thanks to this, and push() was actually one of the slowest individual functions in the code!
15509 }
15510 
15511 /// 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.
15512 private struct WidgetStream {
15513 
15514 	///.
15515 	@property Widget front() {
15516 		return current.widget;
15517 	}
15518 
15519 	/// Use Widget.tree instead.
15520 	this(Widget start) {
15521 		current.widget = start;
15522 		current.childPosition = -1;
15523 		isEmpty = false;
15524 		stack = typeof(stack)(0);
15525 	}
15526 
15527 	/*
15528 		Handle it
15529 		handle its children
15530 
15531 	*/
15532 
15533 	///.
15534 	void popFront() {
15535 	    more:
15536 	    	if(isEmpty) return;
15537 
15538 		// FIXME: the profiler says this function is somewhat slow (noticeable because it can be called a lot of times)
15539 
15540 		current.childPosition++;
15541 		if(current.childPosition >= current.widget.children.length) {
15542 			if(stack.empty())
15543 				isEmpty = true;
15544 			else {
15545 				current = stack.pop();
15546 				goto more;
15547 			}
15548 		} else {
15549 			stack.push(current);
15550 			current.widget = current.widget.children[current.childPosition];
15551 			current.childPosition = -1;
15552 		}
15553 	}
15554 
15555 	///.
15556 	@property bool empty() {
15557 		return isEmpty;
15558 	}
15559 
15560 	private:
15561 
15562 	struct Current {
15563 		Widget widget;
15564 		int childPosition;
15565 	}
15566 
15567 	Current current;
15568 
15569 	Stack!(Current) stack;
15570 
15571 	bool isEmpty;
15572 }
15573 
15574 
15575 /+
15576 
15577 	I could fix up the hierarchy kinda like this
15578 
15579 	class Widget {
15580 		Widget[] children() { return null; }
15581 	}
15582 	interface WidgetContainer {
15583 		Widget asWidget();
15584 		void addChild(Widget w);
15585 
15586 		// alias asWidget this; // but meh
15587 	}
15588 
15589 	Widget can keep a (Widget parent) ctor, but it should prolly deprecate and tell people to instead change their ctors to take WidgetContainer instead.
15590 
15591 	class Layout : Widget, WidgetContainer {}
15592 
15593 	class Window : WidgetContainer {}
15594 
15595 
15596 	All constructors that previously took Widgets should now take WidgetContainers instead
15597 
15598 
15599 
15600 	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".
15601 +/
15602 
15603 /+
15604 	LAYOUTS 2.0
15605 
15606 	can just be assigned as a function. assigning a new one will cause it to be immediately called.
15607 
15608 	they simply are responsible for the recomputeChildLayout. If this pointer is null, it uses the default virtual one.
15609 
15610 	recomputeChildLayout only really needs a property accessor proxy... just the layout info too.
15611 
15612 	and even Paint can just use computedStyle...
15613 
15614 		background color
15615 		font
15616 		border color and style
15617 
15618 	And actually the style proxy can offer some helper routines to draw these like the draw 3d box
15619 		please note that many widgets and in some modes will completely ignore properties as they will.
15620 		they are just hints you set, not promises.
15621 
15622 
15623 
15624 
15625 
15626 	So generally the existing virtual functions are just the default for the class. But individual objects
15627 	or stylesheets can override this. The virtual ones count as tag-level specificity in css.
15628 +/
15629 
15630 /++
15631 	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.
15632 
15633 	History:
15634 		Added May 24, 2021.
15635 +/
15636 struct WidgetBackground {
15637 	/++
15638 		A background with the given solid color.
15639 	+/
15640 	this(Color color) {
15641 		this.color = color;
15642 	}
15643 
15644 	this(WidgetBackground bg) {
15645 		this = bg;
15646 	}
15647 
15648 	/++
15649 		Creates a widget from the string.
15650 
15651 		Currently, it only supports solid colors via [Color.fromString], but it will likely be expanded in the future to something more like css.
15652 	+/
15653 	static WidgetBackground fromString(string s) {
15654 		return WidgetBackground(Color.fromString(s));
15655 	}
15656 
15657 	/++
15658 		The background is not necessarily a solid color, but you can always specify a color as a fallback.
15659 
15660 		History:
15661 			Made `public` on December 18, 2022 (dub v10.10).
15662 	+/
15663 	Color color;
15664 }
15665 
15666 /++
15667 	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!)
15668 
15669 	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.
15670 
15671 	You should not inherit from this directly, but instead use [VisualTheme].
15672 
15673 	History:
15674 		Added May 8, 2021
15675 +/
15676 abstract class BaseVisualTheme {
15677 	/// Don't implement this, instead use [VisualTheme] and implement `paint` methods on specific subclasses you want to override.
15678 	abstract void doPaint(Widget widget, WidgetPainter painter);
15679 
15680 	/+
15681 	/// Don't implement this, instead use [VisualTheme] and implement `StyleOverride` aliases on specific subclasses you want to override.
15682 	abstract void useStyleProperties(Widget w, scope void delegate(scope Widget.Style props) dg);
15683 	+/
15684 
15685 	/++
15686 		Returns the property as a string, or null if it was not overridden in the style definition. The idea here is something like css,
15687 		where the interpretation of the string varies for each property and may include things like measurement units.
15688 	+/
15689 	abstract string getPropertyString(Widget widget, string propertyName);
15690 
15691 	/++
15692 		Default background color of the window. Widgets also use this to simulate transparency.
15693 
15694 		Probably some shade of grey.
15695 	+/
15696 	abstract Color windowBackgroundColor();
15697 	abstract Color widgetBackgroundColor();
15698 	abstract Color foregroundColor();
15699 	abstract Color lightAccentColor();
15700 	abstract Color darkAccentColor();
15701 
15702 	/++
15703 		Colors used to indicate active selections in lists and text boxes, etc.
15704 	+/
15705 	abstract Color selectionForegroundColor();
15706 	/// ditto
15707 	abstract Color selectionBackgroundColor();
15708 
15709 	deprecated("Use selectionForegroundColor and selectionBackgroundColor instead") Color selectionColor() { return selectionBackgroundColor(); }
15710 
15711 	/++
15712 		If you return `null` it will use simpledisplay's default. Otherwise, you return what font you want and it will cache it internally.
15713 	+/
15714 	abstract OperatingSystemFont defaultFont(int dpi);
15715 
15716 	private OperatingSystemFont[int] defaultFontCache_;
15717 	private OperatingSystemFont defaultFontCached(int dpi) {
15718 		if(dpi !in defaultFontCache_) {
15719 			// FIXME: set this to false if X disconnect or if visual theme changes
15720 			defaultFontCache_[dpi] = defaultFont(dpi);
15721 		}
15722 		return defaultFontCache_[dpi];
15723 	}
15724 }
15725 
15726 /+
15727 	A widget should have:
15728 		classList
15729 		dataset
15730 		attributes
15731 		computedStyles
15732 		state (persistent)
15733 		dynamic state (focused, hover, etc)
15734 +/
15735 
15736 // visualTheme.computedStyle(this).paddingLeft
15737 
15738 
15739 /++
15740 	This is your entry point to create your own visual theme for custom widgets.
15741 
15742 	You will want to inherit from this with a `final` class, passing your own class as the `CRTP` argument, then define the necessary methods.
15743 
15744 	Compatibility note: future versions of minigui may add new methods here. You will likely need to implement them when updating.
15745 +/
15746 abstract class VisualTheme(CRTP) : BaseVisualTheme {
15747 	override string getPropertyString(Widget widget, string propertyName) {
15748 		return null;
15749 	}
15750 
15751 	/+
15752 		mixin StyleOverride!Widget
15753 	final override void useStyleProperties(Widget w, scope void delegate(scope Widget.Style props) dg) {
15754 		w.useStyleProperties(dg);
15755 	}
15756 	+/
15757 
15758 	final override void doPaint(Widget widget, WidgetPainter painter) {
15759 		auto derived = cast(CRTP) cast(void*) this;
15760 
15761 		scope void delegate(Widget, WidgetPainter) bestMatch;
15762 		int bestMatchScore;
15763 
15764 		static if(__traits(hasMember, CRTP, "paint"))
15765 		foreach(overload; __traits(getOverloads, CRTP, "paint")) {
15766 			static if(is(typeof(overload) Params == __parameters)) {
15767 				static assert(Params.length == 2);
15768 				static assert(is(Params[0] : Widget));
15769 				static assert(is(Params[1] == WidgetPainter));
15770 				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);
15771 
15772 				alias type = Params[0];
15773 				if(cast(type) widget) {
15774 					auto score = baseClassCount!type;
15775 
15776 					if(score > bestMatchScore) {
15777 						bestMatch = cast(typeof(bestMatch)) &__traits(child, derived, overload);
15778 						bestMatchScore = score;
15779 					}
15780 				}
15781 			} else static assert(0, "paint should be a method.");
15782 		}
15783 
15784 		if(bestMatch)
15785 			bestMatch(widget, painter);
15786 		else
15787 			widget.paint(painter);
15788 	}
15789 
15790 	deprecated("Add an `int dpi` argument to your override now.") OperatingSystemFont defaultFont() { return null; }
15791 
15792 	// 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
15793 	// mixin Beautiful95Theme;
15794 	mixin DefaultLightTheme;
15795 
15796 	private static struct Cached {
15797 		// i prolly want to do this
15798 	}
15799 }
15800 
15801 /// ditto
15802 mixin template Beautiful95Theme() {
15803 	override Color windowBackgroundColor() { return Color(212, 212, 212); }
15804 	override Color widgetBackgroundColor() { return Color.white; }
15805 	override Color foregroundColor() { return Color.black; }
15806 	override Color darkAccentColor() { return Color(172, 172, 172); }
15807 	override Color lightAccentColor() { return Color(223, 223, 223); }
15808 	override Color selectionForegroundColor() { return Color.white; }
15809 	override Color selectionBackgroundColor() { return Color(0, 0, 128); }
15810 	override OperatingSystemFont defaultFont(int dpi) { return null; } // will just use the default out of simpledisplay's xfontstr
15811 }
15812 
15813 /// ditto
15814 mixin template DefaultLightTheme() {
15815 	override Color windowBackgroundColor() { return Color(232, 232, 232); }
15816 	override Color widgetBackgroundColor() { return Color.white; }
15817 	override Color foregroundColor() { return Color.black; }
15818 	override Color darkAccentColor() { return Color(172, 172, 172); }
15819 	override Color lightAccentColor() { return Color(223, 223, 223); }
15820 	override Color selectionForegroundColor() { return Color.white; }
15821 	override Color selectionBackgroundColor() { return Color(0, 0, 128); }
15822 	override OperatingSystemFont defaultFont(int dpi) {
15823 		version(Windows)
15824 			return new OperatingSystemFont("Segoe UI");
15825 		else {
15826 			// FIXME: undo xft's scaling so we don't end up double scaled
15827 			return new OperatingSystemFont("DejaVu Sans", 9 * dpi / 96);
15828 		}
15829 	}
15830 }
15831 
15832 /// ditto
15833 mixin template DefaultDarkTheme() {
15834 	override Color windowBackgroundColor() { return Color(64, 64, 64); }
15835 	override Color widgetBackgroundColor() { return Color.black; }
15836 	override Color foregroundColor() { return Color.white; }
15837 	override Color darkAccentColor() { return Color(20, 20, 20); }
15838 	override Color lightAccentColor() { return Color(80, 80, 80); }
15839 	override Color selectionForegroundColor() { return Color.white; }
15840 	override Color selectionBackgroundColor() { return Color(128, 0, 128); }
15841 	override OperatingSystemFont defaultFont(int dpi) {
15842 		version(Windows)
15843 			return new OperatingSystemFont("Segoe UI", 12);
15844 		else
15845 			return new OperatingSystemFont("DejaVu Sans", 9 * dpi / 96);
15846 	}
15847 }
15848 
15849 /// ditto
15850 alias DefaultTheme = DefaultLightTheme;
15851 
15852 final class DefaultVisualTheme : VisualTheme!DefaultVisualTheme {
15853 	/+
15854 	OperatingSystemFont defaultFont() { return new OperatingSystemFont("Times New Roman", 8, FontWeight.medium); }
15855 	Color windowBackgroundColor() { return Color(242, 242, 242); }
15856 	Color darkAccentColor() { return windowBackgroundColor; }
15857 	Color lightAccentColor() { return windowBackgroundColor; }
15858 	+/
15859 }
15860 
15861 /++
15862 	Event fired when an [Observeable] variable changes. You will want to add an event listener referencing
15863 	the field like `widget.addEventListener((scope StateChanged!(Whatever.field) ev) { });`
15864 
15865 	History:
15866 		Moved from minigui_addons.webview to main minigui on November 27, 2021 (dub v10.4)
15867 +/
15868 class StateChanged(alias field) : Event {
15869 	enum EventString = __traits(identifier, __traits(parent, field)) ~ "." ~ __traits(identifier, field) ~ ":change";
15870 	override bool cancelable() const { return false; }
15871 	this(Widget target, typeof(field) newValue) {
15872 		this.newValue = newValue;
15873 		super(EventString, target);
15874 	}
15875 
15876 	typeof(field) newValue;
15877 }
15878 
15879 /++
15880 	Convenience function to add a `triggered` event listener.
15881 
15882 	Its implementation is simply `w.addEventListener("triggered", dg);`
15883 
15884 	History:
15885 		Added November 27, 2021 (dub v10.4)
15886 +/
15887 void addWhenTriggered(Widget w, void delegate() dg) {
15888 	w.addEventListener("triggered", dg);
15889 }
15890 
15891 /++
15892 	Observable varables can be added to widgets and when they are changed, it fires
15893 	off a [StateChanged] event so you can react to it.
15894 
15895 	It is implemented as a getter and setter property, along with another helper you
15896 	can use to subscribe whith is `name_changed`. You can also subscribe to the [StateChanged]
15897 	event through the usual means. Just give the name of the variable. See [StateChanged] for an
15898 	example.
15899 
15900 	History:
15901 		Moved from minigui_addons.webview to main minigui on November 27, 2021 (dub v10.4)
15902 +/
15903 mixin template Observable(T, string name) {
15904 	private T backing;
15905 
15906 	mixin(q{
15907 		void } ~ name ~ q{_changed (void delegate(T) dg) {
15908 			this.addEventListener((StateChanged!this_thing ev) {
15909 				dg(ev.newValue);
15910 			});
15911 		}
15912 
15913 		@property T } ~ name ~ q{ () {
15914 			return backing;
15915 		}
15916 
15917 		@property void } ~ name ~ q{ (T t) {
15918 			backing = t;
15919 			auto event = new StateChanged!this_thing(this, t);
15920 			event.dispatch();
15921 		}
15922 	});
15923 
15924 	mixin("private alias this_thing = " ~ name ~ ";");
15925 }
15926 
15927 
15928 private bool startsWith(string test, string thing) {
15929 	if(test.length < thing.length)
15930 		return false;
15931 	return test[0 .. thing.length] == thing;
15932 }
15933 
15934 private bool endsWith(string test, string thing) {
15935 	if(test.length < thing.length)
15936 		return false;
15937 	return test[$ - thing.length .. $] == thing;
15938 }
15939 
15940 // still do layout delegation
15941 // and... split off Window from Widget.
15942 
15943 version(minigui_screenshots)
15944 struct Screenshot {
15945 	string name;
15946 }
15947 
15948 version(minigui_screenshots)
15949 static if(__VERSION__ > 2092)
15950 mixin(q{
15951 shared static this() {
15952 	import core.runtime;
15953 
15954 	static UnitTestResult screenshotMagic() {
15955 		string name;
15956 
15957 		import arsd.png;
15958 
15959 		auto results = new Window();
15960 		auto button = new Button("do it", results);
15961 
15962 		Window.newWindowCreated = delegate(Window w) {
15963 			Timer timer;
15964 			timer = new Timer(250, {
15965 				auto img = w.win.takeScreenshot();
15966 				timer.destroy();
15967 
15968 				version(Windows)
15969 					writePng("/var/www/htdocs/minigui-screenshots/windows/" ~ name ~ ".png", img);
15970 				else
15971 					writePng("/var/www/htdocs/minigui-screenshots/linux/" ~ name ~ ".png", img);
15972 
15973 				w.close();
15974 			});
15975 		};
15976 
15977 		button.addWhenTriggered( {
15978 
15979 		foreach(test; __traits(getUnitTests, mixin(__MODULE__))) {
15980 			name = null;
15981 			static foreach(attr; __traits(getAttributes, test)) {
15982 				static if(is(typeof(attr) == Screenshot))
15983 					name = attr.name;
15984 			}
15985 			if(name.length) {
15986 				test();
15987 			}
15988 		}
15989 
15990 		});
15991 
15992 		results.loop();
15993 
15994 		return UnitTestResult(0, 0, false, false);
15995 	}
15996 
15997 
15998 	Runtime.extendedModuleUnitTester = &screenshotMagic;
15999 }
16000 });
16001 version(minigui_screenshots) {
16002 	version(unittest)
16003 		void main() {}
16004 	else static assert(0, "dont forget the -unittest flag to dmd");
16005 }
16006 
16007 // FIXME: i called hotkey accelerator in some places. hotkey = key when menu is active like E&xit. accelerator = global shortcut.
16008 // FIXME: make multiple accelerators disambiguate based ona rgs
16009 // FIXME: MainWindow ctor should have same arg order as Window
16010 // FIXME: mainwindow ctor w/ client area size instead of total size.
16011 // 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.
16012 // FIXME: tri-state checkbox
16013 // FIXME: subordinate controls grouping...