1 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb775498%28v=vs.85%29.aspx
2 
3 // for responsive design, a collapsible widget that if it doesn't have enough room, it just automatically becomes a "more" button or whatever.
4 
5 // responsive minigui, menu search, and file open with a preview hook on the side.
6 
7 /*
8 
9 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
10 
11 the virtual functions remain as the default calculated values. then the reads go through some proxy object that can override it...
12 */
13 
14 // FIXME: text label must be copyable to the clipboard, at least as a full chunk.
15 
16 // FIXME: opt-in file picker widget with image support
17 
18 // FIXME: number widget
19 
20 // https://www.codeguru.com/cpp/controls/buttonctrl/advancedbuttons/article.php/c5161/Native-Win32-ThemeAware-OwnerDraw-Controls-No-MFC.htm
21 // https://docs.microsoft.com/en-us/windows/win32/controls/using-visual-styles
22 
23 // osx style menu search.
24 
25 // would be cool for a scroll bar to have marking capabilities
26 // kinda like vim's marks just on clicks etc and visual representation
27 // generically. may be cool to add an up arrow to the bottom too
28 //
29 // leave a shadow of where you last were for going back easily
30 
31 // So a window needs to have a selection, and that can be represented by a type. This is manipulated by various
32 // functions like cut, copy, paste. Widgets can have a selection and that would assert teh selection ownership for
33 // the window.
34 
35 // so what about context menus?
36 
37 // https://docs.microsoft.com/en-us/windows/desktop/Controls/about-custom-draw
38 
39 // FIXME: make the scroll thing go to bottom when the content changes.
40 
41 // 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
42 
43 // FIXME: the scroll area MUST be fixed to use the proper apis under the hood.
44 
45 
46 // FIXME: add a command search thingy built in and implement tip.
47 // FIXME: omg omg what if menu functions have arguments and it can pop up a gui or command line script them?!
48 
49 // On Windows:
50 // FIXME: various labels look broken in high contrast mode
51 // FIXME: changing themes while the program is upen doesn't trigger a redraw
52 
53 // add note about manifest to documentation. also icons.
54 
55 // a pager control is just a horizontal scroll area just with arrows on the sides instead of a scroll bar
56 // FIXME: clear the corner of scrollbars if they pop up
57 
58 // minigui needs to have a stdout redirection for gui mode on windows writeln
59 
60 // I kinda wanna do state reacting. sort of. idk tho
61 
62 // need a viewer widget that works like a web page - arrows scroll down consistently
63 
64 // I want a nanovega widget, and a svg widget with some kind of event handlers attached to the inside.
65 
66 // FIXME: the menus should be a bit more discoverable, at least a single click to open the others instead of two.
67 // and help info about menu items.
68 // and search in menus?
69 
70 // FIXME: a scroll area event signaling when a thing comes into view might be good
71 // FIXME: arrow key navigation and accelerators in dialog boxes will be a must
72 
73 // FIXME: unify Windows style line endings
74 
75 /*
76 	TODO:
77 
78 	pie menu
79 
80 	class Form with submit behavior -- see AutomaticDialog
81 
82 	disabled widgets and menu items
83 
84 	event cleanup
85 	tooltips.
86 	api improvements
87 
88 	margins are kinda broken, they don't collapse like they should. at least.
89 
90 	a table form btw would be a horizontal layout of vertical layouts holding each column
91 	that would give the same width things
92 */
93 
94 /*
95 
96 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
97 */
98 
99 /++
100 	minigui is a smallish GUI widget library, aiming to be on par with at least
101 	HTML4 forms and a few other expected gui components. It uses native controls
102 	on Windows and does its own thing on Linux (Mac is not currently supported but
103 	may be later, and should use native controls) to keep size down. The Linux
104 	appearance is similar to Windows 95 and avoids using images to maintain network
105 	efficiency on remote X connections, though you can customize that.
106 
107 
108 	minigui's only required dependencies are [arsd.simpledisplay] and [arsd.color],
109 	on which it is built. simpledisplay provides the low-level interfaces and minigui
110 	builds the concept of widgets inside the windows on top of it.
111 
112 	Its #1 goal is to be useful without being large and complicated like GTK and Qt.
113 	It isn't hugely concerned with appearance - on Windows, it just uses the native
114 	controls and native theme, and on Linux, it keeps it simple and I may change that
115 	at any time, though after May 2021, you can customize some things with css-inspired
116 	[Widget.Style] classes. (On Windows, if you compile with `-version=custom_widgets`,
117 	you can use the custom implementation there too, but... you shouldn't.)
118 
119 	The event model is similar to what you use in the browser with Javascript and the
120 	layout engine tries to automatically fit things in, similar to a css flexbox.
121 
122 	FOR BEST RESULTS: be sure to link with the appropriate subsystem command
123 	`-L/SUBSYSTEM:WINDOWS:5.0`, for example, because otherwise you'll get a
124 	console and other visual bugs.
125 
126 	HTML_To_Classes:
127 	$(SMALL_TABLE
128 		HTML Code | Minigui Class
129 
130 		`<input type="text">` | [LineEdit]
131 		`<textarea>` | [TextEdit]
132 		`<select>` | [DropDownSelection]
133 		`<input type="checkbox">` | [Checkbox]
134 		`<input type="radio">` | [Radiobox]
135 		`<button>` | [Button]
136 	)
137 
138 
139 	Stretchiness:
140 		The default is 4. You can use larger numbers for things that should
141 		consume a lot of space, and lower numbers for ones that are better at
142 		smaller sizes.
143 
144 	Overlapped_input:
145 		COMING EVENTUALLY:
146 		minigui will include a little bit of I/O functionality that just works
147 		with the event loop. If you want to get fancy, I suggest spinning up
148 		another thread and posting events back and forth.
149 
150 	$(H2 Add ons)
151 		See the `minigui_addons` directory in the arsd repo for some add on widgets
152 		you can import separately too.
153 
154 	$(H3 XML definitions)
155 		If you use [arsd.minigui_xml], you can create widget trees from XML at runtime.
156 
157 	$(H3 Scriptability)
158 		minigui is compatible with [arsd.script]. If you see `@scriptable` on a method
159 		in this documentation, it means you can call it from the script language.
160 
161 		Tip: to allow easy creation of widget trees from script, import [arsd.minigui_xml]
162 		and make [arsd.minigui_xml.makeWidgetFromString] available to your script:
163 
164 		---
165 		import arsd.minigui_xml;
166 		import arsd.script;
167 
168 		var globals = var.emptyObject;
169 		globals.makeWidgetFromString = &makeWidgetFromString;
170 
171 		// this now works
172 		interpret(`var window = makeWidgetFromString("<MainWindow />");`, globals);
173 		---
174 
175 		More to come.
176 
177 	History:
178 		Minigui had mostly additive changes or bug fixes since its inception until May 2021.
179 
180 		In May 2021 (dub v10.0), minigui got an overhaul. If it was versioned independently, I'd
181 		tag this as version 2.0.
182 
183 		Among the changes:
184 		$(LIST
185 			* 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.
186 
187 			See [Event] for details.
188 
189 			* 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.
190 
191 			See [DoubleClickEvent] for details.
192 
193 			* 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.
194 
195 			See [Widget.Style] for details.
196 
197 			// * A widget must now opt in to receiving keyboard focus, rather than opting out.
198 
199 			* 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.
200 
201 			* 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.
202 
203 			* [LabeledLineEdit] changed its default layout to vertical instead of horizontal. You can restore the old behavior by passing a `TextAlignment` argument to the constructor.
204 
205 			* 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.
206 
207 			* Various non-breaking additions.
208 		)
209 +/
210 module arsd.minigui;
211 
212 /++
213 	This hello world sample will have an oversized button, but that's ok, you see your first window!
214 +/
215 version(Demo)
216 unittest {
217 	import arsd.minigui;
218 
219 	void main() {
220 		auto window = new MainWindow();
221 
222 		auto hello = new TextLabel("Hello, world!", TextAlignment.Center, window);
223 		auto button = new Button("Close", window);
224 		button.addEventListener((scope ClickEvent ev) {
225 			window.close();
226 		});
227 
228 		window.loop();
229 	}
230 
231 	main(); // exclude from docs
232 }
233 
234 public import arsd.simpledisplay;
235 /++
236 	Convenience import to override the Windows GDI Rectangle function (you can still use it through fully-qualified imports)
237 
238 	History:
239 		Was private until May 15, 2021.
240 +/
241 public alias Rectangle = arsd.color.Rectangle; // I specifically want this in here, not the win32 GDI Rectangle()
242 
243 version(Windows) {
244 	import core.sys.windows.winnls;
245 	import core.sys.windows.windef;
246 	import core.sys.windows.basetyps;
247 	import core.sys.windows.winbase;
248 	import core.sys.windows.winuser;
249 	import core.sys.windows.wingdi;
250 	static import gdi = core.sys.windows.wingdi;
251 }
252 
253 // this is a hack to call the original window procedure on native win32 widgets if our event listener thing prevents default.
254 private bool lastDefaultPrevented;
255 
256 /// Methods marked with this are available from scripts if added to the [arsd.script] engine.
257 alias scriptable = arsd_jsvar_compatible;
258 
259 version(Windows) {
260 	// use native widgets when available unless specifically asked otherwise
261 	version(custom_widgets) {
262 		enum bool UsingCustomWidgets = true;
263 		enum bool UsingWin32Widgets = false;
264 	} else {
265 		version = win32_widgets;
266 		enum bool UsingCustomWidgets = false;
267 		enum bool UsingWin32Widgets = true;
268 	}
269 	// and native theming when needed
270 	//version = win32_theming;
271 } else {
272 	enum bool UsingCustomWidgets = true;
273 	enum bool UsingWin32Widgets = false;
274 	version=custom_widgets;
275 }
276 
277 
278 
279 /*
280 
281 	The main goals of minigui.d are to:
282 		1) Provide basic widgets that just work in a lightweight lib.
283 		   I basically want things comparable to a plain HTML form,
284 		   plus the easy and obvious things you expect from Windows
285 		   apps like a menu.
286 		2) Use native things when possible for best functionality with
287 		   least library weight.
288 		3) Give building blocks to provide easy extension for your
289 		   custom widgets, or hooking into additional native widgets
290 		   I didn't wrap.
291 		4) Provide interfaces for easy interaction between third
292 		   party minigui extensions. (event model, perhaps
293 		   signals/slots, drop-in ease of use bits.)
294 		5) Zero non-system dependencies, including Phobos as much as
295 		   I reasonably can. It must only import arsd.color and
296 		   my simpledisplay.d. If you need more, it will have to be
297 		   an extension module.
298 		6) An easy layout system that generally works.
299 
300 	A stretch goal is to make it easy to make gui forms with code,
301 	some kind of resource file (xml?) and even a wysiwyg designer.
302 
303 	Another stretch goal is to make it easy to hook data into the gui,
304 	including from reflection. So like auto-generate a form from a
305 	function signature or struct definition, or show a list from an
306 	array that automatically updates as the array is changed. Then,
307 	your program focuses on the data more than the gui interaction.
308 
309 
310 
311 	STILL NEEDED:
312 		* combo box. (this is diff than select because you can free-form edit too. more like a lineedit with autoselect)
313 		* slider
314 		* listbox
315 		* spinner
316 		* label?
317 		* rich text
318 */
319 
320 
321 /+
322 	enum LayoutMethods {
323 		 verticalFlex,
324 		 horizontalFlex,
325 		 inlineBlock, // left to right, no stretch, goes to next line as needed
326 		 static, // just set to x, y
327 		 verticalNoStretch, // browser style default
328 
329 		 inlineBlockFlex, // goes left to right, flexing, but when it runs out of space, it spills into next line
330 
331 		 grid, // magic
332 	}
333 +/
334 
335 /++
336 	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.
337 
338 
339 	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.
340 
341 	---
342 	class MinimalWidget : Widget {
343 		this(Widget parent) {
344 			super(parent);
345 		}
346 	}
347 	---
348 
349 	$(SIDEBAR
350 		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.
351 	)
352 
353 	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.
354 
355 	Among the things you'll most likely want to change in your custom widget:
356 
357 	$(LIST
358 		* 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.)
359 
360 		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.
361 
362 		Do this $(I after) calling the `super` constructor.
363 
364 		* 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.
365 
366 		Generally, painting is a job for leaf widgets, since child widgets would obscure your drawing area anyway. However, it is your decision.
367 
368 		* 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.
369 
370 		* 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.
371 	)
372 
373 	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.
374 
375 	It is also possible to embed a [SimpleWindow]-based native window inside a widget. See [OpenGlWidget]'s source code as an example.
376 
377 	Your own custom-drawn and native system controls can exist side-by-side.
378 
379 	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.
380 +/
381 class Widget : ReflectableProperties {
382 
383 	/+
384 	/++
385 		Calling this directly after constructor can give you a reflectable object as-needed so you don't pay for what you don't need.
386 
387 		History:
388 			Added September 15, 2021
389 			implemented.... ???
390 	+/
391 	void prepareReflection(this This)() {
392 
393 	}
394 	+/
395 
396 	private bool _enabled = true;
397 
398 	/++
399 		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.
400 
401 		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.
402 
403 		History:
404 			Added November 23, 2021 (dub v10.4)
405 
406 			Warning: the specific behavior of disabling with parents may change in the future.
407 		Bugs:
408 			Currently only implemented for widgets backed by native Windows controls.
409 
410 		See_Also: [disabledReason], [disabledBy]
411 	+/
412 	@property bool enabled() {
413 		return disabledBy() is null;
414 	}
415 
416 	/// ditto
417 	@property void enabled(bool yes) {
418 		_enabled = yes;
419 		version(win32_widgets) {
420 			if(hwnd)
421 				EnableWindow(hwnd, yes);
422 		}
423 		setDynamicState(DynamicState.disabled, yes);
424 	}
425 
426 	private string disabledReason_;
427 
428 	/++
429 		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.
430 
431 		Setting this does NOT disable the widget. You need to call `enabled = false;` separately. It does set the data though.
432 
433 		History:
434 			Added November 23, 2021 (dub v10.4)
435 		See_Also: [enabled], [disabledBy]
436 	+/
437 	@property string disabledReason() {
438 		auto w = disabledBy();
439 		return (w is null) ? null : w.disabledReason_;
440 	}
441 
442 	/// ditto
443 	@property void disabledReason(string reason) {
444 		disabledReason_ = reason;
445 	}
446 
447 	/++
448 		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.
449 
450 		History:
451 			Added November 25, 2021 (dub v10.4)
452 		See_Also: [enabled], [disabledReason]
453 	+/
454 	Widget disabledBy() {
455 		Widget p = this;
456 		while(p) {
457 			if(!p._enabled) 
458 				return p;
459 			p = p.parent;
460 		}
461 		return null;
462 	}
463 
464 	/// Implementations of [ReflectableProperties] interface. See the interface for details.
465 	SetPropertyResult setPropertyFromString(string name, scope const(char)[] value, bool valueIsJson) {
466 		if(valueIsJson)
467 			return SetPropertyResult.wrongFormat;
468 		switch(name) {
469 			case "name":
470 				this.name = value.idup;
471 				return SetPropertyResult.success;
472 			case "statusTip":
473 				this.statusTip = value.idup;
474 				return SetPropertyResult.success;
475 			default:
476 				return SetPropertyResult.noSuchProperty;
477 		}
478 	}
479 	/// ditto
480 	void getPropertiesList(scope void delegate(string name) sink) const {
481 		sink("name");
482 		sink("statusTip");
483 	}
484 	/// ditto
485 	void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink) {
486 		switch(name) {
487 			case "name":
488 				sink(name, this.name, false);
489 				return;
490 			case "statusTip":
491 				sink(name, this.statusTip, false);
492 				return;
493 			default:
494 				sink(name, null, true);
495 		}
496 	}
497 
498 	/++
499 		Scales the given value to the system-reported DPI for the monitor on which the widget resides.
500 
501 		History:
502 			Added November 25, 2021
503 	+/
504 	int scaleWithDpi(int value, int assumedDpi = 96) {
505 		auto divide = (parentWindow && parentWindow.win) ? parentWindow.win.actualDpi : assumedDpi;
506 		// 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.
507 		// this also covers the case when actualDpi returns 0.
508 		if(divide < 96)
509 			divide = 96;
510 		return value * divide / assumedDpi;
511 	}
512 
513 	// avoid this it just forwards to a soon-to-be-deprecated function and is not remotely stable
514 	// I'll think up something better eventually
515 	protected final int defaultLineHeight() {
516 		return scaleWithDpi(Window.lineHeight);
517 	}
518 
519 	/++
520 		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.
521 
522 		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.
523 
524 		History:
525 			Added May 22, 2021
526 	+/
527 	protected bool encapsulatedChildren() {
528 		return false;
529 	}
530 
531 	// Default layout properties {
532 
533 		int minWidth() { return 0; }
534 		int minHeight() {
535 			// default widgets have a vertical layout, therefore the minimum height is the sum of the contents
536 			int sum = 0;
537 			foreach(child; children) {
538 				sum += child.minHeight();
539 				sum += child.marginTop();
540 				sum += child.marginBottom();
541 			}
542 
543 			return sum;
544 		}
545 		int maxWidth() { return int.max; }
546 		int maxHeight() { return int.max; }
547 		int widthStretchiness() { return 4; }
548 		int heightStretchiness() { return 4; }
549 
550 		/++
551 			Where stretchiness will grow from the flex basis, this shrinkiness will let it get smaller if needed to make room for other items.
552 
553 			History:
554 				Added June 15, 2021 (dub v10.1)
555 		+/
556 		int widthShrinkiness() { return 0; }
557 		/// ditto
558 		int heightShrinkiness() { return 0; }
559 
560 		/++
561 			The initial size of the widget for layout calculations. Default is 0.
562 
563 			See_Also: [https://developer.mozilla.org/en-US/docs/Web/CSS/flex-basis|CSS flex-basis]
564 
565 			History:
566 				Added June 15, 2021 (dub v10.1)
567 		+/
568 		int flexBasisWidth() { return 0; }
569 		/// ditto
570 		int flexBasisHeight() { return 0; }
571 
572 		int marginLeft() { return 0; }
573 		int marginRight() { return 0; }
574 		int marginTop() { return 0; }
575 		int marginBottom() { return 0; }
576 		int paddingLeft() { return 0; }
577 		int paddingRight() { return 0; }
578 		int paddingTop() { return 0; }
579 		int paddingBottom() { return 0; }
580 		//LinePreference linePreference() { return LinePreference.PreferOwnLine; }
581 
582 		private bool recomputeChildLayoutRequired = true;
583 		private static class RecomputeEvent {}
584 		private __gshared rce = new RecomputeEvent();
585 		protected final void queueRecomputeChildLayout() {
586 			recomputeChildLayoutRequired = true;
587 
588 			if(this.parentWindow) {
589 				auto sw = this.parentWindow.win;
590 				assert(sw !is null);
591 				if(!sw.eventQueued!RecomputeEvent) {
592 					sw.postEvent(rce);
593 					// import std.stdio; writeln("redraw requested from ", file,":",line," ", this.parentWindow.win.impl.window);
594 				}
595 			}
596 
597 		}
598 
599 		protected final void recomputeChildLayoutEntry() {
600 			if(recomputeChildLayoutRequired) {
601 				recomputeChildLayout();
602 				recomputeChildLayoutRequired = false;
603 				redraw();
604 			} else {
605 				// I still need to check the tree just in case one of them was queued up
606 				// and the event came up here instead of there.
607 				foreach(child; children)
608 					child.recomputeChildLayoutEntry();
609 			}
610 		}
611 
612 		// this function should (almost) never be called directly anymore... call recomputeChildLayoutEntry when executing it and queueRecomputeChildLayout if you just want it done soon
613 		void recomputeChildLayout() {
614 			.recomputeChildLayout!"height"(this);
615 		}
616 
617 	// }
618 
619 
620 	/++
621 		Returns the style's tag name string this object uses.
622 
623 		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.
624 
625 		This tag may never be used, it is just available for the [VisualTheme.getPropertyString] if it chooses to do something like CSS.
626 
627 		History:
628 			Added May 10, 2021
629 	+/
630 	string styleTagName() const {
631 		string n = typeid(this).name;
632 		foreach_reverse(idx, ch; n)
633 			if(ch == '.') {
634 				n = n[idx + 1 .. $];
635 				break;
636 			}
637 		return n;
638 	}
639 
640 	/// API for the [styleClassList]
641 	static struct ClassList {
642 		private Widget widget;
643 
644 		///
645 		void add(string s) {
646 			widget.styleClassList_ ~= s;
647 		}
648 
649 		///
650 		void remove(string s) {
651 			foreach(idx, s1; widget.styleClassList_)
652 				if(s1 == s) {
653 					widget.styleClassList_[idx] = widget.styleClassList_[$-1];
654 					widget.styleClassList_ = widget.styleClassList_[0 .. $-1];
655 					widget.styleClassList_.assumeSafeAppend();
656 					return;
657 				}
658 		}
659 
660 		/// Returns true if it was added, false if it was removed.
661 		bool toggle(string s) {
662 			if(contains(s)) {
663 				remove(s);
664 				return false;
665 			} else {
666 				add(s);
667 				return true;
668 			}
669 		}
670 
671 		///
672 		bool contains(string s) const {
673 			foreach(s1; widget.styleClassList_)
674 				if(s1 == s)
675 					return true;
676 			return false;
677 
678 		}
679 	}
680 
681 	private string[] styleClassList_;
682 
683 	/++
684 		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.
685 
686 		It has no inherent meaning, it is really just a place to put some metadata tags on individual objects.
687 
688 		History:
689 			Added May 10, 2021
690 	+/
691 	inout(ClassList) styleClassList() inout {
692 		return cast(inout(ClassList)) ClassList(cast() this);
693 	}
694 
695 	/++
696 		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.
697 
698 		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.
699 
700 		The upper 32 bits are available for your own extensions.
701 
702 		History:
703 			Added May 10, 2021
704 	+/
705 	enum DynamicState : ulong {
706 		focus = (1 << 0), /// the widget currently has the keyboard focus
707 		hover = (1 << 1), /// the mouse is currently hovering over the widget (may not always be updated)
708 		valid = (1 << 2), /// the widget's content has been validated and it passed (do not set if not validation has been performed!)
709 		invalid = (1 << 3), /// the widget's content has been validated and it failed (do not set if not validation has been performed!)
710 		checked = (1 << 4), /// the widget is toggleable and currently toggled on
711 		selected = (1 << 5), /// the widget represents one option of many and is currently selected, but is not necessarily focused nor checked.
712 		disabled = (1 << 6), /// the widget is currently unable to perform its designated task
713 		indeterminate = (1 << 7), /// the widget has tri-state and is between checked and not checked
714 		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.
715 
716 		USER_BEGIN = (1UL << 32),
717 	}
718 
719 	// I want to add the primary and cancel styles to buttons at least at some point somehow.
720 
721 	/// ditto
722 	@property ulong dynamicState() { return dynamicState_; }
723 	/// ditto
724 	@property ulong dynamicState(ulong newValue) {
725 		if(dynamicState != newValue) {
726 			auto old = dynamicState_;
727 			dynamicState_ = newValue;
728 
729 			useStyleProperties((scope Widget.Style s) {
730 				if(s.variesWithState(old ^ newValue))
731 					redraw();
732 			});
733 		}
734 		return dynamicState_;
735 	}
736 
737 	/// ditto
738 	void setDynamicState(ulong flags, bool state) {
739 		auto ds = dynamicState_;
740 		if(state)
741 			ds |= flags;
742 		else
743 			ds &= ~flags;
744 
745 		dynamicState = ds;
746 	}
747 
748 	private ulong dynamicState_;
749 
750 	deprecated("Use dynamic styles instead now") {
751 		Color backgroundColor() { return backgroundColor_; }
752 		void backgroundColor(Color c){ this.backgroundColor_ = c; }
753 
754 		MouseCursor cursor() { return GenericCursor.Default; }
755 	} private Color backgroundColor_ = Color.transparent;
756 
757 
758 	/++
759 		Style properties are defined as an accessory class so they can be referenced and overridden independently.
760 
761 		It is here so there can be a specificity switch.
762 
763 		See [OverrideStyle] for a helper function to use your own.
764 
765 		History:
766 			Added May 11, 2021
767 	+/
768 	static class Style/* : StyleProperties*/ {
769 		public Widget widget; // public because the mixin template needs access to it
770 
771 		/++
772 			You must override this to trigger automatic redraws if you ever uses the `dynamicState` flag in your style.
773 
774 			History:
775 				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.
776 		+/
777 		bool variesWithState(ulong dynamicStateFlags) {
778 			version(win32_widgets) {
779 				if(widget.hwnd)
780 					return false;
781 			}
782 			return widget.tabStop && ((dynamicStateFlags & DynamicState.focus) ? true : false);
783 		}
784 
785 		///
786 		Color foregroundColor() {
787 			return WidgetPainter.visualTheme.foregroundColor;
788 		}
789 
790 		///
791 		WidgetBackground background() {
792 			// the default is a "transparent" background, which means
793 			// it goes as far up as it can to get the color
794 			if (widget.backgroundColor_ != Color.transparent)
795 				return WidgetBackground(widget.backgroundColor_);
796 			if (widget.parent)
797 				return widget.parent.getComputedStyle.background;
798 			return WidgetBackground(widget.backgroundColor_);
799 		}
800 
801 		private static OperatingSystemFont fontCached_;
802 		private OperatingSystemFont fontCached() {
803 			if(fontCached_ is null)
804 				fontCached_ = font();
805 			return fontCached_;
806 		}
807 
808 		/++
809 			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.
810 		+/
811 		OperatingSystemFont font() {
812 			return null;
813 		}
814 
815 		/++
816 			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.
817 
818 			You can return a member of [GenericCursor] or your own [MouseCursor] instance.
819 
820 			History:
821 				Was previously a method directly on [Widget], moved to [Widget.Style] on May 12, 2021
822 		+/
823 		MouseCursor cursor() {
824 			return GenericCursor.Default;
825 		}
826 
827 		FrameStyle borderStyle() {
828 			return FrameStyle.none;
829 		}
830 
831 		/++
832 		+/
833 		Color borderColor() {
834 			return Color.transparent;
835 		}
836 
837 		FrameStyle outlineStyle() {
838 			if(widget.dynamicState & DynamicState.focus)
839 				return FrameStyle.dotted;
840 			else
841 				return FrameStyle.none;
842 		}
843 
844 		Color outlineColor() {
845 			return foregroundColor;
846 		}
847 	}
848 
849 	/++
850 		This mixin overrides the [useStyleProperties] method to direct it toward your own style class.
851 		The basic usage is simple:
852 
853 		---
854 		static class Style : YourParentClass.Style { /* YourParentClass is frequently Widget, of course, but not always */
855 			// override style hints as-needed here
856 		}
857 		OverrideStyle!Style; // add the method
858 		---
859 
860 		$(TIP
861 			While the class is not forced to be `static`, for best results, it should be. A non-static class
862 			can not be inherited by other objects whereas the static one can. A property on the base class,
863 			called [Widget.Style.widget|widget], is available for you to access its properties.
864 		)
865 
866 		This exists just because [useStyleProperties] has a somewhat convoluted signature and its overrides must
867 		repeat them. Moreover, its implementation uses a stack class to optimize GC pressure from small fetches
868 		and that's a little tedious to repeat in your child classes too when you only care about changing the type.
869 
870 
871 		It also has a further facility to pick a wholly differnet class based on the [DynamicState] of the Widget.
872 		You may also just override `variesWithState` when you use this flag.
873 
874 		---
875 		mixin OverrideStyle!(
876 			DynamicState.focus, YourFocusedStyle,
877 			DynamicState.hover, YourHoverStyle,
878 			YourDefaultStyle
879 		)
880 		---
881 
882 		It checks if `dynamicState` matches the state and if so, returns the object given.
883 
884 		If there is no state mask given, the next one matches everything. The first match given is used.
885 
886 		However, since in most cases you'll want check state inside your individual methods, you probably won't
887 		find much use for this whole-class swap out.
888 
889 		History:
890 			Added May 16, 2021
891 	+/
892 	static protected mixin template OverrideStyle(S...) {
893 		override void useStyleProperties(scope void delegate(scope Widget.Style props) dg) {
894 			ulong mask = 0;
895 			foreach(idx, thing; S) {
896 				static if(is(typeof(thing) : ulong)) {
897 					mask = thing;
898 				} else {
899 					if(!(idx & 1) || (this.dynamicState & mask) == mask) {
900 						//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.");
901 						scope Widget.Style s = new thing();
902 						s.widget = this;
903 						dg(s);
904 						return;
905 					}
906 				}
907 			}
908 		}
909 	}
910 	/++
911 		You can override this by hand, or use the [OverrideStyle] helper which is a bit less verbose.
912 	+/
913 	void useStyleProperties(scope void delegate(scope Style props) dg) {
914 		scope Style s = new Style();
915 		s.widget = this;
916 		dg(s);
917 	}
918 
919 
920 	protected void sendResizeEvent() {
921 		this.emit!ResizeEvent();
922 	}
923 
924 	Menu contextMenu(int x, int y) { return null; }
925 
926 	final bool showContextMenu(int x, int y, int screenX = -2, int screenY = -2) {
927 		if(parentWindow is null || parentWindow.win is null) return false;
928 
929 		auto menu = this.contextMenu(x, y);
930 		if(menu is null)
931 			return false;
932 
933 		version(win32_widgets) {
934 			// FIXME: if it is -1, -1, do it at the current selection location instead
935 			// tho the corner of the window, whcih it does now, isn't the literal worst.
936 
937 			if(screenX < 0 && screenY < 0) {
938 				auto p = this.globalCoordinates();
939 				if(screenX == -2)
940 					p.x += x;
941 				if(screenY == -2)
942 					p.y += y;
943 
944 				screenX = p.x;
945 				screenY = p.y;
946 			}
947 
948 			if(!TrackPopupMenuEx(menu.handle, 0, screenX, screenY, parentWindow.win.impl.hwnd, null))
949 				throw new Exception("TrackContextMenuEx");
950 		} else version(custom_widgets) {
951 			menu.popup(this, x, y);
952 		}
953 
954 		return true;
955 	}
956 
957 	/++
958 		Removes this widget from its parent.
959 
960 		History:
961 			`removeWidget` was made `final` on May 11, 2021.
962 	+/
963 	@scriptable
964 	final void removeWidget() {
965 		auto p = this.parent;
966 		if(p) {
967 			int item;
968 			for(item = 0; item < p._children.length; item++)
969 				if(p._children[item] is this)
970 					break;
971 			auto idx = item;
972 			for(; item < p._children.length - 1; item++)
973 				p._children[item] = p._children[item + 1];
974 			p._children = p._children[0 .. $-1];
975 
976 			this.parent.widgetRemoved(idx, this);
977 			//this.parent = null;
978 		}
979 		version(win32_widgets) {
980 			removeAllChildren();
981 			if(hwnd) {
982 				DestroyWindow(hwnd);
983 				hwnd = null;
984 			}
985 		}
986 	}
987 
988 	/++
989 		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.
990 
991 		History:
992 			Added September 19, 2021
993 	+/
994 	protected void widgetRemoved(size_t oldIndex, Widget oldReference) { }
995 
996 	/++
997 		Removes all child widgets from `this`. You should not use the removed widgets again.
998 
999 		Note that on Windows, it also destroys the native handles for the removed children recursively.
1000 
1001 		History:
1002 			Added July 1, 2021 (dub v10.2)
1003 	+/
1004 	void removeAllChildren() {
1005 		version(win32_widgets)
1006 		foreach(child; _children) {
1007 			child.removeAllChildren();
1008 			if(child.hwnd) {
1009 				DestroyWindow(child.hwnd);
1010 				child.hwnd = null;
1011 			}
1012 		}
1013 		auto orig = this._children;
1014 		this._children = null;
1015 		foreach(idx, w; orig)
1016 			this.widgetRemoved(idx, w);
1017 	}
1018 
1019 	/++
1020 		Calls [getByName] with the generic type of Widget. Meant for script interop where instantiating a template is impossible.
1021 	+/
1022 	@scriptable
1023 	Widget getChildByName(string name) {
1024 		return getByName(name);
1025 	}
1026 	/++
1027 		Finds the nearest descendant with the requested type and [name]. May return `this`.
1028 	+/
1029 	final WidgetClass getByName(WidgetClass = Widget)(string name) {
1030 		if(this.name == name)
1031 			if(auto c = cast(WidgetClass) this)
1032 				return c;
1033 		foreach(child; children) {
1034 			auto w = child.getByName(name);
1035 			if(auto c = cast(WidgetClass) w)
1036 				return c;
1037 		}
1038 		return null;
1039 	}
1040 
1041 	/++
1042 		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.
1043 		Names should be unique in a window.
1044 
1045 		See_Also: [getByName], [getChildByName]
1046 	+/
1047 	@scriptable string name;
1048 
1049 	private EventHandler[][string] bubblingEventHandlers;
1050 	private EventHandler[][string] capturingEventHandlers;
1051 
1052 	/++
1053 		Default event handlers. These are called on the appropriate
1054 		event unless [Event.preventDefault] is called on the event at
1055 		some point through the bubbling process.
1056 
1057 
1058 		If you are implementing your own widget and want to add custom
1059 		events, you should follow the same pattern here: create a virtual
1060 		function named `defaultEventHandler_eventname` with the implementation,
1061 		then, override [setupDefaultEventHandlers] and add a wrapped caller to
1062 		`defaultEventHandlers["eventname"]`. It should be wrapped like so:
1063 		`defaultEventHandlers["eventname"] = (Widget t, Event event) { t.defaultEventHandler_name(event); };`.
1064 		This ensures virtual dispatch based on the correct subclass.
1065 
1066 		Also, don't forget to call `super.setupDefaultEventHandlers();` too in your
1067 		overridden version.
1068 
1069 		You only need to do that on parent classes adding NEW event types. If you
1070 		just want to change the default behavior of an existing event type in a subclass,
1071 		you override the function (and optionally call `super.method_name`) like normal.
1072 
1073 	+/
1074 	protected EventHandler[string] defaultEventHandlers;
1075 
1076 	/// ditto
1077 	void setupDefaultEventHandlers() {
1078 		defaultEventHandlers["click"] = (Widget t, Event event) { t.defaultEventHandler_click(cast(ClickEvent) event); };
1079 		defaultEventHandlers["dblclick"] = (Widget t, Event event) { t.defaultEventHandler_dblclick(cast(DoubleClickEvent) event); };
1080 		defaultEventHandlers["keydown"] = (Widget t, Event event) { t.defaultEventHandler_keydown(cast(KeyDownEvent) event); };
1081 		defaultEventHandlers["keyup"] = (Widget t, Event event) { t.defaultEventHandler_keyup(cast(KeyUpEvent) event); };
1082 		defaultEventHandlers["mouseover"] = (Widget t, Event event) { t.defaultEventHandler_mouseover(cast(MouseOverEvent) event); };
1083 		defaultEventHandlers["mouseout"] = (Widget t, Event event) { t.defaultEventHandler_mouseout(cast(MouseOutEvent) event); };
1084 		defaultEventHandlers["mousedown"] = (Widget t, Event event) { t.defaultEventHandler_mousedown(cast(MouseDownEvent) event); };
1085 		defaultEventHandlers["mouseup"] = (Widget t, Event event) { t.defaultEventHandler_mouseup(cast(MouseUpEvent) event); };
1086 		defaultEventHandlers["mouseenter"] = (Widget t, Event event) { t.defaultEventHandler_mouseenter(cast(MouseEnterEvent) event); };
1087 		defaultEventHandlers["mouseleave"] = (Widget t, Event event) { t.defaultEventHandler_mouseleave(cast(MouseLeaveEvent) event); };
1088 		defaultEventHandlers["mousemove"] = (Widget t, Event event) { t.defaultEventHandler_mousemove(cast(MouseMoveEvent) event); };
1089 		defaultEventHandlers["char"] = (Widget t, Event event) { t.defaultEventHandler_char(cast(CharEvent) event); };
1090 		defaultEventHandlers["triggered"] = (Widget t, Event event) { t.defaultEventHandler_triggered(event); };
1091 		defaultEventHandlers["change"] = (Widget t, Event event) { t.defaultEventHandler_change(event); };
1092 		defaultEventHandlers["focus"] = (Widget t, Event event) { t.defaultEventHandler_focus(event); };
1093 		defaultEventHandlers["blur"] = (Widget t, Event event) { t.defaultEventHandler_blur(event); };
1094 		defaultEventHandlers["focusin"] = (Widget t, Event event) { t.defaultEventHandler_focusin(event); };
1095 		defaultEventHandlers["focusout"] = (Widget t, Event event) { t.defaultEventHandler_focusout(event); };
1096 	}
1097 
1098 	/// ditto
1099 	void defaultEventHandler_click(ClickEvent event) {}
1100 	/// ditto
1101 	void defaultEventHandler_dblclick(DoubleClickEvent event) {}
1102 	/// ditto
1103 	void defaultEventHandler_keydown(KeyDownEvent event) {}
1104 	/// ditto
1105 	void defaultEventHandler_keyup(KeyUpEvent event) {}
1106 	/// ditto
1107 	void defaultEventHandler_mousedown(MouseDownEvent event) {
1108 		if(event.button == MouseButton.left) {
1109 			if(this.tabStop)
1110 				this.focus();
1111 		}
1112 	}
1113 	/// ditto
1114 	void defaultEventHandler_mouseover(MouseOverEvent event) {}
1115 	/// ditto
1116 	void defaultEventHandler_mouseout(MouseOutEvent event) {}
1117 	/// ditto
1118 	void defaultEventHandler_mouseup(MouseUpEvent event) {}
1119 	/// ditto
1120 	void defaultEventHandler_mousemove(MouseMoveEvent event) {}
1121 	/// ditto
1122 	void defaultEventHandler_mouseenter(MouseEnterEvent event) {}
1123 	/// ditto
1124 	void defaultEventHandler_mouseleave(MouseLeaveEvent event) {}
1125 	/// ditto
1126 	void defaultEventHandler_char(CharEvent event) {}
1127 	/// ditto
1128 	void defaultEventHandler_triggered(Event event) {}
1129 	/// ditto
1130 	void defaultEventHandler_change(Event event) {}
1131 	/// ditto
1132 	void defaultEventHandler_focus(Event event) {}
1133 	/// ditto
1134 	void defaultEventHandler_blur(Event event) {}
1135 	/// ditto
1136 	void defaultEventHandler_focusin(Event event) {}
1137 	/// ditto
1138 	void defaultEventHandler_focusout(Event event) {}
1139 
1140 	/++
1141 		[Event]s use a Javascript-esque model. See more details on the [Event] page.
1142 
1143 		[addEventListener] returns an opaque handle that you can later pass to [removeEventListener].
1144 
1145 		addDirectEventListener just inserts a check `if(e.target !is this) return;` meaning it opts out
1146 		of participating in handler delegation.
1147 
1148 		$(TIP
1149 			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.
1150 		)
1151 	+/
1152 	EventListener addDirectEventListener(string event, void delegate() handler, bool useCapture = false) {
1153 		return addEventListener(event, (Widget, scope Event e) {
1154 			if(e.srcElement is this)
1155 				handler();
1156 		}, useCapture);
1157 	}
1158 
1159 	/// ditto
1160 	EventListener addDirectEventListener(string event, void delegate(Event) handler, bool useCapture = false) {
1161 		return addEventListener(event, (Widget, Event e) {
1162 			if(e.srcElement is this)
1163 				handler(e);
1164 		}, useCapture);
1165 	}
1166 
1167 	/// ditto
1168 	@scriptable
1169 	EventListener addEventListener(string event, void delegate() handler, bool useCapture = false) {
1170 		return addEventListener(event, (Widget, scope Event) { handler(); }, useCapture);
1171 	}
1172 
1173 	/// ditto
1174 	EventListener addEventListener(Handler)(Handler handler, bool useCapture = false) {
1175 		static if(is(Handler Fn == delegate)) {
1176 		static if(is(Fn Params == __parameters)) {
1177 			return addEventListener(EventString!(Params[0]), (Widget, Event e) {
1178 				auto ty = cast(Params[0]) e;
1179 				if(ty !is null)
1180 					handler(ty);
1181 			}, useCapture);
1182 		} else static assert(0);
1183 		} else static assert(0, "Your handler wasn't usable because it wasn't passed a delegate. Use the delegate keyword at the call site.");
1184 	}
1185 
1186 	/// ditto
1187 	EventListener addEventListener(string event, void delegate(Event) handler, bool useCapture = false) {
1188 		return addEventListener(event, (Widget, Event e) { handler(e); }, useCapture);
1189 	}
1190 
1191 	/// ditto
1192 	EventListener addEventListener(string event, EventHandler handler, bool useCapture = false) {
1193 		if(event.length > 2 && event[0..2] == "on")
1194 			event = event[2 .. $];
1195 
1196 		if(useCapture)
1197 			capturingEventHandlers[event] ~= handler;
1198 		else
1199 			bubblingEventHandlers[event] ~= handler;
1200 
1201 		return EventListener(this, event, handler, useCapture);
1202 	}
1203 
1204 	/// ditto
1205 	void removeEventListener(string event, EventHandler handler, bool useCapture = false) {
1206 		if(event.length > 2 && event[0..2] == "on")
1207 			event = event[2 .. $];
1208 
1209 		if(useCapture) {
1210 			if(event in capturingEventHandlers)
1211 			foreach(ref evt; capturingEventHandlers[event])
1212 				if(evt is handler) evt = null;
1213 		} else {
1214 			if(event in bubblingEventHandlers)
1215 			foreach(ref evt; bubblingEventHandlers[event])
1216 				if(evt is handler) evt = null;
1217 		}
1218 	}
1219 
1220 	/// ditto
1221 	void removeEventListener(EventListener listener) {
1222 		removeEventListener(listener.event, listener.handler, listener.useCapture);
1223 	}
1224 
1225 	static if(UsingSimpledisplayX11) {
1226 		void discardXConnectionState() {
1227 			foreach(child; children)
1228 				child.discardXConnectionState();
1229 		}
1230 
1231 		void recreateXConnectionState() {
1232 			foreach(child; children)
1233 				child.recreateXConnectionState();
1234 			redraw();
1235 		}
1236 	}
1237 
1238 	/++
1239 		Returns the coordinates of this widget on the screen, relative to the upper left corner of the whole screen.
1240 
1241 		History:
1242 			`globalCoordinates` was made `final` on May 11, 2021.
1243 	+/
1244 	Point globalCoordinates() {
1245 		int x = this.x;
1246 		int y = this.y;
1247 		auto p = this.parent;
1248 		while(p) {
1249 			x += p.x;
1250 			y += p.y;
1251 			p = p.parent;
1252 		}
1253 
1254 		static if(UsingSimpledisplayX11) {
1255 			auto dpy = XDisplayConnection.get;
1256 			arsd.simpledisplay.Window dummyw;
1257 			XTranslateCoordinates(dpy, this.parentWindow.win.impl.window, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
1258 		} else {
1259 			POINT pt;
1260 			pt.x = x;
1261 			pt.y = y;
1262 			MapWindowPoints(this.parentWindow.win.impl.hwnd, null, &pt, 1);
1263 			x = pt.x;
1264 			y = pt.y;
1265 		}
1266 
1267 		return Point(x, y);
1268 	}
1269 
1270 	version(win32_widgets)
1271 	int handleWmDrawItem(DRAWITEMSTRUCT* dis) { return 0; }
1272 
1273 	version(win32_widgets)
1274 	/// Called when a WM_COMMAND is sent to the associated hwnd.
1275 	void handleWmCommand(ushort cmd, ushort id) {}
1276 
1277 	version(win32_widgets)
1278 	/++
1279 		Called when a WM_NOTIFY is sent to the associated hwnd.
1280 
1281 		History:
1282 	+/
1283 	int handleWmNotify(NMHDR* hdr, int code, out int mustReturn) { return 0; }
1284 
1285 	version(win32_widgets)
1286 	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); }
1287 
1288 	/++
1289 		This tip is displayed in the status bar (if there is one in the containing window) when the mouse moves over this widget.
1290 
1291 		Updates to this variable will only be made visible on the next mouse enter event.
1292 	+/
1293 	@scriptable string statusTip;
1294 	// string toolTip;
1295 	// string helpText;
1296 
1297 	/++
1298 		If true, this widget can be focused via keyboard control with the tab key.
1299 
1300 		If false, it is assumed the widget itself does will never receive the keyboard focus (though its childen are free to).
1301 	+/
1302 	bool tabStop = true;
1303 	/++
1304 		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.)
1305 	+/
1306 	int tabOrder;
1307 
1308 	version(win32_widgets) {
1309 		static Widget[HWND] nativeMapping;
1310 		/// The native handle, if there is one.
1311 		HWND hwnd;
1312 		WNDPROC originalWindowProcedure;
1313 
1314 		SimpleWindow simpleWindowWrappingHwnd;
1315 
1316 		// please note it IGNORES your return value and does NOT forward it to Windows!
1317 		int hookedWndProc(UINT iMessage, WPARAM wParam, LPARAM lParam) {
1318 			return 0;
1319 		}
1320 	}
1321 	private bool implicitlyCreated;
1322 
1323 	/// 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.
1324 	int x;
1325 	/// ditto
1326 	int y;
1327 	private int _width;
1328 	private int _height;
1329 	private Widget[] _children;
1330 	private Widget _parent;
1331 	private Window _parentWindow;
1332 
1333 	/++
1334 		Returns the window to which this widget is attached.
1335 
1336 		History:
1337 			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.
1338 	+/
1339 	final @property inout(Window) parentWindow() inout @nogc nothrow pure { return _parentWindow; }
1340 	private @property void parentWindow(Window parent) {
1341 		_parentWindow = parent;
1342 		foreach(child; children)
1343 			child.parentWindow = parent; // please note that this is recursive
1344 	}
1345 
1346 	/++
1347 		Returns the list of the widget's children.
1348 
1349 		History:
1350 			Prior to May 11, 2021, the `Widget[] children` was directly available. Now, only this property getter is available and the actual store is private.
1351 
1352 			Children should be added by the constructor most the time, but if that's impossible, use [addChild] and [removeWidget] to manage the list.
1353 	+/
1354 	final @property inout(Widget)[] children() inout @nogc nothrow pure { return _children; }
1355 
1356 	/++
1357 		Returns the widget's parent.
1358 
1359 		History:
1360 			Prior to May 11, 2021, the `Widget parent` variable was directly available. Now, only this property getter is permitted.
1361 
1362 			The parent should only be managed by the [addChild] and [removeWidget] method.
1363 	+/
1364 	final @property inout(Widget) parent() inout nothrow @nogc pure @safe return { return _parent; }
1365 
1366 	/// The widget's current size.
1367 	final @scriptable public @property int width() const nothrow @nogc pure @safe { return _width; }
1368 	/// ditto
1369 	final @scriptable public @property int height() const nothrow @nogc pure @safe { return _height; }
1370 
1371 	/// Only the layout manager should be calling these.
1372 	final protected @property int width(int a) @safe { return _width = a; }
1373 	/// ditto
1374 	final protected @property int height(int a) @safe { return _height = a; }
1375 
1376 	/++
1377 		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.
1378 
1379 		It is also responsible for calling [sendResizeEvent] to notify other listeners that the widget has changed size.
1380 	+/
1381 	protected void registerMovement() {
1382 		version(win32_widgets) {
1383 			if(hwnd) {
1384 				auto pos = getChildPositionRelativeToParentHwnd(this);
1385 				MoveWindow(hwnd, pos[0], pos[1], width, height, true);
1386 			}
1387 		}
1388 		sendResizeEvent();
1389 	}
1390 
1391 	/// Creates the widget and adds it to the parent.
1392 	this(Widget parent) {
1393 		if(parent !is null)
1394 			parent.addChild(this);
1395 		setupDefaultEventHandlers();
1396 	}
1397 
1398 	/// 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.
1399 	@scriptable
1400 	bool isFocused() {
1401 		return parentWindow && parentWindow.focusedWidget is this;
1402 	}
1403 
1404 	private bool showing_ = true;
1405 	///
1406 	bool showing() { return showing_; }
1407 	///
1408 	bool hidden() { return !showing_; }
1409 	/++
1410 		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.
1411 	+/
1412 	void showing(bool s, bool recalculate = true) {
1413 		auto so = showing_;
1414 		showing_ = s;
1415 		if(s != so) {
1416 			version(win32_widgets)
1417 			if(hwnd)
1418 				ShowWindow(hwnd, s ? SW_SHOW : SW_HIDE);
1419 
1420 			if(parent && recalculate) {
1421 				parent.queueRecomputeChildLayout();
1422 				parent.redraw();
1423 			}
1424 
1425 			foreach(child; children)
1426 				child.showing(s, false);
1427 
1428 		}
1429 		queueRecomputeChildLayout();
1430 		redraw();
1431 	}
1432 	/// Convenience method for `showing = true`
1433 	@scriptable
1434 	void show() {
1435 		showing = true;
1436 	}
1437 	/// Convenience method for `showing = false`
1438 	@scriptable
1439 	void hide() {
1440 		showing = false;
1441 	}
1442 
1443 	///
1444 	@scriptable
1445 	void focus() {
1446 		assert(parentWindow !is null);
1447 		if(isFocused())
1448 			return;
1449 
1450 		if(parentWindow.focusedWidget) {
1451 			// FIXME: more details here? like from and to
1452 			auto from = parentWindow.focusedWidget;
1453 			parentWindow.focusedWidget.setDynamicState(DynamicState.focus, false);
1454 			parentWindow.focusedWidget = null;
1455 			from.emit!BlurEvent();
1456 			this.emit!FocusOutEvent();
1457 		}
1458 
1459 
1460 		version(win32_widgets) {
1461 			if(this.hwnd !is null)
1462 				SetFocus(this.hwnd);
1463 		}
1464 		//else static if(UsingSimpledisplayX11)
1465 			//this.parentWindow.win.focus();
1466 
1467 		parentWindow.focusedWidget = this;
1468 		parentWindow.focusedWidget.setDynamicState(DynamicState.focus, true);
1469 		this.emit!FocusEvent();
1470 		this.emit!FocusInEvent();
1471 	}
1472 
1473 	/+
1474 	/++
1475 		Unfocuses the widget. This may reset
1476 	+/
1477 	@scriptable
1478 	void blur() {
1479 
1480 	}
1481 	+/
1482 
1483 
1484 	/++
1485 		This is called when the widget is added to a window. It gives you a chance to set up event hooks.
1486 
1487 		Update on May 11, 2021: I'm considering removing this method. You can usually achieve these things through looser-coupled methods.
1488 	+/
1489 	void attachedToWindow(Window w) {}
1490 	/++
1491 		Callback when the widget is added to another widget.
1492 
1493 		Update on May 11, 2021: I'm considering removing this method since I've never actually found it useful.
1494 	+/
1495 	void addedTo(Widget w) {}
1496 
1497 	/++
1498 		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.
1499 
1500 		This is available primarily to be overridden. For example, [MainWindow] overrides it to redirect its children into a central widget.
1501 	+/
1502 	protected void addChild(Widget w, int position = int.max) {
1503 		w._parent = this;
1504 		if(position == int.max || position == children.length) {
1505 			_children ~= w;
1506 		} else {
1507 			assert(position < _children.length);
1508 			_children.length = _children.length + 1;
1509 			for(int i = cast(int) _children.length - 1; i > position; i--)
1510 				_children[i] = _children[i - 1];
1511 			_children[position] = w;
1512 		}
1513 
1514 		this.parentWindow = this._parentWindow;
1515 
1516 		w.addedTo(this);
1517 
1518 		if(this.hidden)
1519 			w.showing = false;
1520 
1521 		if(parentWindow !is null) {
1522 			w.attachedToWindow(parentWindow);
1523 			parentWindow.queueRecomputeChildLayout();
1524 			parentWindow.redraw();
1525 		}
1526 	}
1527 
1528 	/++
1529 		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.
1530 	+/
1531 	Widget getChildAtPosition(int x, int y) {
1532 		// it goes backward so the last one to show gets picked first
1533 		// might use z-index later
1534 		foreach_reverse(child; children) {
1535 			if(child.hidden)
1536 				continue;
1537 			if(child.x <= x && child.y <= y
1538 				&& ((x - child.x) < child.width)
1539 				&& ((y - child.y) < child.height))
1540 			{
1541 				return child;
1542 			}
1543 		}
1544 
1545 		return null;
1546 	}
1547 
1548 	/++
1549 		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.
1550 
1551 		History:
1552 			Added July 2, 2021 (v10.2)
1553 	+/
1554 	protected void addScrollPosition(ref int x, ref int y) {};
1555 
1556 	/++
1557 		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.
1558 
1559 		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.
1560 
1561 		[paint] is not called for system widgets as the OS library draws them instead.
1562 
1563 
1564 		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.
1565 
1566 		You should also look at [WidgetPainter.visualTheme] to be theme aware.
1567 
1568 		History:
1569 			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.
1570 	+/
1571 	void paint(WidgetPainter painter) {
1572 		version(win32_widgets)
1573 			if(hwnd)
1574 				return;
1575 		painter.drawThemed(&paintContent); // note this refers to the following overload
1576 	}
1577 
1578 	/++
1579 		Responsible for drawing the content as the theme engine is responsible for other elements.
1580 
1581 		$(WARNING If you override [paint], this method may never be used as it is only called from inside the default implementation of `paint`.)
1582 
1583 		Params:
1584 			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.
1585 
1586 			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.
1587 
1588 			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.
1589 
1590 		Returns:
1591 			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.
1592 
1593 		History:
1594 			Added May 15, 2021
1595 	+/
1596 	Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
1597 		return bounds;
1598 	}
1599 
1600 	deprecated("Change ScreenPainter to WidgetPainter")
1601 	final void paint(ScreenPainter) { assert(0, "Change ScreenPainter to WidgetPainter and recompile your code"); }
1602 
1603 	/// I don't actually like the name of this
1604 	/// this draws a background on it
1605 	void erase(WidgetPainter painter) {
1606 		version(win32_widgets)
1607 			if(hwnd) return; // Windows will do it. I think.
1608 
1609 		auto c = getComputedStyle().background.color;
1610 		painter.fillColor = c;
1611 		painter.outlineColor = c;
1612 
1613 		version(win32_widgets) {
1614 			HANDLE b, p;
1615 			if(c.a == 0) {
1616 				b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
1617 				p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
1618 			}
1619 		}
1620 		painter.drawRectangle(Point(0, 0), width, height);
1621 		version(win32_widgets) {
1622 			if(c.a == 0) {
1623 				SelectObject(painter.impl.hdc, p);
1624 				SelectObject(painter.impl.hdc, b);
1625 			}
1626 		}
1627 	}
1628 
1629 	///
1630 	WidgetPainter draw() {
1631 		int x = this.x, y = this.y;
1632 		auto parent = this.parent;
1633 		while(parent) {
1634 			x += parent.x;
1635 			y += parent.y;
1636 			parent = parent.parent;
1637 		}
1638 
1639 		auto painter = parentWindow.win.draw();
1640 		painter.originX = x;
1641 		painter.originY = y;
1642 		painter.setClipRectangle(Point(0, 0), width, height);
1643 		return WidgetPainter(painter, this);
1644 	}
1645 
1646 	/// 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.
1647 	protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force = false) {
1648 		if(hidden)
1649 			return;
1650 
1651 		int paintX = x;
1652 		int paintY = y;
1653 		if(this.useNativeDrawing()) {
1654 			paintX = 0;
1655 			paintY = 0;
1656 			lox = 0;
1657 			loy = 0;
1658 			containment = Rectangle(0, 0, int.max, int.max);
1659 		}
1660 
1661 		painter.originX = lox + paintX;
1662 		painter.originY = loy + paintY;
1663 
1664 		bool actuallyPainted = false;
1665 
1666 		const clip = containment.intersectionOf(Rectangle(Point(lox + paintX, loy + paintY), Size(width, height)));
1667 		if(clip == Rectangle.init) {
1668 			//import std.stdio; writeln(this, " clipped out");
1669 			return;
1670 		}
1671 
1672 		if(redrawRequested || force) {
1673 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
1674 
1675 			painter.drawingUpon = this;
1676 
1677 			erase(painter);
1678 			if(painter.visualTheme)
1679 				painter.visualTheme.doPaint(this, painter);
1680 			else
1681 				paint(painter);
1682 
1683 			redrawRequested = false;
1684 			actuallyPainted = true;
1685 		}
1686 
1687 		foreach(child; children) {
1688 			version(win32_widgets)
1689 				if(child.useNativeDrawing()) continue;
1690 			child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted);
1691 		}
1692 
1693 		version(win32_widgets)
1694 		foreach(child; children) {
1695 			if(child.useNativeDrawing) {
1696 				painter = WidgetPainter(child.simpleWindowWrappingHwnd.draw, child);
1697 				child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted);
1698 			}
1699 		}
1700 	}
1701 
1702 	protected bool useNativeDrawing() nothrow {
1703 		version(win32_widgets)
1704 			return hwnd !is null;
1705 		else
1706 			return false;
1707 	}
1708 
1709 	private static class RedrawEvent {}
1710 	private __gshared re = new RedrawEvent();
1711 
1712 	private bool redrawRequested;
1713 	///
1714 	final void redraw(string file = __FILE__, size_t line = __LINE__) {
1715 		redrawRequested = true;
1716 
1717 		if(this.parentWindow) {
1718 			auto sw = this.parentWindow.win;
1719 			assert(sw !is null);
1720 			if(!sw.eventQueued!RedrawEvent) {
1721 				sw.postEvent(re);
1722 				// import std.stdio; writeln("redraw requested from ", file,":",line," ", this.parentWindow.win.impl.window);
1723 			}
1724 		}
1725 	}
1726 
1727 	private SimpleWindow drawableWindow;
1728 
1729 	/++
1730 		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.
1731 
1732 		Returns:
1733 			`true` if you should do your default behavior.
1734 
1735 		History:
1736 			Added May 5, 2021
1737 
1738 		Bugs:
1739 			It does not do the static checks on gdc right now.
1740 	+/
1741 	final protected bool emit(EventType, this This, Args...)(Args args) {
1742 		version(GNU) {} else
1743 		static assert(classStaticallyEmits!(This, EventType), "The " ~ This.stringof ~ " class is not declared to emit " ~ EventType.stringof);
1744 		auto e = new EventType(this, args);
1745 		e.dispatch();
1746 		return !e.defaultPrevented;
1747 	}
1748 	/// ditto
1749 	final protected bool emit(string eventString, this This)() {
1750 		auto e = new Event(eventString, this);
1751 		e.dispatch();
1752 		return !e.defaultPrevented;
1753 	}
1754 
1755 	/++
1756 		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.
1757 
1758 		History:
1759 			Added May 5, 2021
1760 	+/
1761 	final public EventListener subscribe(EventType, this This)(void delegate(EventType) handler) {
1762 		static assert(classStaticallyEmits!(This, EventType), "The " ~ This.stringof ~ " class is not declared to emit " ~ EventType.stringof);
1763 		return addEventListener(handler);
1764 	}
1765 
1766 	/++
1767 		Gets the computed style properties from the visual theme.
1768 
1769 		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].)
1770 
1771 		History:
1772 			Added May 8, 2021
1773 	+/
1774 	final StyleInformation getComputedStyle() {
1775 		return StyleInformation(this);
1776 	}
1777 
1778 	// FIXME: I kinda want to hide events from implementation widgets
1779 	// so it just catches them all and stops propagation...
1780 	// i guess i can do it with a event listener on star.
1781 
1782 	mixin Emits!KeyDownEvent; ///
1783 	mixin Emits!KeyUpEvent; ///
1784 	mixin Emits!CharEvent; ///
1785 
1786 	mixin Emits!MouseDownEvent; ///
1787 	mixin Emits!MouseUpEvent; ///
1788 	mixin Emits!ClickEvent; ///
1789 	mixin Emits!DoubleClickEvent; ///
1790 	mixin Emits!MouseMoveEvent; ///
1791 	mixin Emits!MouseOverEvent; ///
1792 	mixin Emits!MouseOutEvent; ///
1793 	mixin Emits!MouseEnterEvent; ///
1794 	mixin Emits!MouseLeaveEvent; ///
1795 
1796 	mixin Emits!ResizeEvent; ///
1797 
1798 	mixin Emits!BlurEvent; ///
1799 	mixin Emits!FocusEvent; ///
1800 
1801 	mixin Emits!FocusInEvent; ///
1802 	mixin Emits!FocusOutEvent; ///
1803 }
1804 
1805 /+
1806 /++
1807 	Interface to indicate that the widget has a simple value property.
1808 
1809 	History:
1810 		Added August 26, 2021
1811 +/
1812 interface HasValue!T {
1813 	/// Getter
1814 	@property T value();
1815 	/// Setter
1816 	@property void value(T);
1817 }
1818 
1819 /++
1820 	Interface to indicate that the widget has a range of possible values for its simple value property.
1821 	This would be present on something like a slider or possibly a number picker.
1822 
1823 	History:
1824 		Added September 11, 2021
1825 +/
1826 interface HasRangeOfValues!T : HasValue!T {
1827 	/// The minimum and maximum values in the range, inclusive.
1828 	@property T minValue();
1829 	@property void minValue(T); /// ditto
1830 	@property T maxValue(); /// ditto
1831 	@property void maxValue(T); /// ditto
1832 
1833 	/// The smallest step the user interface allows. User may still type in values without this limitation.
1834 	@property void step(T);
1835 	@property T step(); /// ditto
1836 }
1837 
1838 /++
1839 	Interface to indicate that the widget has a list of possible values the user can choose from.
1840 	This would be present on something like a drop-down selector.
1841 
1842 	The value is NOT necessarily one of the items on the list. Consider the case of a free-entry
1843 	combobox.
1844 
1845 	History:
1846 		Added September 11, 2021
1847 +/
1848 interface HasListOfValues!T : HasValue!T {
1849 	@property T[] values;
1850 	@property void values(T[]);
1851 
1852 	@property int selectedIndex(); // note it may return -1!
1853 	@property void selectedIndex(int);
1854 }
1855 +/
1856 
1857 /++
1858 	History:
1859 		Added September 2021 (dub v10.4)
1860 +/
1861 class GridLayout : Layout {
1862 
1863 	// 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.
1864 
1865 	/++
1866 		If a widget is too small to fill a grid cell, the graviy tells where it "sticks" to.
1867 	+/
1868 	enum Gravity {
1869 		Center    = 0,
1870 		NorthWest = North | West,
1871 		North     = 0b10_00,
1872 		NorthEast = North | East,
1873 		West      = 0b00_10,
1874 		East      = 0b00_01,
1875 		SouthWest = South | West,
1876 		South     = 0b01_00,
1877 		SouthEast = South | East,
1878 	}
1879 
1880 	/++
1881 		The width and height are in some proportional units and can often just be 12.
1882 	+/
1883 	this(int width, int height, Widget parent) {
1884 		this.gridWidth = width;
1885 		this.gridHeight = height;
1886 		super(parent);
1887 	}
1888 
1889 	/++
1890 		Sets the position of the given child.
1891 
1892 		The units of these arguments are in the proportional grid units you set in the constructor.
1893 	+/
1894 	Widget setChildPosition(return Widget child, int x, int y, int width, int height, Gravity gravity = Gravity.Center) {
1895 		// ensure it is in bounds
1896 		// then ensure no overlaps
1897 
1898 		ChildPosition p = ChildPosition(child, x, y, width, height, gravity);
1899 
1900 		foreach(ref position; positions) {
1901 			if(position.widget is child) {
1902 				position = p;
1903 				goto set;
1904 			}
1905 		}
1906 
1907 		positions ~= p;
1908 
1909 		set:
1910 
1911 		// FIXME: should this batch?
1912 		queueRecomputeChildLayout();
1913 
1914 		return child;
1915 	}
1916 
1917 	override void addChild(Widget w, int position = int.max) {
1918 		super.addChild(w, position);
1919 		//positions ~= ChildPosition(w);
1920 		if(position != int.max) {
1921 			// FIXME: align it so they actually match.
1922 		}
1923 	}
1924 
1925 	override void widgetRemoved(size_t idx, Widget w) {
1926 		// FIXME: keep the positions array aligned
1927 		// positions[idx].widget = null;
1928 	}
1929 
1930 	override void recomputeChildLayout() {
1931 		registerMovement();
1932 		int onGrid = cast(int) positions.length;
1933 		c: foreach(child; children) {
1934 			// just snap it to the grid
1935 			if(onGrid)
1936 			foreach(position; positions)
1937 				if(position.widget is child) {
1938 					child.x = this.width * position.x / this.gridWidth;
1939 					child.y = this.height * position.y / this.gridHeight;
1940 					child.width = this.width * position.width / this.gridWidth;
1941 					child.height = this.height * position.height / this.gridHeight;
1942 
1943 					auto diff = child.width - child.maxWidth();
1944 					// FIXME: gravity?
1945 					if(diff > 0) {
1946 						child.width = child.width - diff;
1947 
1948 						if(position.gravity & Gravity.West) {
1949 							// nothing needed, already aligned
1950 						} else if(position.gravity & Gravity.East) {
1951 							child.x += diff;
1952 						} else {
1953 							child.x += diff / 2;
1954 						}
1955 					}
1956 
1957 					diff = child.height - child.maxHeight();
1958 					// FIXME: gravity?
1959 					if(diff > 0) {
1960 						child.height = child.height - diff;
1961 
1962 						if(position.gravity & Gravity.North) {
1963 							// nothing needed, already aligned
1964 						} else if(position.gravity & Gravity.South) {
1965 							child.y += diff;
1966 						} else {
1967 							child.y += diff / 2;
1968 						}
1969 					}
1970 
1971 
1972 					child.recomputeChildLayout();
1973 					onGrid--;
1974 					continue c;
1975 				}
1976 			// the position isn't given on the grid array, we'll just fill in from where the explicit ones left off.
1977 		}
1978 	}
1979 
1980 	private struct ChildPosition {
1981 		Widget widget;
1982 		int x;
1983 		int y;
1984 		int width;
1985 		int height;
1986 		Gravity gravity;
1987 	}
1988 	private ChildPosition[] positions;
1989 
1990 	int gridWidth = 12;
1991 	int gridHeight = 12;
1992 }
1993 
1994 ///
1995 abstract class ComboboxBase : Widget {
1996 	// if the user can enter arbitrary data, we want to use  2 == CBS_DROPDOWN
1997 	// or to always show the list, we want CBS_SIMPLE == 1
1998 	version(win32_widgets)
1999 		this(uint style, Widget parent) {
2000 			super(parent);
2001 			createWin32Window(this, "ComboBox"w, null, style);
2002 		}
2003 	else version(custom_widgets)
2004 		this(Widget parent) {
2005 			super(parent);
2006 
2007 			addEventListener((KeyDownEvent event) {
2008 				if(event.key == Key.Up) {
2009 					if(selection_ > -1) { // -1 means select blank
2010 						selection_--;
2011 						fireChangeEvent();
2012 					}
2013 					event.preventDefault();
2014 				}
2015 				if(event.key == Key.Down) {
2016 					if(selection_ + 1 < options.length) {
2017 						selection_++;
2018 						fireChangeEvent();
2019 					}
2020 					event.preventDefault();
2021 				}
2022 
2023 			});
2024 
2025 		}
2026 	else static assert(false);
2027 
2028 	private string[] options;
2029 	private int selection_ = -1;
2030 
2031 	void addOption(string s) {
2032 		options ~= s;
2033 		version(win32_widgets)
2034 		SendMessageW(hwnd, 323 /*CB_ADDSTRING*/, 0, cast(LPARAM) toWstringzInternal(s));
2035 	}
2036 
2037 	int getSelection() {
2038 		return selection_;
2039 	}
2040 
2041 	/++
2042 		History:
2043 			Added November 17, 2021
2044 	+/
2045 	string getSelectionString() {
2046 		return selection_ == -1 ? null : options[selection_];
2047 	}
2048 
2049 	void setSelection(int idx) {
2050 		selection_ = idx;
2051 		version(win32_widgets)
2052 		SendMessageW(hwnd, 334 /*CB_SETCURSEL*/, idx, 0);
2053 
2054 		auto t = new SelectionChangedEvent(this, selection_, selection_ == -1 ? null : options[selection_]);
2055 		t.dispatch();
2056 	}
2057 
2058 	static class SelectionChangedEvent : Event {
2059 		this(Widget target, int iv, string sv) {
2060 			super("change", target);
2061 			this.iv = iv;
2062 			this.sv = sv;
2063 		}
2064 		immutable int iv;
2065 		immutable string sv;
2066 
2067 		override @property string stringValue() { return sv; }
2068 		override @property int intValue() { return iv; }
2069 	}
2070 
2071 	version(win32_widgets)
2072 	override void handleWmCommand(ushort cmd, ushort id) {
2073 		if(cmd == CBN_SELCHANGE) {
2074 			selection_ = cast(int) SendMessageW(hwnd, 327 /* CB_GETCURSEL */, 0, 0);
2075 			fireChangeEvent();
2076 		}
2077 	}
2078 
2079 	private void fireChangeEvent() {
2080 		if(selection_ >= options.length)
2081 			selection_ = -1;
2082 
2083 		auto t = new SelectionChangedEvent(this, selection_, selection_ == -1 ? null : options[selection_]);
2084 		t.dispatch();
2085 	}
2086 
2087 	version(win32_widgets) {
2088 		override int minHeight() { return defaultLineHeight + 6; }
2089 		override int maxHeight() { return defaultLineHeight + 6; }
2090 	} else {
2091 		override int minHeight() { return defaultLineHeight + 4; }
2092 		override int maxHeight() { return defaultLineHeight + 4; }
2093 	}
2094 
2095 	version(custom_widgets) {
2096 
2097 		// FIXME: this should scroll if there's too many elements to reasonably fit on screen
2098 
2099 		SimpleWindow dropDown;
2100 		void popup() {
2101 			auto w = width;
2102 			// FIXME: suggestedDropdownHeight see below
2103 			auto h = cast(int) this.options.length * defaultLineHeight + 8;
2104 
2105 			auto coord = this.globalCoordinates();
2106 			auto dropDown = new SimpleWindow(
2107 				w, h,
2108 				null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.dropdownMenu, WindowFlags.dontAutoShow, parentWindow ? parentWindow.win : null);
2109 
2110 			dropDown.move(coord.x, coord.y + this.height);
2111 
2112 			{
2113 				auto cs = getComputedStyle();
2114 				auto painter = dropDown.draw();
2115 				draw3dFrame(0, 0, w, h, painter, FrameStyle.risen, getComputedStyle().background.color);
2116 				auto p = Point(4, 4);
2117 				painter.outlineColor = cs.foregroundColor;
2118 				foreach(option; options) {
2119 					painter.drawText(p, option);
2120 					p.y += defaultLineHeight;
2121 				}
2122 			}
2123 
2124 			dropDown.setEventHandlers(
2125 				(MouseEvent event) {
2126 					if(event.type == MouseEventType.buttonReleased) {
2127 						dropDown.close();
2128 						auto element = (event.y - 4) / defaultLineHeight;
2129 						if(element >= 0 && element <= options.length) {
2130 							selection_ = element;
2131 
2132 							fireChangeEvent();
2133 						}
2134 					}
2135 				}
2136 			);
2137 
2138 			dropDown.visibilityChanged = (bool visible) {
2139 				if(visible) {
2140 					this.redraw();
2141 					dropDown.grabInput();
2142 				} else {
2143 					dropDown.releaseInputGrab();
2144 				}
2145 			};
2146 
2147 			dropDown.show();
2148 		}
2149 
2150 	}
2151 }
2152 
2153 /++
2154 	A drop-down list where the user must select one of the
2155 	given options. Like `<select>` in HTML.
2156 +/
2157 class DropDownSelection : ComboboxBase {
2158 	this(Widget parent) {
2159 		version(win32_widgets)
2160 			super(3 /* CBS_DROPDOWNLIST */ | WS_VSCROLL, parent);
2161 		else version(custom_widgets) {
2162 			super(parent);
2163 
2164 			addEventListener("focus", () { this.redraw; });
2165 			addEventListener("blur", () { this.redraw; });
2166 			addEventListener(EventType.change, () { this.redraw; });
2167 			addEventListener("mousedown", () { this.focus(); this.popup(); });
2168 			addEventListener((KeyDownEvent event) {
2169 				if(event.key == Key.Space)
2170 					popup();
2171 			});
2172 		} else static assert(false);
2173 	}
2174 
2175 	mixin Padding!q{2};
2176 	static class Style : Widget.Style {
2177 		override FrameStyle borderStyle() { return FrameStyle.risen; }
2178 	}
2179 	mixin OverrideStyle!Style;
2180 
2181 	version(custom_widgets)
2182 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
2183 		auto cs = getComputedStyle();
2184 
2185 		painter.drawText(bounds.upperLeft, selection_ == -1 ? "" : options[selection_]);
2186 
2187 		painter.outlineColor = cs.foregroundColor;
2188 		painter.fillColor = cs.foregroundColor;
2189 		Point[4] triangle;
2190 		enum padding = 6;
2191 		enum paddingV = 7;
2192 		enum triangleWidth = 10;
2193 		triangle[0] = Point(width - padding - triangleWidth, paddingV);
2194 		triangle[1] = Point(width - padding - triangleWidth / 2, height - paddingV);
2195 		triangle[2] = Point(width - padding - 0, paddingV);
2196 		triangle[3] = triangle[0];
2197 		painter.drawPolygon(triangle[]);
2198 
2199 		return bounds;
2200 	}
2201 
2202 	version(win32_widgets)
2203 	override void registerMovement() {
2204 		version(win32_widgets) {
2205 			if(hwnd) {
2206 				auto pos = getChildPositionRelativeToParentHwnd(this);
2207 				// the height given to this from Windows' perspective is supposed
2208 				// to include the drop down's height. so I add to it to give some
2209 				// room for that.
2210 				// FIXME: maybe make the subclass provide a suggestedDropdownHeight thing
2211 				MoveWindow(hwnd, pos[0], pos[1], width, height + 200, true);
2212 			}
2213 		}
2214 		sendResizeEvent();
2215 	}
2216 }
2217 
2218 /++
2219 	A text box with a drop down arrow listing selections.
2220 	The user can choose from the list, or type their own.
2221 +/
2222 class FreeEntrySelection : ComboboxBase {
2223 	this(Widget parent) {
2224 		version(win32_widgets)
2225 			super(2 /* CBS_DROPDOWN */, parent);
2226 		else version(custom_widgets) {
2227 			super(parent);
2228 			auto hl = new HorizontalLayout(this);
2229 			lineEdit = new LineEdit(hl);
2230 
2231 			tabStop = false;
2232 
2233 			lineEdit.addEventListener("focus", &lineEdit.selectAll);
2234 
2235 			auto btn = new class ArrowButton {
2236 				this() {
2237 					super(ArrowDirection.down, hl);
2238 				}
2239 				override int maxHeight() {
2240 					return int.max;
2241 				}
2242 			};
2243 			//btn.addDirectEventListener("focus", &lineEdit.focus);
2244 			btn.addEventListener("triggered", &this.popup);
2245 			addEventListener(EventType.change, (Event event) {
2246 				lineEdit.content = event.stringValue;
2247 				lineEdit.focus();
2248 				redraw();
2249 			});
2250 		}
2251 		else static assert(false);
2252 	}
2253 
2254 	version(custom_widgets) {
2255 		LineEdit lineEdit;
2256 	}
2257 }
2258 
2259 /++
2260 	A combination of free entry with a list below it.
2261 +/
2262 class ComboBox : ComboboxBase {
2263 	this(Widget parent) {
2264 		version(win32_widgets)
2265 			super(1 /* CBS_SIMPLE */ | CBS_NOINTEGRALHEIGHT, parent);
2266 		else version(custom_widgets) {
2267 			super(parent);
2268 			lineEdit = new LineEdit(this);
2269 			listWidget = new ListWidget(this);
2270 			listWidget.multiSelect = false;
2271 			listWidget.addEventListener(EventType.change, delegate(Widget, Event) {
2272 				string c = null;
2273 				foreach(option; listWidget.options)
2274 					if(option.selected) {
2275 						c = option.label;
2276 						break;
2277 					}
2278 				lineEdit.content = c;
2279 			});
2280 
2281 			listWidget.tabStop = false;
2282 			this.tabStop = false;
2283 			listWidget.addEventListener("focus", &lineEdit.focus);
2284 			this.addEventListener("focus", &lineEdit.focus);
2285 
2286 			addDirectEventListener(EventType.change, {
2287 				listWidget.setSelection(selection_);
2288 				if(selection_ != -1)
2289 					lineEdit.content = options[selection_];
2290 				lineEdit.focus();
2291 				redraw();
2292 			});
2293 
2294 			lineEdit.addEventListener("focus", &lineEdit.selectAll);
2295 
2296 			listWidget.addDirectEventListener(EventType.change, {
2297 				int set = -1;
2298 				foreach(idx, opt; listWidget.options)
2299 					if(opt.selected) {
2300 						set = cast(int) idx;
2301 						break;
2302 					}
2303 				if(set != selection_)
2304 					this.setSelection(set);
2305 			});
2306 		} else static assert(false);
2307 	}
2308 
2309 	override int minHeight() { return defaultLineHeight * 3; }
2310 	override int maxHeight() { return int.max; }
2311 	override int heightStretchiness() { return 5; }
2312 
2313 	version(custom_widgets) {
2314 		LineEdit lineEdit;
2315 		ListWidget listWidget;
2316 
2317 		override void addOption(string s) {
2318 			listWidget.options ~= ListWidget.Option(s);
2319 			ComboboxBase.addOption(s);
2320 		}
2321 	}
2322 }
2323 
2324 /+
2325 class Spinner : Widget {
2326 	version(win32_widgets)
2327 	this(Widget parent) {
2328 		super(parent);
2329 		parentWindow = parent.parentWindow;
2330 		auto hlayout = new HorizontalLayout(this);
2331 		lineEdit = new LineEdit(hlayout);
2332 		upDownControl = new UpDownControl(hlayout);
2333 	}
2334 
2335 	LineEdit lineEdit;
2336 	UpDownControl upDownControl;
2337 }
2338 
2339 class UpDownControl : Widget {
2340 	version(win32_widgets)
2341 	this(Widget parent) {
2342 		super(parent);
2343 		parentWindow = parent.parentWindow;
2344 		createWin32Window(this, "msctls_updown32"w, null, 4/*UDS_ALIGNRIGHT*/| 2 /* UDS_SETBUDDYINT */ | 16 /* UDS_AUTOBUDDY */ | 32 /* UDS_ARROWKEYS */);
2345 	}
2346 
2347 	override int minHeight() { return defaultLineHeight; }
2348 	override int maxHeight() { return defaultLineHeight * 3/2; }
2349 
2350 	override int minWidth() { return defaultLineHeight * 3/2; }
2351 	override int maxWidth() { return defaultLineHeight * 3/2; }
2352 }
2353 +/
2354 
2355 /+
2356 class DataView : Widget {
2357 	// this is the omnibus data viewer
2358 	// the internal data layout is something like:
2359 	// string[string][] but also each node can have parents
2360 }
2361 +/
2362 
2363 
2364 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb775491(v=vs.85).aspx#PROGRESS_CLASS
2365 
2366 // http://svn.dsource.org/projects/bindings/trunk/win32/commctrl.d
2367 
2368 // FIXME: menus should prolly capture the mouse. ugh i kno.
2369 /*
2370 	TextEdit needs:
2371 
2372 	* caret manipulation
2373 	* selection control
2374 	* convenience functions for appendText, insertText, insertTextAtCaret, etc.
2375 
2376 	For example:
2377 
2378 	connect(paste, &textEdit.insertTextAtCaret);
2379 
2380 	would be nice.
2381 
2382 
2383 
2384 	I kinda want an omnibus dataview that combines list, tree,
2385 	and table - it can be switched dynamically between them.
2386 
2387 	Flattening policy: only show top level, show recursive, show grouped
2388 	List styles: plain list (e.g. <ul>), tiles (some details next to it), icons (like Windows explorer)
2389 
2390 	Single select, multi select, organization, drag+drop
2391 */
2392 
2393 //static if(UsingSimpledisplayX11)
2394 version(win32_widgets) {}
2395 else version(custom_widgets) {
2396 	enum scrollClickRepeatInterval = 50;
2397 
2398 deprecated("Get these properties off `Widget.getComputedStyle` instead. The defaults are now set in the `WidgetPainter.visualTheme`.") {
2399 	enum windowBackgroundColor = Color(212, 212, 212); // used to be 192
2400 	enum activeTabColor = lightAccentColor;
2401 	enum hoveringColor = Color(228, 228, 228);
2402 	enum buttonColor = windowBackgroundColor;
2403 	enum depressedButtonColor = darkAccentColor;
2404 	enum activeListXorColor = Color(255, 255, 127);
2405 	enum progressBarColor = Color(0, 0, 128);
2406 	enum activeMenuItemColor = Color(0, 0, 128);
2407 
2408 }}
2409 else static assert(false);
2410 deprecated("Get these properties off the `visualTheme` instead.") {
2411 	// these are used by horizontal rule so not just custom_widgets. for now at least.
2412 	enum darkAccentColor = Color(172, 172, 172);
2413 	enum lightAccentColor = Color(223, 223, 223); // used to be 223
2414 }
2415 
2416 private const(wchar)* toWstringzInternal(in char[] s) {
2417 	wchar[] str;
2418 	str.reserve(s.length + 1);
2419 	foreach(dchar ch; s)
2420 		str ~= ch;
2421 	str ~= '\0';
2422 	return str.ptr;
2423 }
2424 
2425 static if(SimpledisplayTimerAvailable)
2426 void setClickRepeat(Widget w, int interval, int delay = 250) {
2427 	Timer timer;
2428 	int delayRemaining = delay / interval;
2429 	if(delayRemaining <= 1)
2430 		delayRemaining = 2;
2431 
2432 	immutable originalDelayRemaining = delayRemaining;
2433 
2434 	w.addDirectEventListener("mousedown", (Event ev) {
2435 		if(ev.srcElement !is w)
2436 			return;
2437 		if(timer !is null) {
2438 			timer.destroy();
2439 			timer = null;
2440 		}
2441 		delayRemaining = originalDelayRemaining;
2442 		timer = new Timer(interval, () {
2443 			if(delayRemaining > 0)
2444 				delayRemaining--;
2445 			else {
2446 				auto ev = new ClickEvent(w);
2447 				ev.sendDirectly();
2448 			}
2449 		});
2450 	});
2451 
2452 	w.addDirectEventListener("mouseup", (Event ev) {
2453 		if(ev.srcElement !is w)
2454 			return;
2455 		if(timer !is null) {
2456 			timer.destroy();
2457 			timer = null;
2458 		}
2459 	});
2460 
2461 	w.addDirectEventListener("mouseleave", (Event ev) {
2462 		if(ev.srcElement !is w)
2463 			return;
2464 		if(timer !is null) {
2465 			timer.destroy();
2466 			timer = null;
2467 		}
2468 	});
2469 
2470 }
2471 else
2472 void setClickRepeat(Widget w, int interval, int delay = 250) {}
2473 
2474 enum FrameStyle {
2475 	none, ///
2476 	risen, /// a 3d pop-out effect (think Windows 95 button)
2477 	sunk, /// a 3d sunken effect (think Windows 95 button as you click on it)
2478 	solid, ///
2479 	dotted, ///
2480 	fantasy, /// a style based on a popular fantasy video game
2481 }
2482 
2483 version(custom_widgets)
2484 deprecated
2485 void draw3dFrame(Widget widget, ScreenPainter painter, FrameStyle style) {
2486 	draw3dFrame(0, 0, widget.width, widget.height, painter, style, WidgetPainter.visualTheme.windowBackgroundColor);
2487 }
2488 
2489 version(custom_widgets)
2490 void draw3dFrame(Widget widget, ScreenPainter painter, FrameStyle style, Color background) {
2491 	draw3dFrame(0, 0, widget.width, widget.height, painter, style, background);
2492 }
2493 
2494 version(custom_widgets)
2495 deprecated
2496 void draw3dFrame(int x, int y, int width, int height, ScreenPainter painter, FrameStyle style) {
2497 	draw3dFrame(x, y, width, height, painter, style, WidgetPainter.visualTheme.windowBackgroundColor);
2498 }
2499 
2500 int draw3dFrame(int x, int y, int width, int height, ScreenPainter painter, FrameStyle style, Color background, Color border = Color.transparent) {
2501 	int borderWidth;
2502 	final switch(style) {
2503 		case FrameStyle.sunk, FrameStyle.risen:
2504 			// outer layer
2505 			painter.outlineColor = style == FrameStyle.sunk ? Color.white : Color.black;
2506 			borderWidth = 2;
2507 		break;
2508 		case FrameStyle.none:
2509 			painter.outlineColor = background;
2510 			borderWidth = 0;
2511 		break;
2512 		case FrameStyle.solid:
2513 			painter.pen = Pen(border, 1);
2514 			borderWidth = 1;
2515 		break;
2516 		case FrameStyle.dotted:
2517 			painter.pen = Pen(border, 1, Pen.Style.Dotted);
2518 			borderWidth = 1;
2519 		break;
2520 		case FrameStyle.fantasy:
2521 			painter.pen = Pen(border, 3);
2522 			borderWidth = 3;
2523 		break;
2524 	}
2525 
2526 	painter.fillColor = background;
2527 	painter.drawRectangle(Point(x + 0, y + 0), width, height);
2528 
2529 
2530 	if(style == FrameStyle.sunk || style == FrameStyle.risen) {
2531 		// 3d effect
2532 		auto vt = WidgetPainter.visualTheme;
2533 
2534 		painter.outlineColor = (style == FrameStyle.sunk) ? vt.darkAccentColor : vt.lightAccentColor;
2535 		painter.drawLine(Point(x + 0, y + 0), Point(x + width, y + 0));
2536 		painter.drawLine(Point(x + 0, y + 0), Point(x + 0, y + height - 1));
2537 
2538 		// inner layer
2539 		//right, bottom
2540 		painter.outlineColor = (style == FrameStyle.sunk) ? vt.lightAccentColor : vt.darkAccentColor;
2541 		painter.drawLine(Point(x + width - 2, y + 2), Point(x + width - 2, y + height - 2));
2542 		painter.drawLine(Point(x + 2, y + height - 2), Point(x + width - 2, y + height - 2));
2543 		// left, top
2544 		painter.outlineColor = (style == FrameStyle.sunk) ? Color.black : Color.white;
2545 		painter.drawLine(Point(x + 1, y + 1), Point(x + width, y + 1));
2546 		painter.drawLine(Point(x + 1, y + 1), Point(x + 1, y + height - 2));
2547 	} else if(style == FrameStyle.fantasy) {
2548 		painter.pen = Pen(Color.white, 1, Pen.Style.Solid);
2549 		painter.fillColor = Color.transparent;
2550 		painter.drawRectangle(Point(x + 1, y + 1), Point(x + width - 1, y + height - 1));
2551 	}
2552 
2553 	return borderWidth;
2554 }
2555 
2556 /++
2557 	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.
2558 
2559 	See_Also:
2560 		[MenuItem]
2561 		[ToolButton]
2562 		[Menu.addItem]
2563 +/
2564 class Action {
2565 	version(win32_widgets) {
2566 		private int id;
2567 		private static int lastId = 9000;
2568 		private static Action[int] mapping;
2569 	}
2570 
2571 	KeyEvent accelerator;
2572 
2573 	// FIXME: disable message
2574 	// and toggle thing?
2575 	// ??? and trigger arguments too ???
2576 
2577 	/++
2578 		Params:
2579 			label = the textual label
2580 			icon = icon ID. See [GenericIcons]. There is currently no way to do custom icons.
2581 			triggered = initial handler, more can be added via the [triggered] member.
2582 	+/
2583 	this(string label, ushort icon = 0, void delegate() triggered = null) {
2584 		this.label = label;
2585 		this.iconId = icon;
2586 		if(triggered !is null)
2587 			this.triggered ~= triggered;
2588 		version(win32_widgets) {
2589 			id = ++lastId;
2590 			mapping[id] = this;
2591 		}
2592 	}
2593 
2594 	private string label;
2595 	private ushort iconId;
2596 	// icon
2597 
2598 	// when it is triggered, the triggered event is fired on the window
2599 	/// The list of handlers when it is triggered.
2600 	void delegate()[] triggered;
2601 }
2602 
2603 /*
2604 	plan:
2605 		keyboard accelerators
2606 
2607 		* menus (and popups and tooltips)
2608 		* status bar
2609 		* toolbars and buttons
2610 
2611 		sortable table view
2612 
2613 		maybe notification area icons
2614 		basic clipboard
2615 
2616 		* radio box
2617 		splitter
2618 		toggle buttons (optionally mutually exclusive, like in Paint)
2619 		label, rich text display, multi line plain text (selectable)
2620 		* fieldset
2621 		* nestable grid layout
2622 		single line text input
2623 		* multi line text input
2624 		slider
2625 		spinner
2626 		list box
2627 		drop down
2628 		combo box
2629 		auto complete box
2630 		* progress bar
2631 
2632 		terminal window/widget (on unix it might even be a pty but really idk)
2633 
2634 		ok button
2635 		cancel button
2636 
2637 		keyboard hotkeys
2638 
2639 		scroll widget
2640 
2641 		event redirections and network transparency
2642 		script integration
2643 */
2644 
2645 
2646 /*
2647 	MENUS
2648 
2649 	auto bar = new MenuBar(window);
2650 	window.menuBar = bar;
2651 
2652 	auto fileMenu = bar.addItem(new Menu("&File"));
2653 	fileMenu.addItem(new MenuItem("&Exit"));
2654 
2655 
2656 	EVENTS
2657 
2658 	For controls, you should usually use "triggered" rather than "click", etc., because
2659 	triggered handles both keyboard (focus and press as well as hotkeys) and mouse activation.
2660 	This is the case on menus and pushbuttons.
2661 
2662 	"click", on the other hand, currently only fires when it is literally clicked by the mouse.
2663 */
2664 
2665 
2666 /*
2667 enum LinePreference {
2668 	AlwaysOnOwnLine, // always on its own line
2669 	PreferOwnLine, // it will always start a new line, and if max width <= line width, it will expand all the way
2670 	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
2671 }
2672 */
2673 
2674 /++
2675 	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.
2676 
2677 	---
2678 	class MyWidget : Widget {
2679 		this(Widget parent) { super(parent); }
2680 
2681 		// set paddingLeft, paddingRight, paddingTop, and paddingBottom all to `return 4;` in one go:
2682 		mixin Padding!q{4};
2683 
2684 		// set marginLeft, marginRight, marginTop, and marginBottom all to `return 8;` in one go:
2685 		mixin Margin!q{8};
2686 
2687 		// but if I specify one outside, it overrides the override, so now marginLeft is 2,
2688 		// while Top/Bottom/Right remain 8 from the mixin above.
2689 		override int marginLeft() { return 2; }
2690 	}
2691 	---
2692 
2693 
2694 	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]).
2695 
2696 	Padding is the area inside a widget where its background is drawn, but the content avoids.
2697 
2698 	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!).
2699 
2700 	* 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.
2701 +/
2702 mixin template Padding(string code) {
2703 	override int paddingLeft() { return mixin(code);}
2704 	override int paddingRight() { return mixin(code);}
2705 	override int paddingTop() { return mixin(code);}
2706 	override int paddingBottom() { return mixin(code);}
2707 }
2708 
2709 /// ditto
2710 mixin template Margin(string code) {
2711 	override int marginLeft() { return mixin(code);}
2712 	override int marginRight() { return mixin(code);}
2713 	override int marginTop() { return mixin(code);}
2714 	override int marginBottom() { return mixin(code);}
2715 }
2716 
2717 private
2718 void recomputeChildLayout(string relevantMeasure)(Widget parent) {
2719 	enum calcingV = relevantMeasure == "height";
2720 
2721 	parent.registerMovement();
2722 
2723 	if(parent.children.length == 0)
2724 		return;
2725 
2726 	auto parentStyle = parent.getComputedStyle();
2727 
2728 	enum firstThingy = relevantMeasure == "height" ? "Top" : "Left";
2729 	enum secondThingy = relevantMeasure == "height" ? "Bottom" : "Right";
2730 
2731 	enum otherFirstThingy = relevantMeasure == "height" ? "Left" : "Top";
2732 	enum otherSecondThingy = relevantMeasure == "height" ? "Right" : "Bottom";
2733 
2734 	// my own width and height should already be set by the caller of this function...
2735 	int spaceRemaining = mixin("parent." ~ relevantMeasure) -
2736 		mixin("parentStyle.padding"~firstThingy~"()") -
2737 		mixin("parentStyle.padding"~secondThingy~"()");
2738 
2739 	int stretchinessSum;
2740 	int stretchyChildSum;
2741 	int lastMargin = 0;
2742 
2743 	int shrinkinessSum;
2744 	int shrinkyChildSum;
2745 
2746 	// set initial size
2747 	foreach(child; parent.children) {
2748 
2749 		auto childStyle = child.getComputedStyle();
2750 
2751 		if(cast(StaticPosition) child)
2752 			continue;
2753 		if(child.hidden)
2754 			continue;
2755 
2756 		const iw = child.flexBasisWidth();
2757 		const ih = child.flexBasisHeight();
2758 
2759 		static if(calcingV) {
2760 			child.width = parent.width -
2761 				mixin("childStyle.margin"~otherFirstThingy~"()") -
2762 				mixin("childStyle.margin"~otherSecondThingy~"()") -
2763 				mixin("parentStyle.padding"~otherFirstThingy~"()") -
2764 				mixin("parentStyle.padding"~otherSecondThingy~"()");
2765 
2766 			if(child.width < 0)
2767 				child.width = 0;
2768 			if(child.width > childStyle.maxWidth())
2769 				child.width = childStyle.maxWidth();
2770 
2771 			if(iw > 0) {
2772 				auto totalPossible = child.width;
2773 				if(child.width > iw && child.widthStretchiness() == 0)
2774 					child.width = iw;
2775 			}
2776 
2777 			child.height = mymax(childStyle.minHeight(), ih);
2778 		} else {
2779 			// set to take all the space
2780 			child.height = parent.height -
2781 				mixin("childStyle.margin"~firstThingy~"()") -
2782 				mixin("childStyle.margin"~secondThingy~"()") -
2783 				mixin("parentStyle.padding"~firstThingy~"()") -
2784 				mixin("parentStyle.padding"~secondThingy~"()");
2785 
2786 			// then clamp it
2787 			if(child.height < 0)
2788 				child.height = 0;
2789 			if(child.height > childStyle.maxHeight())
2790 				child.height = childStyle.maxHeight();
2791 
2792 			// and if possible, respect the ideal target
2793 			if(ih > 0) {
2794 				auto totalPossible = child.height;
2795 				if(child.height > ih && child.heightStretchiness() == 0)
2796 					child.height = ih;
2797 			}
2798 
2799 			// if we have an ideal, try to respect it, otehrwise, just use the minimum
2800 			child.width = mymax(childStyle.minWidth(), iw);
2801 		}
2802 
2803 		spaceRemaining -= mixin("child." ~ relevantMeasure);
2804 
2805 		int thisMargin = mymax(lastMargin, mixin("childStyle.margin"~firstThingy~"()"));
2806 		auto margin = mixin("childStyle.margin" ~ secondThingy ~ "()");
2807 		lastMargin = margin;
2808 		spaceRemaining -= thisMargin + margin;
2809 
2810 		auto s = mixin("child." ~ relevantMeasure ~ "Stretchiness()");
2811 		stretchinessSum += s;
2812 		if(s > 0)
2813 			stretchyChildSum++;
2814 
2815 		auto s2 = mixin("child." ~ relevantMeasure ~ "Shrinkiness()");
2816 		shrinkinessSum += s2;
2817 		if(s2 > 0)
2818 			shrinkyChildSum++;
2819 	}
2820 
2821 	if(spaceRemaining < 0 && shrinkyChildSum) {
2822 		// shrink to get into the space if it is possible
2823 		auto toRemove = -spaceRemaining;
2824 		auto removalPerItem  = toRemove * shrinkinessSum / shrinkyChildSum;
2825 		auto remainder = toRemove * shrinkinessSum % shrinkyChildSum;
2826 
2827 		// FIXME: wtf why am i shrinking things with no shrinkiness?
2828 
2829 		foreach(child; parent.children) {
2830 			auto childStyle = child.getComputedStyle();
2831 			if(cast(StaticPosition) child)
2832 				continue;
2833 			if(child.hidden)
2834 				continue;
2835 			static if(calcingV) {
2836 				auto maximum = childStyle.maxHeight();
2837 			} else {
2838 				auto maximum = childStyle.maxWidth();
2839 			}
2840 
2841 			mixin("child._" ~ relevantMeasure) -= removalPerItem + remainder; // this is removing more than needed to trigger the next thing. ugh.
2842 
2843 			spaceRemaining += removalPerItem + remainder;
2844 		}
2845 	}
2846 
2847 	// stretch to fill space
2848 	while(spaceRemaining > 0 && stretchinessSum && stretchyChildSum) {
2849 		auto spacePerChild = spaceRemaining / stretchinessSum;
2850 		bool spreadEvenly;
2851 		bool giveToBiggest;
2852 		if(spacePerChild <= 0) {
2853 			spacePerChild = spaceRemaining / stretchyChildSum;
2854 			spreadEvenly = true;
2855 		}
2856 		if(spacePerChild <= 0) {
2857 			giveToBiggest = true;
2858 		}
2859 		int previousSpaceRemaining = spaceRemaining;
2860 		stretchinessSum = 0;
2861 		Widget mostStretchy;
2862 		int mostStretchyS;
2863 		foreach(child; parent.children) {
2864 			auto childStyle = child.getComputedStyle();
2865 			if(cast(StaticPosition) child)
2866 				continue;
2867 			if(child.hidden)
2868 				continue;
2869 			static if(calcingV) {
2870 				auto maximum = childStyle.maxHeight();
2871 			} else {
2872 				auto maximum = childStyle.maxWidth();
2873 			}
2874 
2875 			if(mixin("child." ~ relevantMeasure) >= maximum) {
2876 				auto adj = mixin("child." ~ relevantMeasure) - maximum;
2877 				mixin("child._" ~ relevantMeasure) -= adj;
2878 				spaceRemaining += adj;
2879 				continue;
2880 			}
2881 			auto s = mixin("child." ~ relevantMeasure ~ "Stretchiness()");
2882 			if(s <= 0)
2883 				continue;
2884 			auto spaceAdjustment = spacePerChild * (spreadEvenly ? 1 : s);
2885 			mixin("child._" ~ relevantMeasure) += spaceAdjustment;
2886 			spaceRemaining -= spaceAdjustment;
2887 			if(mixin("child." ~ relevantMeasure) > maximum) {
2888 				auto diff = mixin("child." ~ relevantMeasure) - maximum;
2889 				mixin("child._" ~ relevantMeasure) -= diff;
2890 				spaceRemaining += diff;
2891 			} else if(mixin("child._" ~ relevantMeasure) < maximum) {
2892 				stretchinessSum += mixin("child." ~ relevantMeasure ~ "Stretchiness()");
2893 				if(mostStretchy is null || s >= mostStretchyS) {
2894 					mostStretchy = child;
2895 					mostStretchyS = s;
2896 				}
2897 			}
2898 		}
2899 
2900 		if(giveToBiggest && mostStretchy !is null) {
2901 			auto child = mostStretchy;
2902 			auto childStyle = child.getComputedStyle();
2903 			int spaceAdjustment = spaceRemaining;
2904 
2905 			static if(calcingV)
2906 				auto maximum = childStyle.maxHeight();
2907 			else
2908 				auto maximum = childStyle.maxWidth();
2909 
2910 			mixin("child._" ~ relevantMeasure) += spaceAdjustment;
2911 			spaceRemaining -= spaceAdjustment;
2912 			if(mixin("child._" ~ relevantMeasure) > maximum) {
2913 				auto diff = mixin("child." ~ relevantMeasure) - maximum;
2914 				mixin("child._" ~ relevantMeasure) -= diff;
2915 				spaceRemaining += diff;
2916 			}
2917 		}
2918 
2919 		if(spaceRemaining == previousSpaceRemaining)
2920 			break; // apparently nothing more we can do
2921 
2922 	}
2923 
2924 	// position
2925 	lastMargin = 0;
2926 	int currentPos = mixin("parent.padding"~firstThingy~"()");
2927 	foreach(child; parent.children) {
2928 		auto childStyle = child.getComputedStyle();
2929 		if(cast(StaticPosition) child) {
2930 			child.recomputeChildLayout();
2931 			continue;
2932 		}
2933 		if(child.hidden)
2934 			continue;
2935 		auto margin = mixin("childStyle.margin" ~ secondThingy ~ "()");
2936 		int thisMargin = mymax(lastMargin, mixin("childStyle.margin"~firstThingy~"()"));
2937 		currentPos += thisMargin;
2938 		static if(calcingV) {
2939 			child.x = parentStyle.paddingLeft() + childStyle.marginLeft();
2940 			child.y = currentPos;
2941 		} else {
2942 			child.x = currentPos;
2943 			child.y = parentStyle.paddingTop() + childStyle.marginTop();
2944 
2945 		}
2946 		currentPos += mixin("child." ~ relevantMeasure);
2947 		currentPos += margin;
2948 		lastMargin = margin;
2949 
2950 		child.recomputeChildLayout();
2951 	}
2952 }
2953 
2954 int mymax(int a, int b) { return a > b ? a : b; }
2955 int mymax(int a, int b, int c) {
2956 	auto d = mymax(a, b);
2957 	return c > d ? c : d;
2958 }
2959 
2960 // OK so we need to make getting at the native window stuff possible in simpledisplay.d
2961 // and here, it must be integrable with the layout, the event system, and not be painted over.
2962 version(win32_widgets) {
2963 
2964 	// this function just does stuff that a parent window needs for redirection
2965 	int WindowProcedureHelper(Widget this_, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
2966 		this_.hookedWndProc(msg, wParam, lParam);
2967 
2968 		switch(msg) {
2969 
2970 			case WM_VSCROLL, WM_HSCROLL:
2971 				auto pos = HIWORD(wParam);
2972 				auto m = LOWORD(wParam);
2973 
2974 				auto scrollbarHwnd = cast(HWND) lParam;
2975 
2976 				if(auto widgetp = scrollbarHwnd in Widget.nativeMapping) {
2977 
2978 					//auto smw = cast(ScrollMessageWidget) widgetp.parent;
2979 
2980 					switch(m) {
2981 						/+
2982 						// I don't think those messages are ever actually sent normally by the widget itself,
2983 						// they are more used for the keyboard interface. methinks.
2984 						case SB_BOTTOM:
2985 							//import std.stdio; writeln("end");
2986 							auto event = new Event("scrolltoend", *widgetp);
2987 							event.dispatch();
2988 							//if(!event.defaultPrevented)
2989 						break;
2990 						case SB_TOP:
2991 							//import std.stdio; writeln("top");
2992 							auto event = new Event("scrolltobeginning", *widgetp);
2993 							event.dispatch();
2994 						break;
2995 						case SB_ENDSCROLL:
2996 							// idk
2997 						break;
2998 						+/
2999 						case SB_LINEDOWN:
3000 							(*widgetp).emitCommand!"scrolltonextline"();
3001 						return 0;
3002 						case SB_LINEUP:
3003 							(*widgetp).emitCommand!"scrolltopreviousline"();
3004 						return 0;
3005 						case SB_PAGEDOWN:
3006 							(*widgetp).emitCommand!"scrolltonextpage"();
3007 						return 0;
3008 						case SB_PAGEUP:
3009 							(*widgetp).emitCommand!"scrolltopreviouspage"();
3010 						return 0;
3011 						case SB_THUMBPOSITION:
3012 							auto ev = new ScrollToPositionEvent(*widgetp, pos);
3013 							ev.dispatch();
3014 						return 0;
3015 						case SB_THUMBTRACK:
3016 							// eh kinda lying but i like the real time update display
3017 							auto ev = new ScrollToPositionEvent(*widgetp, pos);
3018 							ev.dispatch();
3019 							// the event loop doesn't seem to carry on with a requested redraw..
3020 							// so we request it to get our dirty bit set...
3021 							// then we need to immediately actually redraw it too for instant feedback to user
3022 							if(this_.parentWindow)
3023 								this_.parentWindow.actualRedraw();
3024 						return 0;
3025 						default:
3026 					}
3027 				}
3028 			break;
3029 
3030 			case WM_CONTEXTMENU:
3031 				auto hwndFrom = cast(HWND) wParam;
3032 
3033 				auto xPos = cast(short) LOWORD(lParam); 
3034 				auto yPos = cast(short) HIWORD(lParam); 
3035 
3036 				if(auto widgetp = hwndFrom in Widget.nativeMapping) {
3037 					POINT p;
3038 					p.x = xPos;
3039 					p.y = yPos;
3040 					ScreenToClient(hwnd, &p);
3041 					auto clientX = cast(ushort) p.x;
3042 					auto clientY = cast(ushort) p.y;
3043 
3044 					auto wap = widgetAtPoint(*widgetp, clientX, clientY);
3045 
3046 					if(wap.widget.showContextMenu(wap.x, wap.y, xPos, yPos)) {
3047 						return 0;
3048 					}
3049 				}
3050 			break;
3051 
3052 			case WM_DRAWITEM:
3053 				auto dis = cast(DRAWITEMSTRUCT*) lParam;
3054 				if(auto widgetp = dis.hwndItem in Widget.nativeMapping) {
3055 					return (*widgetp).handleWmDrawItem(dis);
3056 				}
3057 			break;
3058 
3059 			case WM_NOTIFY:
3060 				auto hdr = cast(NMHDR*) lParam;
3061 				auto hwndFrom = hdr.hwndFrom;
3062 				auto code = hdr.code;
3063 
3064 				if(auto widgetp = hwndFrom in Widget.nativeMapping) {
3065 					return (*widgetp).handleWmNotify(hdr, code, mustReturn);
3066 				}
3067 			break;
3068 			case WM_COMMAND:
3069 				auto handle = cast(HWND) lParam;
3070 				auto cmd = HIWORD(wParam);
3071 				return processWmCommand(hwnd, handle, cmd, LOWORD(wParam));
3072 
3073 			default:
3074 				// pass it on
3075 		}
3076 		return 0;
3077 	}
3078 
3079 
3080 
3081 	extern(Windows)
3082 	private
3083 	// this is called by native child windows, whereas the other hook is done by simpledisplay windows
3084 	// but can i merge them?!
3085 	LRESULT HookedWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
3086 		//import std.stdio; try { writeln(iMessage); } catch(Exception e) {};
3087 
3088 		if(auto te = hWnd in Widget.nativeMapping) {
3089 			try {
3090 
3091 				te.hookedWndProc(iMessage, wParam, lParam);
3092 
3093 				int mustReturn;
3094 				auto ret = WindowProcedureHelper(*te, hWnd, iMessage, wParam, lParam, mustReturn);
3095 				if(mustReturn)
3096 					return ret;
3097 
3098 				if(iMessage == WM_SETFOCUS) {
3099 					auto lol = *te;
3100 					while(lol !is null && lol.implicitlyCreated)
3101 						lol = lol.parent;
3102 					lol.focus();
3103 					//(*te).parentWindow.focusedWidget = lol;
3104 				}
3105 
3106 
3107 				if(iMessage == WM_CTLCOLORBTN || iMessage == WM_CTLCOLORSTATIC) {
3108 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
3109 					return cast(typeof(return)) GetSysColorBrush(COLOR_3DFACE); // this is the window background color...
3110 						//GetStockObject(NULL_BRUSH);
3111 				}
3112 
3113 				auto pos = getChildPositionRelativeToParentOrigin(*te);
3114 				lastDefaultPrevented = false;
3115 				// try {import std.stdio; writeln(typeid(*te)); } catch(Exception e) {}
3116 				if(SimpleWindow.triggerEvents(hWnd, iMessage, wParam, lParam, pos[0], pos[1], (*te).parentWindow.win) || !lastDefaultPrevented)
3117 					return CallWindowProcW((*te).originalWindowProcedure, hWnd, iMessage, wParam, lParam);
3118 				else {
3119 					// it was something we recognized, should only call the window procedure if the default was not prevented
3120 				}
3121 			} catch(Exception e) {
3122 				assert(0, e.toString());
3123 			}
3124 			return 0;
3125 		}
3126 		assert(0, "shouldn't be receiving messages for this window....");
3127 		//import std.conv;
3128 		//assert(0, to!string(hWnd) ~ " :: " ~ to!string(TextEdit.nativeMapping)); // not supposed to happen
3129 	}
3130 
3131 	extern(Windows)
3132 	private
3133 	LRESULT HookedWndProcBSGROUPBOX_HACK(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
3134 		if(iMessage == WM_ERASEBKGND) {
3135 			auto dc = GetDC(hWnd);
3136 			auto b = SelectObject(dc, GetSysColorBrush(COLOR_3DFACE));
3137 			auto p = SelectObject(dc, GetStockObject(NULL_PEN));
3138 			RECT r;
3139 			GetWindowRect(hWnd, &r);
3140 			// since the pen is null, to fill the whole space, we need the +1 on both.
3141 			gdi.Rectangle(dc, 0, 0, r.right - r.left + 1, r.bottom - r.top + 1);
3142 			SelectObject(dc, p);
3143 			SelectObject(dc, b);
3144 			ReleaseDC(hWnd, dc);
3145 			return 1;
3146 		}
3147 		return HookedWndProc(hWnd, iMessage, wParam, lParam);
3148 	}
3149 
3150 	/++
3151 		Calls MS Windows' CreateWindowExW function to create a native backing for the given widget. It will create
3152 		needed mappings, window procedure hooks, and other private member variables needed to tie it into the rest
3153 		of minigui's expectations.
3154 
3155 		This should be called in your widget's constructor AFTER you call `super(parent);`. The parent window
3156 		member MUST already be initialized for this function to succeed, which is done by [Widget]'s base constructor.
3157 
3158 		It assumes `className` is zero-terminated. It should come from a `"wide string literal"w`.
3159 
3160 		To check if you can use this, use `static if(UsingWin32Widgets)`.
3161 	+/
3162 	void createWin32Window(Widget p, const(wchar)[] className, string windowText, DWORD style, DWORD extStyle = 0) {
3163 		assert(p.parentWindow !is null);
3164 		assert(p.parentWindow.win.impl.hwnd !is null);
3165 
3166 		auto bsgroupbox = style == BS_GROUPBOX;
3167 
3168 		HWND phwnd;
3169 
3170 		auto wtf = p.parent;
3171 		while(wtf) {
3172 			if(wtf.hwnd !is null) {
3173 				phwnd = wtf.hwnd;
3174 				break;
3175 			}
3176 			wtf = wtf.parent;
3177 		}
3178 
3179 		if(phwnd is null)
3180 			phwnd = p.parentWindow.win.impl.hwnd;
3181 
3182 		assert(phwnd !is null);
3183 
3184 		WCharzBuffer wt = WCharzBuffer(windowText);
3185 
3186 		style |= WS_VISIBLE | WS_CHILD;
3187 		//if(className != WC_TABCONTROL)
3188 			style |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
3189 		p.hwnd = CreateWindowExW(extStyle, className.ptr, wt.ptr, style,
3190 				CW_USEDEFAULT, CW_USEDEFAULT, 100, 100,
3191 				phwnd, null, cast(HINSTANCE) GetModuleHandle(null), null);
3192 
3193 		assert(p.hwnd !is null);
3194 
3195 
3196 		static HFONT font;
3197 		if(font is null) {
3198 			NONCLIENTMETRICS params;
3199 			params.cbSize = params.sizeof;
3200 			if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
3201 				font = CreateFontIndirect(&params.lfMessageFont);
3202 			}
3203 		}
3204 
3205 		if(font)
3206 			SendMessage(p.hwnd, WM_SETFONT, cast(uint) font, true);
3207 
3208 		p.simpleWindowWrappingHwnd = new SimpleWindow(p.hwnd);
3209 		p.simpleWindowWrappingHwnd.beingOpenKeepsAppOpen = false;
3210 		Widget.nativeMapping[p.hwnd] = p;
3211 
3212 		if(bsgroupbox)
3213 		p.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(p.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProcBSGROUPBOX_HACK);
3214 		else
3215 		p.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(p.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc);
3216 
3217 		EnumChildWindows(p.hwnd, &childHandler, cast(LPARAM) cast(void*) p);
3218 
3219 		p.registerMovement();
3220 	}
3221 }
3222 
3223 version(win32_widgets)
3224 private
3225 extern(Windows) BOOL childHandler(HWND hwnd, LPARAM lparam) {
3226 	if(hwnd is null || hwnd in Widget.nativeMapping)
3227 		return true;
3228 	auto parent = cast(Widget) cast(void*) lparam;
3229 	Widget p = new Widget(null);
3230 	p._parent = parent;
3231 	p.parentWindow = parent.parentWindow;
3232 	p.hwnd = hwnd;
3233 	p.implicitlyCreated = true;
3234 	Widget.nativeMapping[p.hwnd] = p;
3235 	p.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(p.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc);
3236 	return true;
3237 }
3238 
3239 /++
3240 	Encapsulates the simpledisplay [ScreenPainter] for use on a [Widget], with [VisualTheme] and invalidated area awareness.
3241 +/
3242 struct WidgetPainter {
3243 	this(ScreenPainter screenPainter, Widget drawingUpon) {
3244 		this.drawingUpon = drawingUpon;
3245 		this.screenPainter = screenPainter;
3246 		if(auto font = visualTheme.defaultFontCached)
3247 			this.screenPainter.setFont(font);
3248 	}
3249 
3250 	///
3251 	ScreenPainter screenPainter;
3252 	/// Forward to the screen painter for other methods
3253 	alias screenPainter this;
3254 
3255 	private Widget drawingUpon;
3256 
3257 	/++
3258 		This is the list of rectangles that actually need to be redrawn.
3259 
3260 		Not actually implemented yet.
3261 	+/
3262 	Rectangle[] invalidatedRectangles;
3263 
3264 	private static BaseVisualTheme _visualTheme;
3265 
3266 	/++
3267 		Functions to access the visual theme and helpers to easily use it.
3268 
3269 		These are aware of the current widget's computed style out of the theme.
3270 	+/
3271 	static @property BaseVisualTheme visualTheme() {
3272 		if(_visualTheme is null)
3273 			_visualTheme = new DefaultVisualTheme();
3274 		return _visualTheme;
3275 	}
3276 
3277 	/// ditto
3278 	static @property void visualTheme(BaseVisualTheme theme) {
3279 		_visualTheme = theme;
3280 	}
3281 
3282 	/// ditto
3283 	Color themeForeground() {
3284 		return drawingUpon.getComputedStyle().foregroundColor();
3285 	}
3286 
3287 	/// ditto
3288 	Color themeBackground() {
3289 		return drawingUpon.getComputedStyle().background.color;
3290 	}
3291 
3292 	int isDarkTheme() {
3293 		return 0; // unspecified, yes, no as enum. FIXME
3294 	}
3295 
3296 	/++
3297 		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.
3298 
3299 		It gives your draw delegate a [Rectangle] representing the coordinates inside your border and padding.
3300 
3301 		If you change teh clip rectangle, you should change it back before you return.
3302 
3303 
3304 		The sequence it uses is:
3305 			background
3306 			content (delegated to you)
3307 			border
3308 			focused outline
3309 			selected overlay
3310 
3311 		Example code:
3312 
3313 		---
3314 		void paint(WidgetPainter painter) {
3315 			painter.drawThemed((bounds) {
3316 				return bounds; // if the selection overlay should be contained, you can return it here.
3317 			});
3318 		}
3319 		---
3320 	+/
3321 	void drawThemed(scope Rectangle delegate(const Rectangle bounds) drawBody) {
3322 		drawThemed((WidgetPainter painter, const Rectangle bounds) {
3323 			return drawBody(bounds);
3324 		});
3325 	}
3326 	// this overload is actually mroe for setting the delegate to a virtual function
3327 	void drawThemed(scope Rectangle delegate(WidgetPainter painter, const Rectangle bounds) drawBody) {
3328 		Rectangle rect = Rectangle(0, 0, drawingUpon.width, drawingUpon.height);
3329 
3330 		auto cs = drawingUpon.getComputedStyle();
3331 
3332 		auto bg = cs.background.color;
3333 
3334 		auto borderWidth = draw3dFrame(0, 0, drawingUpon.width, drawingUpon.height, this, cs.borderStyle, bg, cs.borderColor);
3335 
3336 		rect.left += borderWidth;
3337 		rect.right -= borderWidth;
3338 		rect.top += borderWidth;
3339 		rect.bottom -= borderWidth;
3340 
3341 		auto insideBorderRect = rect;
3342 
3343 		rect.left += cs.paddingLeft;
3344 		rect.right -= cs.paddingRight;
3345 		rect.top += cs.paddingTop;
3346 		rect.bottom += cs.paddingBottom;
3347 
3348 		this.outlineColor = this.themeForeground;
3349 		this.fillColor = bg;
3350 
3351 		auto widgetFont = cs.fontCached;
3352 		if(widgetFont !is null)
3353 			this.setFont(widgetFont);
3354 
3355 		rect = drawBody(this, rect);
3356 
3357 		if(widgetFont !is null) {
3358 			if(auto vtFont = visualTheme.defaultFontCached)
3359 				this.setFont(vtFont);
3360 			else
3361 				this.setFont(null);
3362 		}
3363 
3364 		if(auto os = cs.outlineStyle()) {
3365 			this.pen = Pen(cs.outlineColor(), 1, os == FrameStyle.dotted ? Pen.Style.Dotted : Pen.Style.Solid);
3366 			this.fillColor = Color.transparent;
3367 			this.drawRectangle(insideBorderRect);
3368 		}
3369 	}
3370 
3371 	/++
3372 		First, draw the background.
3373 		Then draw your content.
3374 		Next, draw the border.
3375 		And the focused indicator.
3376 		And the is-selected box.
3377 
3378 		If it is focused i can draw the outline too...
3379 
3380 		If selected i can even do the xor action but that's at the end.
3381 	+/
3382 	void drawThemeBackground() {
3383 
3384 	}
3385 
3386 	void drawThemeBorder() {
3387 
3388 	}
3389 
3390 	// all this stuff is a dangerous experiment....
3391 	static class ScriptableVersion {
3392 		ScreenPainterImplementation* p;
3393 		int originX, originY;
3394 
3395 		@scriptable:
3396 		void drawRectangle(int x, int y, int width, int height) {
3397 			p.drawRectangle(x + originX, y + originY, width, height);
3398 		}
3399 		void drawLine(int x1, int y1, int x2, int y2) {
3400 			p.drawLine(x1 + originX, y1 + originY, x2 + originX, y2 + originY);
3401 		}
3402 		void drawText(int x, int y, string text) {
3403 			p.drawText(x + originX, y + originY, 100000, 100000, text, 0);
3404 		}
3405 		void setOutlineColor(int r, int g, int b) {
3406 			p.pen = Pen(Color(r,g,b), 1);
3407 		}
3408 		void setFillColor(int r, int g, int b) {
3409 			p.fillColor = Color(r,g,b);
3410 		}
3411 	}
3412 
3413 	ScriptableVersion toArsdJsvar() {
3414 		auto sv = new ScriptableVersion;
3415 		sv.p = this.screenPainter.impl;
3416 		sv.originX = this.screenPainter.originX;
3417 		sv.originY = this.screenPainter.originY;
3418 		return sv;
3419 	}
3420 
3421 	static WidgetPainter fromJsVar(T)(T t) {
3422 		return WidgetPainter.init;
3423 	}
3424 	// done..........
3425 }
3426 
3427 
3428 struct Style {
3429 	static struct helper(string m, T) {
3430 		enum method = m;
3431 		T v;
3432 
3433 		mixin template MethodOverride(typeof(this) v) {
3434 			mixin("override typeof(v.v) "~v.method~"() { return v.v; }");
3435 		}
3436 	}
3437 
3438 	static auto opDispatch(string method, T)(T value) {
3439 		return helper!(method, T)(value);
3440 	}
3441 }
3442 
3443 /++
3444 	Implementation detail of the [ControlledBy] UDA.
3445 
3446 	History:
3447 		Added Oct 28, 2020
3448 +/
3449 struct ControlledBy_(T, Args...) {
3450 	Args args;
3451 
3452 	static if(Args.length)
3453 	this(Args args) {
3454 		this.args = args;
3455 	}
3456 
3457 	private T construct(Widget parent) {
3458 		return new T(args, parent);
3459 	}
3460 }
3461 
3462 /++
3463 	User-defined attribute you can add to struct members contrlled by [addDataControllerWidget] or [dialog] to tell which widget you want created for them.
3464 
3465 	History:
3466 		Added Oct 28, 2020
3467 +/
3468 auto ControlledBy(T, Args...)(Args args) {
3469 	return ControlledBy_!(T, Args)(args);
3470 }
3471 
3472 struct ContainerMeta {
3473 	string name;
3474 	ContainerMeta[] children;
3475 	Widget function(Widget parent) factory;
3476 
3477 	Widget instantiate(Widget parent) {
3478 		auto n = factory(parent);
3479 		n.name = name;
3480 		foreach(child; children)
3481 			child.instantiate(n);
3482 		return n;
3483 	}
3484 }
3485 
3486 /++
3487 	This is a helper for [addDataControllerWidget]. You can use it as a UDA on the type. See
3488 	http://dpldocs.info/this-week-in-d/Blog.Posted_2020_11_02.html for more information.
3489 
3490 	Please note that as of May 28, 2021, a dmd bug prevents this from compiling on module-level
3491 	structures. It works fine on structs declared inside functions though.
3492 
3493 	See: https://issues.dlang.org/show_bug.cgi?id=21984
3494 +/
3495 template Container(CArgs...) {
3496 	static if(CArgs.length && is(CArgs[0] : Widget)) {
3497 		private alias Super = CArgs[0];
3498 		private alias CArgs2 = CArgs[1 .. $];
3499 	} else {
3500 		private alias Super = Layout;
3501 		private alias CArgs2 = CArgs;
3502 	}
3503 
3504 	class Container : Super {
3505 		this(Widget parent) { super(parent); }
3506 
3507 		// just to partially support old gdc versions
3508 		version(GNU) {
3509 			static if(CArgs2.length >= 1) { enum tmp0 = CArgs2[0]; mixin typeof(tmp0).MethodOverride!(CArgs2[0]); }
3510 			static if(CArgs2.length >= 2) { enum tmp1 = CArgs2[1]; mixin typeof(tmp1).MethodOverride!(CArgs2[1]); }
3511 			static if(CArgs2.length >= 3) { enum tmp2 = CArgs2[2]; mixin typeof(tmp2).MethodOverride!(CArgs2[2]); }
3512 			static if(CArgs2.length > 3) static assert(0, "only a few overrides like this supported on your compiler version at this time");
3513 		} else mixin(q{
3514 			static foreach(Arg; CArgs2) {
3515 				mixin Arg.MethodOverride!(Arg);
3516 			}
3517 		});
3518 
3519 		static ContainerMeta opCall(string name, ContainerMeta[] children...) {
3520 			return ContainerMeta(
3521 				name,
3522 				children.dup,
3523 				function (Widget parent) { return new typeof(this)(parent); }
3524 			);
3525 		}
3526 
3527 		static ContainerMeta opCall(ContainerMeta[] children...) {
3528 			return opCall(null, children);
3529 		}
3530 	}
3531 }
3532 
3533 /++
3534 	The data controller widget is created by reflecting over the given
3535 	data type. You can use [ControlledBy] as a UDA on a struct or
3536 	just let it create things automatically.
3537 
3538 	Unlike [dialog], this uses real-time updating of the data and
3539 	you add it to another window yourself.
3540 
3541 	---
3542 		struct Test {
3543 			int x;
3544 			int y;
3545 		}
3546 
3547 		auto window = new Window();
3548 		auto dcw = new DataControllerWidget!Test(new Test, window);
3549 	---
3550 
3551 	The way it works is any public members are given a widget based
3552 	on their data type, and public methods trigger an action button
3553 	if no relevant parameters or a dialog action if it does have
3554 	parameters, similar to the [menu] facility.
3555 
3556 	If you change data programmatically, without going through the
3557 	DataControllerWidget methods, you will have to tell it something
3558 	has changed and it needs to redraw. This is done with the `invalidate`
3559 	method.
3560 
3561 	History:
3562 		Added Oct 28, 2020
3563 +/
3564 /// Group: generating_from_code
3565 class DataControllerWidget(T) : WidgetContainer {
3566 	static if(is(T == class) || is(T : const E[], E))
3567 		private alias Tref = T;
3568 	else
3569 		private alias Tref = T*;
3570 
3571 	Tref datum;
3572 
3573 	/++
3574 		See_also: [addDataControllerWidget]
3575 	+/
3576 	this(Tref datum, Widget parent) {
3577 		this.datum = datum;
3578 
3579 		Widget cp = this;
3580 
3581 		super(parent);
3582 
3583 		foreach(attr; __traits(getAttributes, T))
3584 			static if(is(typeof(attr) == ContainerMeta)) {
3585 				cp = attr.instantiate(this);
3586 			}
3587 
3588 		auto def = this.getByName("default");
3589 		if(def !is null)
3590 			cp = def;
3591 
3592 		Widget helper(string name) {
3593 			auto maybe = this.getByName(name);
3594 			if(maybe is null)
3595 				return cp;
3596 			return maybe;
3597 
3598 		}
3599 
3600 		foreach(member; __traits(allMembers, T))
3601 		static if(member != "this") // wtf https://issues.dlang.org/show_bug.cgi?id=22011
3602 		static if(__traits(getProtection, __traits(getMember, this.datum, member)) == "public") {
3603 			void delegate() update;
3604 
3605 			auto w = widgetFor!(__traits(getMember, T, member))(&__traits(getMember, this.datum, member), helper(member), update);
3606 
3607 			if(update)
3608 				updaters ~= update;
3609 
3610 			static if(is(typeof(__traits(getMember, this.datum, member)) == function)) {
3611 				w.addEventListener("triggered", delegate() {
3612 					makeAutomaticHandler!(__traits(getMember, this.datum, member))(&__traits(getMember, this.datum, member))();
3613 					notifyDataUpdated();
3614 				});
3615 			} else static if(is(typeof(w.isChecked) == bool)) {
3616 				w.addEventListener(EventType.change, (Event ev) {
3617 					__traits(getMember, this.datum, member) = w.isChecked;
3618 				});
3619 			} else static if(is(typeof(w.value) == string) || is(typeof(w.content) == string)) {
3620 				w.addEventListener("change", (Event e) { genericSetValue(&__traits(getMember, this.datum, member), e.stringValue); } );
3621 			} else static if(is(typeof(w.value) == int)) {
3622 				w.addEventListener("change", (Event e) { genericSetValue(&__traits(getMember, this.datum, member), e.intValue); } );
3623 			} else static if(is(typeof(w) == DropDownSelection)) {
3624 				// special case for this to kinda support enums and such. coudl be better though
3625 				w.addEventListener("change", (Event e) { genericSetValue(&__traits(getMember, this.datum, member), e.intValue); } );
3626 			} else {
3627 				static assert(0, "unsupported type " ~ typeof(__traits(getMember, this.datum, member)).stringof ~ " " ~ typeof(w).stringof);
3628 			}
3629 		}
3630 	}
3631 
3632 	/++
3633 		If you modify the data in the structure directly, you need to call this to update the UI and propagate any change messages.
3634 
3635 		History:
3636 			Added May 28, 2021
3637 	+/
3638 	void notifyDataUpdated() {
3639 		foreach(updater; updaters)
3640 			updater();
3641 
3642 		this.emit!(ChangeEvent!void)(delegate{});
3643 	}
3644 
3645 	private Widget[string] memberWidgets;
3646 	private void delegate()[] updaters;
3647 
3648 	mixin Emits!(ChangeEvent!void);
3649 }
3650 
3651 private int saturatedSum(int[] values...) {
3652 	int sum;
3653 	foreach(value; values) {
3654 		if(value == int.max)
3655 			return int.max;
3656 		sum += value;
3657 	}
3658 	return sum;
3659 }
3660 
3661 void genericSetValue(T, W)(T* where, W what) {
3662 	import std.conv;
3663 	*where = to!T(what);
3664 	//*where = cast(T) stringToLong(what);
3665 }
3666 
3667 /++
3668 	Creates a widget for the value `tt`, which is pointed to at runtime by `valptr`, with the given parent.
3669 
3670 	The `update` delegate can be called if you change `*valptr` to reflect those changes in the widget.
3671 
3672 	Note that this creates the widget but does not attach any event handlers to it.
3673 +/
3674 private static auto widgetFor(alias tt, P)(P valptr, Widget parent, out void delegate() update) {
3675 
3676 	string displayName = __traits(identifier, tt).beautify;
3677 
3678 	static if(controlledByCount!tt == 1) {
3679 		foreach(i, attr; __traits(getAttributes, tt)) {
3680 			static if(is(typeof(attr) == ControlledBy_!(T, Args), T, Args...)) {
3681 				auto w = attr.construct(parent);
3682 				static if(__traits(compiles, w.setPosition(*valptr)))
3683 					update = () { w.setPosition(*valptr); };
3684 				else static if(__traits(compiles, w.setValue(*valptr)))
3685 					update = () { w.setValue(*valptr); };
3686 
3687 				if(update)
3688 					update();
3689 				return w;
3690 			}
3691 		}
3692 	} else static if(controlledByCount!tt == 0) {
3693 		static if(is(typeof(tt) == enum)) {
3694 			// FIXME: update
3695 			auto dds = new DropDownSelection(parent);
3696 			foreach(idx, option; __traits(allMembers, typeof(tt))) {
3697 				dds.addOption(option);
3698 				if(__traits(getMember, typeof(tt), option) == *valptr)
3699 					dds.setSelection(cast(int) idx);
3700 			}
3701 			return dds;
3702 		} else static if(is(typeof(tt) == bool)) {
3703 			auto box = new Checkbox(displayName, parent);
3704 			update = () { box.isChecked = *valptr; };
3705 			update();
3706 			return box;
3707 		} else static if(is(typeof(tt) : const long)) {
3708 			auto le = new LabeledLineEdit(displayName, parent);
3709 			update = () { le.content = toInternal!string(*valptr); };
3710 			update();
3711 			return le;
3712 		} else static if(is(typeof(tt) : const string)) {
3713 			auto le = new LabeledLineEdit(displayName, parent);
3714 			update = () { le.content = *valptr; };
3715 			update();
3716 			return le;
3717 		} else static if(is(typeof(tt) == function)) {
3718 			auto w = new Button(displayName, parent);
3719 			return w;
3720 		}
3721 	} else static assert(0, "multiple controllers not yet supported");
3722 }
3723 
3724 private template controlledByCount(alias tt) {
3725 	static int helper() {
3726 		int count;
3727 		foreach(i, attr; __traits(getAttributes, tt))
3728 			static if(is(typeof(attr) == ControlledBy_!(T, Args), T, Args...))
3729 				count++;
3730 		return count;
3731 	}
3732 
3733 	enum controlledByCount = helper;
3734 }
3735 
3736 /++
3737 	Intended for UFCS action like `window.addDataControllerWidget(new MyObject());`
3738 
3739 	If you provide a `redrawOnChange` widget, it will automatically register a change event handler that calls that widget's redraw method.
3740 
3741 	History:
3742 		The `redrawOnChange` parameter was added on May 28, 2021.
3743 +/
3744 DataControllerWidget!T addDataControllerWidget(T)(Widget parent, T t, Widget redrawOnChange = null) if(is(T == class)) {
3745 	auto dcw = new DataControllerWidget!T(t, parent);
3746 	initializeDataControllerWidget(dcw, redrawOnChange);
3747 	return dcw;
3748 }
3749 
3750 /// ditto
3751 DataControllerWidget!T addDataControllerWidget(T)(Widget parent, T* t, Widget redrawOnChange = null) if(is(T == struct)) {
3752 	auto dcw = new DataControllerWidget!T(t, parent);
3753 	initializeDataControllerWidget(dcw, redrawOnChange);
3754 	return dcw;
3755 }
3756 
3757 private void initializeDataControllerWidget(Widget w, Widget redrawOnChange) {
3758 	if(redrawOnChange !is null)
3759 		w.addEventListener("change", delegate() { redrawOnChange.redraw(); });
3760 }
3761 
3762 /++
3763 	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.
3764 
3765 	History:
3766 		Finalized on June 3, 2021 for the dub v10.0 release
3767 +/
3768 struct StyleInformation {
3769 	private Widget w;
3770 	private BaseVisualTheme visualTheme;
3771 
3772 	private this(Widget w) {
3773 		this.w = w;
3774 		this.visualTheme = WidgetPainter.visualTheme;
3775 	}
3776 
3777 	/// Forwards to [Widget.Style]
3778 	// through the [VisualTheme]
3779 	public @property opDispatch(string name)() {
3780 		typeof(__traits(getMember, Widget.Style.init, name)()) prop;
3781 		w.useStyleProperties((scope Widget.Style props) {
3782 		//visualTheme.useStyleProperties(w, (props) {
3783 			prop = __traits(getMember, props, name);
3784 		});
3785 		return prop;
3786 	}
3787 
3788 	@property {
3789 		// Layout helpers. Currently just forwarding since I haven't made up my mind on a better way.
3790 		/** */ int paddingLeft() { return w.paddingLeft(); }
3791 		/** */ int paddingRight() { return w.paddingRight(); }
3792 		/** */ int paddingTop() { return w.paddingTop(); }
3793 		/** */ int paddingBottom() { return w.paddingBottom(); }
3794 
3795 		/** */ int marginLeft() { return w.marginLeft(); }
3796 		/** */ int marginRight() { return w.marginRight(); }
3797 		/** */ int marginTop() { return w.marginTop(); }
3798 		/** */ int marginBottom() { return w.marginBottom(); }
3799 
3800 		/** */ int maxHeight() { return w.maxHeight(); }
3801 		/** */ int minHeight() { return w.minHeight(); }
3802 
3803 		/** */ int maxWidth() { return w.maxWidth(); }
3804 		/** */ int minWidth() { return w.minWidth(); }
3805 
3806 		/** */ int flexBasisWidth() { return w.flexBasisWidth(); }
3807 		/** */ int flexBasisHeight() { return w.flexBasisHeight(); }
3808 
3809 		/** */ int heightStretchiness() { return w.heightStretchiness(); }
3810 		/** */ int widthStretchiness() { return w.widthStretchiness(); }
3811 
3812 		/** */ int heightShrinkiness() { return w.heightShrinkiness(); }
3813 		/** */ int widthShrinkiness() { return w.widthShrinkiness(); }
3814 
3815 		// Global helpers some of these are unstable.
3816 		static:
3817 		/** */ Color windowBackgroundColor() { return WidgetPainter.visualTheme.windowBackgroundColor(); }
3818 		/** */ Color widgetBackgroundColor() { return WidgetPainter.visualTheme.widgetBackgroundColor(); }
3819 		/** */ Color lightAccentColor() { return WidgetPainter.visualTheme.lightAccentColor(); }
3820 		/** */ Color darkAccentColor() { return WidgetPainter.visualTheme.darkAccentColor(); }
3821 
3822 		/** */ Color activeTabColor() { return lightAccentColor; }
3823 		/** */ Color buttonColor() { return windowBackgroundColor; }
3824 		/** */ Color depressedButtonColor() { return darkAccentColor; }
3825 		/** */ Color hoveringColor() { return Color(228, 228, 228); }
3826 		/** */ Color activeListXorColor() {
3827 			auto c = WidgetPainter.visualTheme.selectionColor();
3828 			return Color(c.r ^ 255, c.g ^ 255, c.b ^ 255, c.a);
3829 		}
3830 		/** */ Color progressBarColor() { return WidgetPainter.visualTheme.selectionColor(); }
3831 		/** */ Color activeMenuItemColor() { return WidgetPainter.visualTheme.selectionColor(); }
3832 	}
3833 
3834 
3835 
3836 	/+
3837 
3838 	private static auto extractStyleProperty(string name)(Widget w) {
3839 		typeof(__traits(getMember, Widget.Style.init, name)()) prop;
3840 		w.useStyleProperties((props) {
3841 			prop = __traits(getMember, props, name);
3842 		});
3843 		return prop;
3844 	}
3845 
3846 	// FIXME: clear this upon a X server disconnect
3847 	private static OperatingSystemFont[string] fontCache;
3848 
3849 	T getProperty(T)(string name, lazy T default_) {
3850 		if(visualTheme !is null) {
3851 			auto str = visualTheme.getPropertyString(w, name);
3852 			if(str is null)
3853 				return default_;
3854 			static if(is(T == Color))
3855 				return Color.fromString(str);
3856 			else static if(is(T == Measurement))
3857 				return Measurement(cast(int) toInternal!int(str));
3858 			else static if(is(T == WidgetBackground))
3859 				return WidgetBackground.fromString(str);
3860 			else static if(is(T == OperatingSystemFont)) {
3861 				if(auto f = str in fontCache)
3862 					return *f;
3863 				else
3864 					return fontCache[str] = new OperatingSystemFont(str);
3865 			} else static if(is(T == FrameStyle)) {
3866 				switch(str) {
3867 					default:
3868 						return FrameStyle.none;
3869 					foreach(style; __traits(allMembers, FrameStyle))
3870 					case style:
3871 						return __traits(getMember, FrameStyle, style);
3872 				}
3873 			} else static assert(0);
3874 		} else
3875 			return default_;
3876 	}
3877 
3878 	static struct Measurement {
3879 		int value;
3880 		alias value this;
3881 	}
3882 
3883 	@property:
3884 
3885 	int paddingLeft() { return getProperty("padding-left", Measurement(w.paddingLeft())); }
3886 	int paddingRight() { return getProperty("padding-right", Measurement(w.paddingRight())); }
3887 	int paddingTop() { return getProperty("padding-top", Measurement(w.paddingTop())); }
3888 	int paddingBottom() { return getProperty("padding-bottom", Measurement(w.paddingBottom())); }
3889 
3890 	int marginLeft() { return getProperty("margin-left", Measurement(w.marginLeft())); }
3891 	int marginRight() { return getProperty("margin-right", Measurement(w.marginRight())); }
3892 	int marginTop() { return getProperty("margin-top", Measurement(w.marginTop())); }
3893 	int marginBottom() { return getProperty("margin-bottom", Measurement(w.marginBottom())); }
3894 
3895 	int maxHeight() { return getProperty("max-height", Measurement(w.maxHeight())); }
3896 	int minHeight() { return getProperty("min-height", Measurement(w.minHeight())); }
3897 
3898 	int maxWidth() { return getProperty("max-width", Measurement(w.maxWidth())); }
3899 	int minWidth() { return getProperty("min-width", Measurement(w.minWidth())); }
3900 
3901 
3902 	WidgetBackground background() { return getProperty("background", extractStyleProperty!"background"(w)); }
3903 	Color foregroundColor() { return getProperty("foreground-color", extractStyleProperty!"foregroundColor"(w)); }
3904 
3905 	OperatingSystemFont font() { return getProperty("font", extractStyleProperty!"fontCached"(w)); }
3906 
3907 	FrameStyle borderStyle() { return getProperty("border-style", extractStyleProperty!"borderStyle"(w)); }
3908 	Color borderColor() { return getProperty("border-color", extractStyleProperty!"borderColor"(w)); }
3909 
3910 	FrameStyle outlineStyle() { return getProperty("outline-style", extractStyleProperty!"outlineStyle"(w)); }
3911 	Color outlineColor() { return getProperty("outline-color", extractStyleProperty!"outlineColor"(w)); }
3912 
3913 
3914 	Color windowBackgroundColor() { return WidgetPainter.visualTheme.windowBackgroundColor(); }
3915 	Color widgetBackgroundColor() { return WidgetPainter.visualTheme.widgetBackgroundColor(); }
3916 	Color lightAccentColor() { return WidgetPainter.visualTheme.lightAccentColor(); }
3917 	Color darkAccentColor() { return WidgetPainter.visualTheme.darkAccentColor(); }
3918 
3919 	Color activeTabColor() { return lightAccentColor; }
3920 	Color buttonColor() { return windowBackgroundColor; }
3921 	Color depressedButtonColor() { return darkAccentColor; }
3922 	Color hoveringColor() { return Color(228, 228, 228); }
3923 	Color activeListXorColor() {
3924 		auto c = WidgetPainter.visualTheme.selectionColor();
3925 		return Color(c.r ^ 255, c.g ^ 255, c.b ^ 255, c.a);
3926 	}
3927 	Color progressBarColor() { return WidgetPainter.visualTheme.selectionColor(); }
3928 	Color activeMenuItemColor() { return WidgetPainter.visualTheme.selectionColor(); }
3929 	+/
3930 }
3931 
3932 
3933 
3934 // pragma(msg, __traits(classInstanceSize, Widget));
3935 
3936 /*private*/ template EventString(E) {
3937 	static if(is(typeof(E.EventString)))
3938 		enum EventString = E.EventString;
3939 	else
3940 		enum EventString = E.mangleof; // FIXME fqn? or something more user friendly
3941 }
3942 
3943 /*private*/ template EventStringIdentifier(E) {
3944 	string helper() {
3945 		auto es = EventString!E;
3946 		char[] id = new char[](es.length * 2);
3947 		size_t idx;
3948 		foreach(char ch; es) {
3949 			id[idx++] = cast(char)('a' + (ch >> 4));
3950 			id[idx++] = cast(char)('a' + (ch & 0x0f));
3951 		}
3952 		return cast(string) id;
3953 	}
3954 
3955 	enum EventStringIdentifier = helper();
3956 }
3957 
3958 
3959 template classStaticallyEmits(This, EventType) {
3960 	static if(is(This Base == super))
3961 		static if(is(Base : Widget))
3962 			enum baseEmits = classStaticallyEmits!(Base, EventType);
3963 		else
3964 			enum baseEmits = false;
3965 	else
3966 		enum baseEmits = false;
3967 
3968 	enum thisEmits = is(typeof(__traits(getMember, This, "emits_" ~ EventStringIdentifier!EventType)) == EventType[0]);
3969 
3970 	enum classStaticallyEmits = thisEmits || baseEmits;
3971 }
3972 
3973 /++
3974 	A helper to make widgets out of other native windows.
3975 
3976 	History:
3977 		Factored out of OpenGlWidget on November 5, 2021
3978 +/
3979 class NestedChildWindowWidget : Widget {
3980 	SimpleWindow win;
3981 
3982 	///
3983 	// win = new SimpleWindow(640, 480, null, OpenGlOptions.yes, Resizability.automaticallyScaleIfPossible, WindowTypes.nestedChild, WindowFlags.normal, getParentWindow(parent));
3984 	this(SimpleWindow win, Widget parent) {
3985 		this.parentWindow = parent.parentWindow;
3986 		this.win = win;
3987 
3988 		super(parent);
3989 		windowsetup(win);
3990 	}
3991 
3992 	static protected SimpleWindow getParentWindow(Widget parent) {
3993 		assert(parent !is null);
3994 		SimpleWindow pwin = parent.parentWindow.win;
3995 
3996 		version(win32_widgets) {
3997 			HWND phwnd;
3998 			auto wtf = parent;
3999 			while(wtf) {
4000 				if(wtf.hwnd) {
4001 					phwnd = wtf.hwnd;
4002 					break;
4003 				}
4004 				wtf = wtf.parent;
4005 			}
4006 			// kinda a hack here just because the ctor below just needs a SimpleWindow wrapper....
4007 			if(phwnd)
4008 				pwin = new SimpleWindow(phwnd);
4009 		}
4010 
4011 		return pwin;
4012 	}
4013 
4014 	protected void windowsetup(SimpleWindow w) {
4015 		/*
4016 		win.onFocusChange = (bool getting) {
4017 			if(getting)
4018 				this.focus();
4019 		};
4020 		*/
4021 
4022 		version(win32_widgets) {
4023 			Widget.nativeMapping[win.hwnd] = this;
4024 			this.originalWindowProcedure = cast(WNDPROC) SetWindowLongPtr(win.hwnd, GWL_WNDPROC, cast(size_t) &HookedWndProc);
4025 		} else {
4026 			win.setEventHandlers(
4027 				(MouseEvent e) {
4028 					Widget p = this;
4029 					while(p ! is parentWindow) {
4030 						e.x += p.x;
4031 						e.y += p.y;
4032 						p = p.parent;
4033 					}
4034 					parentWindow.dispatchMouseEvent(e);
4035 				},
4036 				(KeyEvent e) {
4037 					//import std.stdio;
4038 					//writefln("%x   %s", cast(uint) e.key, e.key);
4039 					parentWindow.dispatchKeyEvent(e);
4040 				},
4041 				(dchar e) {
4042 					parentWindow.dispatchCharEvent(e);
4043 				},
4044 			);
4045 		}
4046 
4047 	}
4048 
4049 	override void showing(bool s, bool recalc) {
4050 		auto cur = hidden;
4051 		win.hidden = !s;
4052 		if(cur != s && s)
4053 			redraw();
4054 	}
4055 
4056 	/// OpenGL widgets cannot have child widgets. Do not call this.
4057 	/* @disable */ final override void addChild(Widget, int) {
4058 		throw new Error("cannot add children to OpenGL widgets");
4059 	}
4060 
4061 	/// When an opengl widget is laid out, it will adjust the glViewport for you automatically.
4062 	/// Keep in mind that events like mouse coordinates are still relative to your size.
4063 	override void registerMovement() {
4064 		//import std.stdio; writefln("%d %d %d %d", x,y,width,height);
4065 		version(win32_widgets)
4066 			auto pos = getChildPositionRelativeToParentHwnd(this);
4067 		else
4068 			auto pos = getChildPositionRelativeToParentOrigin(this);
4069 		win.moveResize(pos[0], pos[1], width, height);
4070 
4071 		registerMovementAdditionalWork();
4072 		sendResizeEvent();
4073 	}
4074 
4075 	abstract void registerMovementAdditionalWork();
4076 }
4077 
4078 /++
4079 	Nests an opengl capable window inside this window as a widget.
4080 
4081 	You may also just want to create an additional [SimpleWindow] with
4082 	[OpenGlOptions.yes] yourself.
4083 
4084 	An OpenGL widget cannot have child widgets. It will throw if you try.
4085 +/
4086 static if(OpenGlEnabled)
4087 class OpenGlWidget : NestedChildWindowWidget {
4088 
4089 	override void registerMovementAdditionalWork() {
4090 		win.setAsCurrentOpenGlContext();
4091 	}
4092 
4093 	///
4094 	this(Widget parent) {
4095 		auto win = new SimpleWindow(640, 480, null, OpenGlOptions.yes, Resizability.automaticallyScaleIfPossible, WindowTypes.nestedChild, WindowFlags.normal, getParentWindow(parent));
4096 		super(win, parent);
4097 	}
4098 
4099 	override void paint(WidgetPainter painter) {
4100 		glViewport(0, 0, this.width, this.height);
4101 		win.redrawOpenGlSceneNow();
4102 	}
4103 
4104 	void redrawOpenGlScene(void delegate() dg) {
4105 		win.redrawOpenGlScene = dg;
4106 	}
4107 }
4108 
4109 version(custom_widgets)
4110 	private alias ListWidgetBase = ScrollableWidget;
4111 else
4112 	private alias ListWidgetBase = Widget;
4113 
4114 /++
4115 	A list widget contains a list of strings that the user can examine and select.
4116 
4117 
4118 	In the future, items in the list may be possible to be more than just strings.
4119 
4120 	See_Also:
4121 		[TableView]
4122 +/
4123 class ListWidget : ListWidgetBase {
4124 	/// 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.
4125 	mixin Emits!(ChangeEvent!void);
4126 
4127 	static struct Option {
4128 		string label;
4129 		bool selected;
4130 	}
4131 
4132 	/++
4133 		Sets the current selection to the `y`th item in the list. Will emit [ChangeEvent] when complete.
4134 	+/
4135 	void setSelection(int y) {
4136 		if(!multiSelect)
4137 			foreach(ref opt; options)
4138 				opt.selected = false;
4139 		if(y >= 0 && y < options.length)
4140 			options[y].selected = !options[y].selected;
4141 
4142 		this.emit!(ChangeEvent!void)(delegate {});
4143 
4144 		version(custom_widgets)
4145 			redraw();
4146 	}
4147 
4148 	version(custom_widgets)
4149 	override void defaultEventHandler_click(ClickEvent event) {
4150 		this.focus();
4151 		auto y = (event.clientY - 4) / defaultLineHeight;
4152 		if(y >= 0 && y < options.length) {
4153 			setSelection(y);
4154 		}
4155 		super.defaultEventHandler_click(event);
4156 	}
4157 
4158 	this(Widget parent) {
4159 		tabStop = false;
4160 		super(parent);
4161 		version(win32_widgets)
4162 			createWin32Window(this, WC_LISTBOX, "", 
4163 				0|WS_CHILD|WS_VISIBLE|LBS_NOTIFY, 0);
4164 	}
4165 
4166 	version(win32_widgets)
4167 	override void handleWmCommand(ushort code, ushort id) {
4168 		switch(code) {
4169 			case LBN_SELCHANGE:
4170 				auto sel = SendMessageW(hwnd, LB_GETCURSEL, 0, 0);
4171 				setSelection(cast(int) sel);
4172 			break;
4173 			default:
4174 		}
4175 	}
4176 
4177 
4178 	version(custom_widgets)
4179 	override void paintFrameAndBackground(WidgetPainter painter) {
4180 		draw3dFrame(this, painter, FrameStyle.sunk, Color.white);
4181 	}
4182 
4183 	version(custom_widgets)
4184 	override void paint(WidgetPainter painter) {
4185 		auto cs = getComputedStyle();
4186 		auto pos = Point(4, 4);
4187 		foreach(idx, option; options) {
4188 			painter.fillColor = Color.white;
4189 			painter.outlineColor = Color.white;
4190 			painter.drawRectangle(pos, width - 8, defaultLineHeight);
4191 			painter.outlineColor = cs.foregroundColor;
4192 			painter.drawText(pos, option.label);
4193 			if(option.selected) {
4194 				painter.rasterOp = RasterOp.xor;
4195 				painter.outlineColor = Color.white;
4196 				painter.fillColor = cs.activeListXorColor;
4197 				painter.drawRectangle(pos, width - 8, defaultLineHeight);
4198 				painter.rasterOp = RasterOp.normal;
4199 			}
4200 			pos.y += defaultLineHeight;
4201 		}
4202 	}
4203 
4204 	static class Style : Widget.Style {
4205 		override WidgetBackground background() {
4206 			return WidgetBackground(WidgetPainter.visualTheme.widgetBackgroundColor);
4207 		}
4208 	}
4209 	mixin OverrideStyle!Style;
4210 	//mixin Padding!q{2};
4211 
4212 	void addOption(string text) {
4213 		options ~= Option(text);
4214 		version(win32_widgets) {
4215 			WCharzBuffer buffer = WCharzBuffer(text);
4216 			SendMessageW(hwnd, LB_ADDSTRING, 0, cast(LPARAM) buffer.ptr);
4217 		}
4218 		version(custom_widgets) {
4219 			setContentSize(width, cast(int) (options.length * defaultLineHeight));
4220 			redraw();
4221 		}
4222 	}
4223 
4224 	void clear() {
4225 		options = null;
4226 		version(win32_widgets) {
4227 			while(SendMessageW(hwnd, LB_DELETESTRING, 0, 0) > 0)
4228 				{}
4229 
4230 		} else version(custom_widgets) {
4231 			redraw();
4232 		}
4233 	}
4234 
4235 	Option[] options;
4236 	version(win32_widgets)
4237 		enum multiSelect = false; /// not implemented yet
4238 	else
4239 		bool multiSelect;
4240 
4241 	override int heightStretchiness() { return 6; }
4242 }
4243 
4244 
4245 
4246 /// For [ScrollableWidget], determines when to show the scroll bar to the user.
4247 enum ScrollBarShowPolicy {
4248 	automatic, /// automatically show the scroll bar if it is necessary
4249 	never, /// never show the scroll bar (scrolling must be done programmatically)
4250 	always /// always show the scroll bar, even if it is disabled
4251 }
4252 
4253 /++
4254 	A widget that tries (with, at best, limited success) to offer scrolling that is transparent to the inner.
4255 
4256 	It isn't very good and will very likely be removed. Try [ScrollMessageWidget] or [ScrollableContainerWidget] instead for new code.
4257 +/
4258 // FIXME ScrollBarShowPolicy
4259 // FIXME: use the ScrollMessageWidget in here now that it exists
4260 class ScrollableWidget : Widget {
4261 	// FIXME: make line size configurable
4262 	// FIXME: add keyboard controls
4263 	version(win32_widgets) {
4264 		override int hookedWndProc(UINT msg, WPARAM wParam, LPARAM lParam) {
4265 			if(msg == WM_VSCROLL || msg == WM_HSCROLL) {
4266 				auto pos = HIWORD(wParam);
4267 				auto m = LOWORD(wParam);
4268 
4269 				// FIXME: I can reintroduce the
4270 				// scroll bars now by using this
4271 				// in the top-level window handler
4272 				// to forward comamnds
4273 				auto scrollbarHwnd = lParam;
4274 				switch(m) {
4275 					case SB_BOTTOM:
4276 						if(msg == WM_HSCROLL)
4277 							horizontalScrollTo(contentWidth_);
4278 						else
4279 							verticalScrollTo(contentHeight_);
4280 					break;
4281 					case SB_TOP:
4282 						if(msg == WM_HSCROLL)
4283 							horizontalScrollTo(0);
4284 						else
4285 							verticalScrollTo(0);
4286 					break;
4287 					case SB_ENDSCROLL:
4288 						// idk
4289 					break;
4290 					case SB_LINEDOWN:
4291 						if(msg == WM_HSCROLL)
4292 							horizontalScroll(16);
4293 						else
4294 							verticalScroll(16);
4295 					break;
4296 					case SB_LINEUP:
4297 						if(msg == WM_HSCROLL)
4298 							horizontalScroll(-16);
4299 						else
4300 							verticalScroll(-16);
4301 					break;
4302 					case SB_PAGEDOWN:
4303 						if(msg == WM_HSCROLL)
4304 							horizontalScroll(100);
4305 						else
4306 							verticalScroll(100);
4307 					break;
4308 					case SB_PAGEUP:
4309 						if(msg == WM_HSCROLL)
4310 							horizontalScroll(-100);
4311 						else
4312 							verticalScroll(-100);
4313 					break;
4314 					case SB_THUMBPOSITION:
4315 					case SB_THUMBTRACK:
4316 						if(msg == WM_HSCROLL)
4317 							horizontalScrollTo(pos);
4318 						else
4319 							verticalScrollTo(pos);
4320 
4321 						if(m == SB_THUMBTRACK) {
4322 							// the event loop doesn't seem to carry on with a requested redraw..
4323 							// so we request it to get our dirty bit set...
4324 							redraw();
4325 
4326 							// then we need to immediately actually redraw it too for instant feedback to user
4327 							if(parentWindow)
4328 								parentWindow.actualRedraw();
4329 						}
4330 					break;
4331 					default:
4332 				}
4333 			}
4334 			return super.hookedWndProc(msg, wParam, lParam);
4335 		}
4336 	}
4337 	///
4338 	this(Widget parent) {
4339 		this.parentWindow = parent.parentWindow;
4340 
4341 		version(win32_widgets) {
4342 			createWin32Window(this, Win32Class!"arsd_minigui_ScrollableWidget"w, "", 
4343 				0|WS_CHILD|WS_VISIBLE|WS_HSCROLL|WS_VSCROLL, 0);
4344 			super(parent);
4345 		} else version(custom_widgets) {
4346 			outerContainer = new InternalScrollableContainerWidget(this, parent);
4347 			super(outerContainer);
4348 		} else static assert(0);
4349 	}
4350 
4351 	version(custom_widgets)
4352 		InternalScrollableContainerWidget outerContainer;
4353 
4354 	override void defaultEventHandler_click(ClickEvent event) {
4355 		if(event.button == MouseButton.wheelUp)
4356 			verticalScroll(-16);
4357 		if(event.button == MouseButton.wheelDown)
4358 			verticalScroll(16);
4359 		super.defaultEventHandler_click(event);
4360 	}
4361 
4362 	override void defaultEventHandler_keydown(KeyDownEvent event) {
4363 		switch(event.key) {
4364 			case Key.Left:
4365 				horizontalScroll(-16);
4366 			break;
4367 			case Key.Right:
4368 				horizontalScroll(16);
4369 			break;
4370 			case Key.Up:
4371 				verticalScroll(-16);
4372 			break;
4373 			case Key.Down:
4374 				verticalScroll(16);
4375 			break;
4376 			case Key.Home:
4377 				verticalScrollTo(0);
4378 			break;
4379 			case Key.End:
4380 				verticalScrollTo(contentHeight);
4381 			break;
4382 			case Key.PageUp:
4383 				verticalScroll(-160);
4384 			break;
4385 			case Key.PageDown:
4386 				verticalScroll(160);
4387 			break;
4388 			default:
4389 		}
4390 		super.defaultEventHandler_keydown(event);
4391 	}
4392 
4393 
4394 	version(win32_widgets)
4395 	override void recomputeChildLayout() {
4396 		super.recomputeChildLayout();
4397 		SCROLLINFO info;
4398 		info.cbSize = info.sizeof;
4399 		info.nPage = viewportHeight;
4400 		info.fMask = SIF_PAGE | SIF_RANGE;
4401 		info.nMin = 0;
4402 		info.nMax = contentHeight_;
4403 		SetScrollInfo(hwnd, SB_VERT, &info, true);
4404 
4405 		info.cbSize = info.sizeof;
4406 		info.nPage = viewportWidth;
4407 		info.fMask = SIF_PAGE | SIF_RANGE;
4408 		info.nMin = 0;
4409 		info.nMax = contentWidth_;
4410 		SetScrollInfo(hwnd, SB_HORZ, &info, true);
4411 	}
4412 
4413 	/*
4414 		Scrolling
4415 		------------
4416 
4417 		You are assigned a width and a height by the layout engine, which
4418 		is your viewport box. However, you may draw more than that by setting
4419 		a contentWidth and contentHeight.
4420 
4421 		If these can be contained by the viewport, no scrollbar is displayed.
4422 		If they cannot fit though, it will automatically show scroll as necessary.
4423 
4424 		If contentWidth == 0, no horizontal scrolling is performed. If contentHeight
4425 		is zero, no vertical scrolling is performed.
4426 
4427 		If scrolling is necessary, the lib will automatically work with the bars.
4428 		When you redraw, the origin and clipping info in the painter is set so if
4429 		you just draw everything, it will work, but you can be more efficient by checking
4430 		the viewportWidth, viewportHeight, and scrollOrigin members.
4431 	*/
4432 
4433 	///
4434 	final @property int viewportWidth() {
4435 		return width - (showingVerticalScroll ? 16 : 0);
4436 	}
4437 	///
4438 	final @property int viewportHeight() {
4439 		return height - (showingHorizontalScroll ? 16 : 0);
4440 	}
4441 
4442 	// FIXME property
4443 	Point scrollOrigin_;
4444 
4445 	///
4446 	final const(Point) scrollOrigin() {
4447 		return scrollOrigin_;
4448 	}
4449 
4450 	// the user sets these two
4451 	private int contentWidth_ = 0;
4452 	private int contentHeight_ = 0;
4453 
4454 	///
4455 	int contentWidth() { return contentWidth_; }
4456 	///
4457 	int contentHeight() { return contentHeight_; }
4458 
4459 	///
4460 	void setContentSize(int width, int height) {
4461 		contentWidth_ = width;
4462 		contentHeight_ = height;
4463 
4464 		version(custom_widgets) {
4465 			if(showingVerticalScroll || showingHorizontalScroll) {
4466 				outerContainer.recomputeChildLayout();
4467 			}
4468 
4469 			if(showingVerticalScroll())
4470 				outerContainer.verticalScrollBar.redraw();
4471 			if(showingHorizontalScroll())
4472 				outerContainer.horizontalScrollBar.redraw();
4473 		} else version(win32_widgets) {
4474 			recomputeChildLayout();
4475 		} else static assert(0);
4476 	}
4477 
4478 	///
4479 	void verticalScroll(int delta) {
4480 		verticalScrollTo(scrollOrigin.y + delta);
4481 	}
4482 	///
4483 	void verticalScrollTo(int pos) {
4484 		scrollOrigin_.y = pos;
4485 		if(pos == int.max || (scrollOrigin_.y + viewportHeight > contentHeight))
4486 			scrollOrigin_.y = contentHeight - viewportHeight;
4487 
4488 		if(scrollOrigin_.y < 0)
4489 			scrollOrigin_.y = 0;
4490 
4491 		version(win32_widgets) {
4492 			SCROLLINFO info;
4493 			info.cbSize = info.sizeof;
4494 			info.fMask = SIF_POS;
4495 			info.nPos = scrollOrigin_.y;
4496 			SetScrollInfo(hwnd, SB_VERT, &info, true);
4497 		} else version(custom_widgets) {
4498 			outerContainer.verticalScrollBar.setPosition(scrollOrigin_.y);
4499 		} else static assert(0);
4500 
4501 		redraw();
4502 	}
4503 
4504 	///
4505 	void horizontalScroll(int delta) {
4506 		horizontalScrollTo(scrollOrigin.x + delta);
4507 	}
4508 	///
4509 	void horizontalScrollTo(int pos) {
4510 		scrollOrigin_.x = pos;
4511 		if(pos == int.max || (scrollOrigin_.x + viewportWidth > contentWidth))
4512 			scrollOrigin_.x = contentWidth - viewportWidth;
4513 
4514 		if(scrollOrigin_.x < 0)
4515 			scrollOrigin_.x = 0;
4516 
4517 		version(win32_widgets) {
4518 			SCROLLINFO info;
4519 			info.cbSize = info.sizeof;
4520 			info.fMask = SIF_POS;
4521 			info.nPos = scrollOrigin_.x;
4522 			SetScrollInfo(hwnd, SB_HORZ, &info, true);
4523 		} else version(custom_widgets) {
4524 			outerContainer.horizontalScrollBar.setPosition(scrollOrigin_.x);
4525 		} else static assert(0);
4526 
4527 		redraw();
4528 	}
4529 	///
4530 	void scrollTo(Point p) {
4531 		verticalScrollTo(p.y);
4532 		horizontalScrollTo(p.x);
4533 	}
4534 
4535 	///
4536 	void ensureVisibleInScroll(Point p) {
4537 		auto rect = viewportRectangle();
4538 		if(rect.contains(p))
4539 			return;
4540 		if(p.x < rect.left)
4541 			horizontalScroll(p.x - rect.left);
4542 		else if(p.x > rect.right)
4543 			horizontalScroll(p.x - rect.right);
4544 
4545 		if(p.y < rect.top)
4546 			verticalScroll(p.y - rect.top);
4547 		else if(p.y > rect.bottom)
4548 			verticalScroll(p.y - rect.bottom);
4549 	}
4550 
4551 	///
4552 	void ensureVisibleInScroll(Rectangle rect) {
4553 		ensureVisibleInScroll(rect.upperLeft);
4554 		ensureVisibleInScroll(rect.lowerRight);
4555 	}
4556 
4557 	///
4558 	Rectangle viewportRectangle() {
4559 		return Rectangle(scrollOrigin, Size(viewportWidth, viewportHeight));
4560 	}
4561 
4562 	///
4563 	bool showingHorizontalScroll() {
4564 		return contentWidth > width;
4565 	}
4566 	///
4567 	bool showingVerticalScroll() {
4568 		return contentHeight > height;
4569 	}
4570 
4571 	/// This is called before the ordinary paint delegate,
4572 	/// giving you a chance to draw the window frame, etc,
4573 	/// before the scroll clip takes effect
4574 	void paintFrameAndBackground(WidgetPainter painter) {
4575 		version(win32_widgets) {
4576 			auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
4577 			auto p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
4578 			// since the pen is null, to fill the whole space, we need the +1 on both.
4579 			gdi.Rectangle(painter.impl.hdc, 0, 0, this.width + 1, this.height + 1);
4580 			SelectObject(painter.impl.hdc, p);
4581 			SelectObject(painter.impl.hdc, b);
4582 		}
4583 
4584 	}
4585 
4586 	// make space for the scroll bar, and that's it.
4587 	final override int paddingRight() { return 16; }
4588 	final override int paddingBottom() { return 16; }
4589 
4590 	/*
4591 		END SCROLLING
4592 	*/
4593 
4594 	override WidgetPainter draw() {
4595 		int x = this.x, y = this.y;
4596 		auto parent = this.parent;
4597 		while(parent) {
4598 			x += parent.x;
4599 			y += parent.y;
4600 			parent = parent.parent;
4601 		}
4602 
4603 		//version(win32_widgets) {
4604 			//auto painter = simpleWindowWrappingHwnd ? simpleWindowWrappingHwnd.draw() : parentWindow.win.draw();
4605 		//} else {
4606 			auto painter = parentWindow.win.draw();
4607 		//}
4608 		painter.originX = x;
4609 		painter.originY = y;
4610 
4611 		painter.originX = painter.originX - scrollOrigin.x;
4612 		painter.originY = painter.originY - scrollOrigin.y;
4613 		painter.setClipRectangle(scrollOrigin, viewportWidth(), viewportHeight());
4614 
4615 		return WidgetPainter(painter, this);
4616 	}
4617 
4618 	mixin ScrollableChildren;
4619 }
4620 
4621 // you need to have a Point scrollOrigin in the class somewhere
4622 // and a paintFrameAndBackground
4623 private mixin template ScrollableChildren() {
4624 	override protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force = false) {
4625 		if(hidden)
4626 			return;
4627 
4628 		//version(win32_widgets)
4629 			//painter = simpleWindowWrappingHwnd ? simpleWindowWrappingHwnd.draw() : parentWindow.win.draw();
4630 
4631 		painter.originX = lox + x;
4632 		painter.originY = loy + y;
4633 
4634 		bool actuallyPainted = false;
4635 
4636 		const clip = containment.intersectionOf(Rectangle(Point(lox + x, loy + y), Size(width, height)));
4637 		if(clip == Rectangle.init)
4638 			return;
4639 
4640 		if(force || redrawRequested) {
4641 			//painter.setClipRectangle(scrollOrigin, width, height);
4642 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
4643 			paintFrameAndBackground(painter);
4644 		}
4645 
4646 		painter.originX = painter.originX - scrollOrigin.x;
4647 		painter.originY = painter.originY - scrollOrigin.y;
4648 		if(force || redrawRequested) {
4649 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY) + Point(2, 2) /* border */, clip.width - 4, clip.height - 4);
4650 			//painter.setClipRectangle(scrollOrigin + Point(2, 2) /* border */, width - 4, height - 4);
4651 
4652 			//erase(painter); // we paintFrameAndBackground above so no need
4653 			if(painter.visualTheme)
4654 				painter.visualTheme.doPaint(this, painter);
4655 			else
4656 				paint(painter);
4657 
4658 			actuallyPainted = true;
4659 			redrawRequested = false;
4660 		}
4661 		foreach(child; children) {
4662 			if(cast(FixedPosition) child)
4663 				child.privatePaint(painter, painter.originX + scrollOrigin.x, painter.originY + scrollOrigin.y, clip, actuallyPainted);
4664 			else
4665 				child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted);
4666 		}
4667 	}
4668 }
4669 
4670 private class InternalScrollableContainerInsideWidget : ContainerWidget {
4671 	ScrollableContainerWidget scw;
4672 
4673 	this(ScrollableContainerWidget parent) {
4674 		scw = parent;
4675 		super(parent);
4676 	}
4677 
4678 	version(custom_widgets)
4679 	override protected void privatePaint(WidgetPainter painter, int lox, int loy, Rectangle containment, bool force = false) {
4680 		if(hidden)
4681 			return;
4682 
4683 		bool actuallyPainted = false;
4684 
4685 		auto scrollOrigin = Point(scw.scrollX_, scw.scrollY_);
4686 
4687 		const clip = containment.intersectionOf(Rectangle(Point(lox + x, loy + y), Size(width, height)));
4688 		if(clip == Rectangle.init)
4689 			return;
4690 
4691 		painter.originX = lox + x - scrollOrigin.x;
4692 		painter.originY = loy + y - scrollOrigin.y;
4693 		if(force || redrawRequested) {
4694 			painter.setClipRectangle(clip.upperLeft - Point(painter.originX, painter.originY), clip.width, clip.height);
4695 
4696 			erase(painter);
4697 			if(painter.visualTheme)
4698 				painter.visualTheme.doPaint(this, painter);
4699 			else
4700 				paint(painter);
4701 
4702 			actuallyPainted = true;
4703 			redrawRequested = false;
4704 		}
4705 		foreach(child; children) {
4706 			if(cast(FixedPosition) child)
4707 				child.privatePaint(painter, painter.originX + scrollOrigin.x, painter.originY + scrollOrigin.y, clip, actuallyPainted);
4708 			else
4709 				child.privatePaint(painter, painter.originX, painter.originY, clip, actuallyPainted);
4710 		}
4711 	}
4712 
4713 	version(custom_widgets)
4714 	override protected void addScrollPosition(ref int x, ref int y) {
4715 		x += scw.scrollX_;
4716 		y += scw.scrollY_;
4717 	}
4718 }
4719 
4720 /++
4721 	A widget meant to contain other widgets that may need to scroll.
4722 
4723 	History:
4724 		Added July 1, 2021 (dub v10.2)
4725 +/
4726 class ScrollableContainerWidget : ContainerWidget {
4727 	///
4728 	this(Widget parent) {
4729 		super(parent);
4730 
4731 		container = new InternalScrollableContainerInsideWidget(this);
4732 		hsb = new HorizontalScrollbar(this);
4733 		vsb = new VerticalScrollbar(this);
4734 
4735 		tabStop = false;
4736 		container.tabStop = false;
4737 		magic = true;
4738 
4739 
4740 		vsb.addEventListener("scrolltonextline", () {
4741 			scrollBy(0, 16);
4742 		});
4743 		vsb.addEventListener("scrolltopreviousline", () {
4744 			scrollBy(0, -16);
4745 		});
4746 		vsb.addEventListener("scrolltonextpage", () {
4747 			scrollBy(0, container.height);
4748 		});
4749 		vsb.addEventListener("scrolltopreviouspage", () {
4750 			scrollBy(0, -container.height);
4751 		});
4752 		vsb.addEventListener((scope ScrollToPositionEvent spe) {
4753 			scrollTo(scrollX_, spe.value);
4754 		});
4755 
4756 		this.addEventListener(delegate (scope ClickEvent e) {
4757 			if(e.button == MouseButton.wheelUp) {
4758 				if(!e.defaultPrevented)
4759 					scrollBy(0, -16);
4760 				e.stopPropagation();
4761 			} else if(e.button == MouseButton.wheelDown) {
4762 				if(!e.defaultPrevented)
4763 					scrollBy(0, 16);
4764 				e.stopPropagation();
4765 			}
4766 		});
4767 	}
4768 
4769 	/+
4770 	override void defaultEventHandler_click(ClickEvent e) {
4771 	}
4772 	+/
4773 
4774 	override void removeAllChildren() {
4775 		container.removeAllChildren();
4776 	}
4777 
4778 	void scrollTo(int x, int y) {
4779 		scrollBy(x - scrollX_, y - scrollY_);
4780 	}
4781 
4782 	void scrollBy(int x, int y) {
4783 		auto ox = scrollX_;
4784 		auto oy = scrollY_;
4785 
4786 		auto nx = ox + x;
4787 		auto ny = oy + y;
4788 
4789 		if(nx < 0)
4790 			nx = 0;
4791 		if(ny < 0)
4792 			ny = 0;
4793 
4794 		auto maxX = hsb.max - container.width;
4795 		if(maxX < 0) maxX = 0;
4796 		auto maxY = vsb.max - container.height;
4797 		if(maxY < 0) maxY = 0;
4798 
4799 		if(nx > maxX)
4800 			nx = maxX;
4801 		if(ny > maxY)
4802 			ny = maxY;
4803 
4804 		auto dx = nx - ox;
4805 		auto dy = ny - oy;
4806 
4807 		if(dx || dy) {
4808 			version(win32_widgets)
4809 				ScrollWindowEx(container.hwnd, -dx, -dy, null, null, null, null, SW_SCROLLCHILDREN | SW_INVALIDATE | SW_ERASE);
4810 			else
4811 				redraw();
4812 
4813 			hsb.setPosition = nx;
4814 			vsb.setPosition = ny;
4815 
4816 			scrollX_ = nx;
4817 			scrollY_ = ny;
4818 		}
4819 	}
4820 
4821 	private int scrollX_;
4822 	private int scrollY_;
4823 
4824 	void setTotalArea(int width, int height) {
4825 		hsb.setMax(width);
4826 		vsb.setMax(height);
4827 	}
4828 
4829 	///
4830 	void setViewableArea(int width, int height) {
4831 		hsb.setViewableArea(width);
4832 		vsb.setViewableArea(height);
4833 	}
4834 
4835 	private bool magic;
4836 	override void addChild(Widget w, int position = int.max) {
4837 		if(magic)
4838 			container.addChild(w, position);
4839 		else
4840 			super.addChild(w, position);
4841 	}
4842 
4843 	override void recomputeChildLayout() {
4844 		if(hsb is null || vsb is null || container is null) return;
4845 
4846 		/+
4847 		import std.stdio; writeln(x, " ", y , " ", width, " ", height);
4848 		writeln(this.ContainerWidget.minWidth(), "x", this.ContainerWidget.minHeight());
4849 		+/
4850 
4851 		registerMovement();
4852 
4853 		hsb.height = 16; // FIXME? are tese 16s sane?
4854 		hsb.x = 0;
4855 		hsb.y = this.height - hsb.height;
4856 		hsb.width = this.width - 16;
4857 		hsb.recomputeChildLayout();
4858 
4859 		vsb.width = 16; // FIXME?
4860 		vsb.x = this.width - vsb.width;
4861 		vsb.y = 0;
4862 		vsb.height = this.height - 16;
4863 		vsb.recomputeChildLayout();
4864 
4865 		container.x = 0;
4866 		container.y = 0;
4867 		container.width = this.width - vsb.width;
4868 		container.height = this.height - hsb.height;
4869 		container.recomputeChildLayout();
4870 
4871 		scrollX_ = 0;
4872 		scrollY_ = 0;
4873 
4874 		hsb.setPosition(0);
4875 		vsb.setPosition(0);
4876 
4877 		setTotalArea(this.ContainerWidget.minWidth(), this.ContainerWidget.minHeight());
4878 		setViewableArea(width, height);
4879 	}
4880 
4881 	override int minHeight() { return 64; }
4882 
4883 	HorizontalScrollbar hsb;
4884 	VerticalScrollbar vsb;
4885 	ContainerWidget container;
4886 }
4887 
4888 
4889 version(custom_widgets)
4890 private class InternalScrollableContainerWidget : Widget {
4891 
4892 	ScrollableWidget sw;
4893 
4894 	VerticalScrollbar verticalScrollBar;
4895 	HorizontalScrollbar horizontalScrollBar;
4896 
4897 	this(ScrollableWidget sw, Widget parent) {
4898 		this.sw = sw;
4899 
4900 		this.tabStop = false;
4901 
4902 		horizontalScrollBar = new HorizontalScrollbar(this);
4903 		verticalScrollBar = new VerticalScrollbar(this);
4904 
4905 		horizontalScrollBar.showing_ = false;
4906 		verticalScrollBar.showing_ = false;
4907 
4908 		horizontalScrollBar.addEventListener("scrolltonextline", {
4909 			horizontalScrollBar.setPosition(horizontalScrollBar.position + 1);
4910 			sw.horizontalScrollTo(horizontalScrollBar.position);
4911 		});
4912 		horizontalScrollBar.addEventListener("scrolltopreviousline", {
4913 			horizontalScrollBar.setPosition(horizontalScrollBar.position - 1);
4914 			sw.horizontalScrollTo(horizontalScrollBar.position);
4915 		});
4916 		verticalScrollBar.addEventListener("scrolltonextline", {
4917 			verticalScrollBar.setPosition(verticalScrollBar.position + 1);
4918 			sw.verticalScrollTo(verticalScrollBar.position);
4919 		});
4920 		verticalScrollBar.addEventListener("scrolltopreviousline", {
4921 			verticalScrollBar.setPosition(verticalScrollBar.position - 1);
4922 			sw.verticalScrollTo(verticalScrollBar.position);
4923 		});
4924 		horizontalScrollBar.addEventListener("scrolltonextpage", {
4925 			horizontalScrollBar.setPosition(horizontalScrollBar.position + horizontalScrollBar.step_);
4926 			sw.horizontalScrollTo(horizontalScrollBar.position);
4927 		});
4928 		horizontalScrollBar.addEventListener("scrolltopreviouspage", {
4929 			horizontalScrollBar.setPosition(horizontalScrollBar.position - horizontalScrollBar.step_);
4930 			sw.horizontalScrollTo(horizontalScrollBar.position);
4931 		});
4932 		verticalScrollBar.addEventListener("scrolltonextpage", {
4933 			verticalScrollBar.setPosition(verticalScrollBar.position + verticalScrollBar.step_);
4934 			sw.verticalScrollTo(verticalScrollBar.position);
4935 		});
4936 		verticalScrollBar.addEventListener("scrolltopreviouspage", {
4937 			verticalScrollBar.setPosition(verticalScrollBar.position - verticalScrollBar.step_);
4938 			sw.verticalScrollTo(verticalScrollBar.position);
4939 		});
4940 		horizontalScrollBar.addEventListener("scrolltoposition", (Event event) {
4941 			horizontalScrollBar.setPosition(event.intValue);
4942 			sw.horizontalScrollTo(horizontalScrollBar.position);
4943 		});
4944 		verticalScrollBar.addEventListener("scrolltoposition", (Event event) {
4945 			verticalScrollBar.setPosition(event.intValue);
4946 			sw.verticalScrollTo(verticalScrollBar.position);
4947 		});
4948 		horizontalScrollBar.addEventListener("scrolltrack", (Event event) {
4949 			horizontalScrollBar.setPosition(event.intValue);
4950 			sw.horizontalScrollTo(horizontalScrollBar.position);
4951 		});
4952 		verticalScrollBar.addEventListener("scrolltrack", (Event event) {
4953 			verticalScrollBar.setPosition(event.intValue);
4954 		});
4955 
4956 		super(parent);
4957 	}
4958 
4959 	// this is supposed to be basically invisible...
4960 	override int minWidth() { return sw.minWidth; }
4961 	override int minHeight() { return sw.minHeight; }
4962 	override int maxWidth() { return sw.maxWidth; }
4963 	override int maxHeight() { return sw.maxHeight; }
4964 	override int widthStretchiness() { return sw.widthStretchiness; }
4965 	override int heightStretchiness() { return sw.heightStretchiness; }
4966 	override int marginLeft() { return sw.marginLeft; }
4967 	override int marginRight() { return sw.marginRight; }
4968 	override int marginTop() { return sw.marginTop; }
4969 	override int marginBottom() { return sw.marginBottom; }
4970 	override int paddingLeft() { return sw.paddingLeft; }
4971 	override int paddingRight() { return sw.paddingRight; }
4972 	override int paddingTop() { return sw.paddingTop; }
4973 	override int paddingBottom() { return sw.paddingBottom; }
4974 	override void focus() { sw.focus(); }
4975 
4976 
4977 	override void recomputeChildLayout() {
4978 		// The stupid thing needs to calculate if a scroll bar is needed...
4979 		recomputeChildLayoutHelper();
4980 		// then running it again will position things correctly if the bar is NOT needed
4981 		recomputeChildLayoutHelper();
4982 
4983 		// this sucks but meh it barely works
4984 	}
4985 
4986 	private void recomputeChildLayoutHelper() {
4987 		if(sw is null) return;
4988 
4989 		bool both = sw.showingVerticalScroll && sw.showingHorizontalScroll;
4990 		if(horizontalScrollBar && verticalScrollBar) {
4991 			horizontalScrollBar.width = this.width - (both ? verticalScrollBar.minWidth() : 0);
4992 			horizontalScrollBar.height = horizontalScrollBar.minHeight();
4993 			horizontalScrollBar.x = 0;
4994 			horizontalScrollBar.y = this.height - horizontalScrollBar.minHeight();
4995 
4996 			verticalScrollBar.width = verticalScrollBar.minWidth();
4997 			verticalScrollBar.height = this.height - (both ? horizontalScrollBar.minHeight() : 0) - 2 - 2;
4998 			verticalScrollBar.x = this.width - verticalScrollBar.minWidth();
4999 			verticalScrollBar.y = 0 + 2;
5000 
5001 			sw.x = 0;
5002 			sw.y = 0;
5003 			sw.width = this.width - (verticalScrollBar.showing ? verticalScrollBar.width : 0);
5004 			sw.height = this.height - (horizontalScrollBar.showing ? horizontalScrollBar.height : 0);
5005 
5006 			if(sw.contentWidth_ <= this.width)
5007 				sw.scrollOrigin_.x = 0;
5008 			if(sw.contentHeight_ <= this.height)
5009 				sw.scrollOrigin_.y = 0;
5010 
5011 			horizontalScrollBar.recomputeChildLayout();
5012 			verticalScrollBar.recomputeChildLayout();
5013 			sw.recomputeChildLayout();
5014 		}
5015 
5016 		if(sw.contentWidth_ <= this.width)
5017 			sw.scrollOrigin_.x = 0;
5018 		if(sw.contentHeight_ <= this.height)
5019 			sw.scrollOrigin_.y = 0;
5020 
5021 		if(sw.showingHorizontalScroll())
5022 			horizontalScrollBar.showing(true, false);
5023 		else
5024 			horizontalScrollBar.showing(false, false);
5025 		if(sw.showingVerticalScroll())
5026 			verticalScrollBar.showing(true, false);
5027 		else
5028 			verticalScrollBar.showing(false, false);
5029 
5030 		verticalScrollBar.setViewableArea(sw.viewportHeight());
5031 		verticalScrollBar.setMax(sw.contentHeight);
5032 		verticalScrollBar.setPosition(sw.scrollOrigin.y);
5033 
5034 		horizontalScrollBar.setViewableArea(sw.viewportWidth());
5035 		horizontalScrollBar.setMax(sw.contentWidth);
5036 		horizontalScrollBar.setPosition(sw.scrollOrigin.x);
5037 	}
5038 }
5039 
5040 /*
5041 class ScrollableClientWidget : Widget {
5042 	this(Widget parent) {
5043 		super(parent);
5044 	}
5045 	override void paint(WidgetPainter p) {
5046 		parent.paint(p);
5047 	}
5048 }
5049 */
5050 
5051 /++
5052 	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.
5053 +/
5054 abstract class Slider : Widget {
5055 	this(int min, int max, int step, Widget parent) {
5056 		min_ = min;
5057 		max_ = max;
5058 		step_ = step;
5059 		page_ = step;
5060 		super(parent);
5061 	}
5062 
5063 	private int min_;
5064 	private int max_;
5065 	private int step_;
5066 	private int position_;
5067 	private int page_;
5068 
5069 	// selection start and selection end
5070 	// tics
5071 	// tooltip?
5072 	// some way to see and just type the value
5073 	// win32 buddy controls are labels
5074 
5075 	///
5076 	void setMin(int a) {
5077 		min_ = a;
5078 		version(custom_widgets)
5079 			redraw();
5080 		version(win32_widgets)
5081 			SendMessage(hwnd, TBM_SETRANGEMIN, true, a);
5082 	}
5083 	///
5084 	int min() {
5085 		return min_;
5086 	}
5087 	///
5088 	void setMax(int a) {
5089 		max_ = a;
5090 		version(custom_widgets)
5091 			redraw();
5092 		version(win32_widgets)
5093 			SendMessage(hwnd, TBM_SETRANGEMAX, true, a);
5094 	}
5095 	///
5096 	int max() {
5097 		return max_;
5098 	}
5099 	///
5100 	void setPosition(int a) {
5101 		if(a > max)
5102 			a = max;
5103 		if(a < min)
5104 			a = min;
5105 		position_ = a;
5106 		version(custom_widgets)
5107 			setPositionCustom(a);
5108 
5109 		version(win32_widgets)
5110 			setPositionWindows(a);
5111 	}
5112 	version(win32_widgets) {
5113 		protected abstract void setPositionWindows(int a);
5114 	}
5115 
5116 	protected abstract int win32direction();
5117 
5118 	/++
5119 		Alias for [position] for better compatibility with generic code.
5120 
5121 		History:
5122 			Added October 5, 2021
5123 	+/
5124 	@property int value() {
5125 		return position;
5126 	}
5127 
5128 	///
5129 	int position() {
5130 		return position_;
5131 	}
5132 	///
5133 	void setStep(int a) {
5134 		step_ = a;
5135 		version(win32_widgets)
5136 			SendMessage(hwnd, TBM_SETLINESIZE, 0, a);
5137 	}
5138 	///
5139 	int step() {
5140 		return step_;
5141 	}
5142 	///
5143 	void setPageSize(int a) {
5144 		page_ = a;
5145 		version(win32_widgets)
5146 			SendMessage(hwnd, TBM_SETPAGESIZE, 0, a);
5147 	}
5148 	///
5149 	int pageSize() {
5150 		return page_;
5151 	}
5152 
5153 	private void notify() {
5154 		auto event = new ChangeEvent!int(this, &this.position);
5155 		event.dispatch();
5156 	}
5157 
5158 	version(win32_widgets)
5159 	void win32Setup(int style) {
5160 		createWin32Window(this, TRACKBAR_CLASS, "", 
5161 			0|WS_CHILD|WS_VISIBLE|style|TBS_TOOLTIPS, 0);
5162 
5163 		// the trackbar sends the same messages as scroll, which
5164 		// our other layer sends as these... just gonna translate
5165 		// here
5166 		this.addDirectEventListener("scrolltoposition", (Event event) {
5167 			event.stopPropagation();
5168 			this.setPosition(this.win32direction > 0 ? event.intValue : max - event.intValue);
5169 			notify();
5170 		});
5171 		this.addDirectEventListener("scrolltonextline", (Event event) {
5172 			event.stopPropagation();
5173 			this.setPosition(this.position + this.step_ * this.win32direction);
5174 			notify();
5175 		});
5176 		this.addDirectEventListener("scrolltopreviousline", (Event event) {
5177 			event.stopPropagation();
5178 			this.setPosition(this.position - this.step_ * this.win32direction);
5179 			notify();
5180 		});
5181 		this.addDirectEventListener("scrolltonextpage", (Event event) {
5182 			event.stopPropagation();
5183 			this.setPosition(this.position + this.page_ * this.win32direction);
5184 			notify();
5185 		});
5186 		this.addDirectEventListener("scrolltopreviouspage", (Event event) {
5187 			event.stopPropagation();
5188 			this.setPosition(this.position - this.page_ * this.win32direction);
5189 			notify();
5190 		});
5191 
5192 		setMin(min_);
5193 		setMax(max_);
5194 		setStep(step_);
5195 		setPageSize(page_);
5196 	}
5197 
5198 	version(custom_widgets) {
5199 		protected MouseTrackingWidget thumb;
5200 
5201 		protected abstract void setPositionCustom(int a);
5202 
5203 		override void defaultEventHandler_keydown(KeyDownEvent event) {
5204 			switch(event.key) {
5205 				case Key.Up:
5206 				case Key.Right:
5207 					setPosition(position() - step() * win32direction);
5208 					changed();
5209 				break;
5210 				case Key.Down:
5211 				case Key.Left:
5212 					setPosition(position() + step() * win32direction);
5213 					changed();
5214 				break;
5215 				case Key.Home:
5216 					setPosition(win32direction > 0 ? min() : max());
5217 					changed();
5218 				break;
5219 				case Key.End:
5220 					setPosition(win32direction > 0 ? max() : min());
5221 					changed();
5222 				break;
5223 				case Key.PageUp:
5224 					setPosition(position() - pageSize() * win32direction);
5225 					changed();
5226 				break;
5227 				case Key.PageDown:
5228 					setPosition(position() + pageSize() * win32direction);
5229 					changed();
5230 				break;
5231 				default:
5232 			}
5233 			super.defaultEventHandler_keydown(event);
5234 		}
5235 
5236 		protected void changed() {
5237 			auto ev = new ChangeEvent!int(this, &position);
5238 			ev.dispatch();
5239 		}
5240 	}
5241 }
5242 
5243 /++
5244 
5245 +/
5246 class VerticalSlider : Slider {
5247 	this(int min, int max, int step, Widget parent) {
5248 		version(custom_widgets)
5249 			initialize();
5250 
5251 		super(min, max, step, parent);
5252 
5253 		version(win32_widgets)
5254 			win32Setup(TBS_VERT | 0x0200 /* TBS_REVERSED */);
5255 	}
5256 
5257 	protected override int win32direction() {
5258 		return -1;
5259 	}
5260 
5261 	version(win32_widgets)
5262 	protected override void setPositionWindows(int a) {
5263 		// the windows thing makes the top 0 and i don't like that.
5264 		SendMessage(hwnd, TBM_SETPOS, true, max - a);
5265 	}
5266 
5267 	version(custom_widgets)
5268 	private void initialize() {
5269 		thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.vertical, this);
5270 
5271 		thumb.tabStop = false;
5272 
5273 		thumb.thumbWidth = width;
5274 		thumb.thumbHeight = 16;
5275 
5276 		thumb.addEventListener(EventType.change, () {
5277 			auto sx = thumb.positionY * max() / (thumb.height - 16);
5278 			sx = max - sx;
5279 			//informProgramThatUserChangedPosition(sx);
5280 
5281 			position_ = sx;
5282 
5283 			changed();
5284 		});
5285 	}
5286 
5287 	version(custom_widgets)
5288 	override void recomputeChildLayout() {
5289 		thumb.thumbWidth = this.width;
5290 		super.recomputeChildLayout();
5291 		setPositionCustom(position_);
5292 	}
5293 
5294 	version(custom_widgets)
5295 	protected override void setPositionCustom(int a) {
5296 		if(max())
5297 			thumb.positionY = (max - a) * (thumb.height - 16) / max();
5298 		redraw();
5299 	}
5300 }
5301 
5302 /++
5303 
5304 +/
5305 class HorizontalSlider : Slider {
5306 	this(int min, int max, int step, Widget parent) {
5307 		version(custom_widgets)
5308 			initialize();
5309 
5310 		super(min, max, step, parent);
5311 
5312 		version(win32_widgets)
5313 			win32Setup(TBS_HORZ);
5314 	}
5315 
5316 	version(win32_widgets)
5317 	protected override void setPositionWindows(int a) {
5318 		SendMessage(hwnd, TBM_SETPOS, true, a);
5319 	}
5320 
5321 	protected override int win32direction() {
5322 		return 1;
5323 	}
5324 
5325 	version(custom_widgets)
5326 	private void initialize() {
5327 		thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.horizontal, this);
5328 
5329 		thumb.tabStop = false;
5330 
5331 		thumb.thumbWidth = 16;
5332 		thumb.thumbHeight = height;
5333 
5334 		thumb.addEventListener(EventType.change, () {
5335 			auto sx = thumb.positionX * max() / (thumb.width - 16);
5336 			//informProgramThatUserChangedPosition(sx);
5337 
5338 			position_ = sx;
5339 
5340 			changed();
5341 		});
5342 	}
5343 
5344 	version(custom_widgets)
5345 	override void recomputeChildLayout() {
5346 		thumb.thumbHeight = this.height;
5347 		super.recomputeChildLayout();
5348 		setPositionCustom(position_);
5349 	}
5350 
5351 	version(custom_widgets)
5352 	protected override void setPositionCustom(int a) {
5353 		if(max())
5354 			thumb.positionX = a * (thumb.width - 16) / max();
5355 		redraw();
5356 	}
5357 }
5358 
5359 
5360 ///
5361 abstract class ScrollbarBase : Widget {
5362 	///
5363 	this(Widget parent) {
5364 		super(parent);
5365 		tabStop = false;
5366 	}
5367 
5368 	private int viewableArea_;
5369 	private int max_;
5370 	private int step_ = 16;
5371 	private int position_;
5372 
5373 	///
5374 	bool atEnd() {
5375 		return position_ + viewableArea_ >= max_;
5376 	}
5377 
5378 	///
5379 	bool atStart() {
5380 		return position_ == 0;
5381 	}
5382 
5383 	///
5384 	void setViewableArea(int a) {
5385 		viewableArea_ = a;
5386 		version(custom_widgets)
5387 			redraw();
5388 	}
5389 	///
5390 	void setMax(int a) {
5391 		max_ = a;
5392 		version(custom_widgets)
5393 			redraw();
5394 	}
5395 	///
5396 	int max() {
5397 		return max_;
5398 	}
5399 	///
5400 	void setPosition(int a) {
5401 		if(a == int.max)
5402 			a = max;
5403 		position_ = max ? a : 0;
5404 		if(position_ + viewableArea_ > max)
5405 			position_ = max - viewableArea_;
5406 		if(position_ < 0)
5407 			position_ = 0;
5408 		version(custom_widgets)
5409 			redraw();
5410 	}
5411 	///
5412 	int position() {
5413 		return position_;
5414 	}
5415 	///
5416 	void setStep(int a) {
5417 		step_ = a;
5418 	}
5419 	///
5420 	int step() {
5421 		return step_;
5422 	}
5423 
5424 	// FIXME: remove this.... maybe
5425 	/+
5426 	protected void informProgramThatUserChangedPosition(int n) {
5427 		position_ = n;
5428 		auto evt = new Event(EventType.change, this);
5429 		evt.intValue = n;
5430 		evt.dispatch();
5431 	}
5432 	+/
5433 
5434 	version(custom_widgets) {
5435 		abstract protected int getBarDim();
5436 		int thumbSize() {
5437 			if(viewableArea_ >= max_)
5438 				return getBarDim();
5439 
5440 			int res;
5441 			if(max_) {
5442 				res = getBarDim() * viewableArea_ / max_;
5443 			}
5444 			if(res < 6)
5445 				res = 6;
5446 
5447 			return res;
5448 		}
5449 
5450 		int thumbPosition() {
5451 			/*
5452 				viewableArea_ is the viewport height/width
5453 				position_ is where we are
5454 			*/
5455 			if(max_) {
5456 				if(position_ + viewableArea_ >= max_)
5457 					return getBarDim - thumbSize;
5458 				return getBarDim * position_ / max_;
5459 			}
5460 			return 0;
5461 		}
5462 	}
5463 }
5464 
5465 //public import mgt;
5466 
5467 /++
5468 	A mouse tracking widget is one that follows the mouse when dragged inside it.
5469 
5470 	Concrete subclasses may include a scrollbar thumb and a volume control.
5471 +/
5472 //version(custom_widgets)
5473 class MouseTrackingWidget : Widget {
5474 
5475 	///
5476 	int positionX() { return positionX_; }
5477 	///
5478 	int positionY() { return positionY_; }
5479 
5480 	///
5481 	void positionX(int p) { positionX_ = p; }
5482 	///
5483 	void positionY(int p) { positionY_ = p; }
5484 
5485 	private int positionX_;
5486 	private int positionY_;
5487 
5488 	///
5489 	enum Orientation {
5490 		horizontal, ///
5491 		vertical, ///
5492 		twoDimensional, ///
5493 	}
5494 
5495 	private int thumbWidth_;
5496 	private int thumbHeight_;
5497 
5498 	///
5499 	int thumbWidth() { return thumbWidth_; }
5500 	///
5501 	int thumbHeight() { return thumbHeight_; }
5502 	///
5503 	int thumbWidth(int a) { return thumbWidth_ = a; }
5504 	///
5505 	int thumbHeight(int a) { return thumbHeight_ = a; }
5506 
5507 	private bool dragging;
5508 	private bool hovering;
5509 	private int startMouseX, startMouseY;
5510 
5511 	///
5512 	this(Orientation orientation, Widget parent) {
5513 		super(parent);
5514 
5515 		//assert(parentWindow !is null);
5516 
5517 		addEventListener((MouseDownEvent event) {
5518 			if(event.clientX >= positionX && event.clientX < positionX + thumbWidth && event.clientY >= positionY && event.clientY < positionY + thumbHeight) {
5519 				dragging = true;
5520 				startMouseX = event.clientX - positionX;
5521 				startMouseY = event.clientY - positionY;
5522 				parentWindow.captureMouse(this);
5523 			} else {
5524 				if(orientation == Orientation.horizontal || orientation == Orientation.twoDimensional)
5525 					positionX = event.clientX - thumbWidth / 2;
5526 				if(orientation == Orientation.vertical || orientation == Orientation.twoDimensional)
5527 					positionY = event.clientY - thumbHeight / 2;
5528 
5529 				if(positionX + thumbWidth > this.width)
5530 					positionX = this.width - thumbWidth;
5531 				if(positionY + thumbHeight > this.height)
5532 					positionY = this.height - thumbHeight;
5533 
5534 				if(positionX < 0)
5535 					positionX = 0;
5536 				if(positionY < 0)
5537 					positionY = 0;
5538 
5539 
5540 				// this.emit!(ChangeEvent!void)();
5541 				auto evt = new Event(EventType.change, this);
5542 				evt.sendDirectly();
5543 
5544 				redraw();
5545 
5546 			}
5547 		});
5548 
5549 		addEventListener(EventType.mouseup, (Event event) {
5550 			dragging = false;
5551 			parentWindow.releaseMouseCapture();
5552 		});
5553 
5554 		addEventListener(EventType.mouseout, (Event event) {
5555 			if(!hovering)
5556 				return;
5557 			hovering = false;
5558 			redraw();
5559 		});
5560 
5561 		int lpx, lpy;
5562 
5563 		addEventListener((MouseMoveEvent event) {
5564 			auto oh = hovering;
5565 			if(event.clientX >= positionX && event.clientX < positionX + thumbWidth && event.clientY >= positionY && event.clientY < positionY + thumbHeight) {
5566 				hovering = true;
5567 			} else {
5568 				hovering = false;
5569 			}
5570 			if(!dragging) {
5571 				if(hovering != oh)
5572 					redraw();
5573 				return;
5574 			}
5575 
5576 			if(orientation == Orientation.horizontal || orientation == Orientation.twoDimensional)
5577 				positionX = event.clientX - startMouseX; // FIXME: click could be in the middle of it
5578 			if(orientation == Orientation.vertical || orientation == Orientation.twoDimensional)
5579 				positionY = event.clientY - startMouseY;
5580 
5581 			if(positionX + thumbWidth > this.width)
5582 				positionX = this.width - thumbWidth;
5583 			if(positionY + thumbHeight > this.height)
5584 				positionY = this.height - thumbHeight;
5585 
5586 			if(positionX < 0)
5587 				positionX = 0;
5588 			if(positionY < 0)
5589 				positionY = 0;
5590 
5591 			if(positionX != lpx || positionY != lpy) {
5592 				auto evt = new Event(EventType.change, this);
5593 				evt.sendDirectly();
5594 
5595 				lpx = positionX;
5596 				lpy = positionY;
5597 			}
5598 
5599 			redraw();
5600 		});
5601 	}
5602 
5603 	version(custom_widgets)
5604 	override void paint(WidgetPainter painter) {
5605 		auto cs = getComputedStyle();
5606 		auto c = darken(cs.windowBackgroundColor, 0.2);
5607 		painter.outlineColor = c;
5608 		painter.fillColor = c;
5609 		painter.drawRectangle(Point(0, 0), this.width, this.height);
5610 
5611 		auto color = hovering ? cs.hoveringColor : cs.windowBackgroundColor;
5612 		draw3dFrame(positionX, positionY, thumbWidth, thumbHeight, painter, FrameStyle.risen, color);
5613 	}
5614 }
5615 
5616 //version(custom_widgets)
5617 //private
5618 class HorizontalScrollbar : ScrollbarBase {
5619 
5620 	version(custom_widgets) {
5621 		private MouseTrackingWidget thumb;
5622 
5623 		override int getBarDim() {
5624 			return thumb.width;
5625 		}
5626 	}
5627 
5628 	override void setViewableArea(int a) {
5629 		super.setViewableArea(a);
5630 
5631 		version(win32_widgets) {
5632 			SCROLLINFO info;
5633 			info.cbSize = info.sizeof;
5634 			info.nPage = a + 1;
5635 			info.fMask = SIF_PAGE;
5636 			SetScrollInfo(hwnd, SB_CTL, &info, true);
5637 		} else version(custom_widgets) {
5638 			thumb.positionX = thumbPosition;
5639 			thumb.thumbWidth = thumbSize;
5640 			thumb.redraw();
5641 		} else static assert(0);
5642 
5643 	}
5644 
5645 	override void setMax(int a) {
5646 		super.setMax(a);
5647 		version(win32_widgets) {
5648 			SCROLLINFO info;
5649 			info.cbSize = info.sizeof;
5650 			info.nMin = 0;
5651 			info.nMax = max;
5652 			info.fMask = SIF_RANGE;
5653 			SetScrollInfo(hwnd, SB_CTL, &info, true);
5654 		} else version(custom_widgets) {
5655 			thumb.positionX = thumbPosition;
5656 			thumb.thumbWidth = thumbSize;
5657 			thumb.redraw();
5658 		}
5659 	}
5660 
5661 	override void setPosition(int a) {
5662 		super.setPosition(a);
5663 		version(win32_widgets) {
5664 			SCROLLINFO info;
5665 			info.cbSize = info.sizeof;
5666 			info.fMask = SIF_POS;
5667 			info.nPos = position;
5668 			SetScrollInfo(hwnd, SB_CTL, &info, true);
5669 		} else version(custom_widgets) {
5670 			thumb.positionX = thumbPosition();
5671 			thumb.thumbWidth = thumbSize;
5672 			thumb.redraw();
5673 		} else static assert(0);
5674 	}
5675 
5676 	this(Widget parent) {
5677 		super(parent);
5678 
5679 		version(win32_widgets) {
5680 			createWin32Window(this, "Scrollbar"w, "", 
5681 				0|WS_CHILD|WS_VISIBLE|SBS_HORZ|SBS_BOTTOMALIGN, 0);
5682 		} else version(custom_widgets) {
5683 			auto vl = new HorizontalLayout(this);
5684 			auto leftButton = new ArrowButton(ArrowDirection.left, vl);
5685 			leftButton.setClickRepeat(scrollClickRepeatInterval);
5686 			thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.horizontal, vl);
5687 			auto rightButton = new ArrowButton(ArrowDirection.right, vl);
5688 			rightButton.setClickRepeat(scrollClickRepeatInterval);
5689 
5690 			leftButton.tabStop = false;
5691 			rightButton.tabStop = false;
5692 			thumb.tabStop = false;
5693 
5694 			leftButton.addEventListener(EventType.triggered, () {
5695 				this.emitCommand!"scrolltopreviousline"();
5696 				//informProgramThatUserChangedPosition(position - step());
5697 			});
5698 			rightButton.addEventListener(EventType.triggered, () {
5699 				this.emitCommand!"scrolltonextline"();
5700 				//informProgramThatUserChangedPosition(position + step());
5701 			});
5702 
5703 			thumb.thumbWidth = this.minWidth;
5704 			thumb.thumbHeight = 16;
5705 
5706 			thumb.addEventListener(EventType.change, () {
5707 				auto sx = thumb.positionX * max() / thumb.width;
5708 				//informProgramThatUserChangedPosition(sx);
5709 
5710 				auto ev = new ScrollToPositionEvent(this, sx);
5711 				ev.dispatch();
5712 			});
5713 		}
5714 	}
5715 
5716 	override int minHeight() { return 16; }
5717 	override int maxHeight() { return 16; }
5718 	override int minWidth() { return 48; }
5719 }
5720 
5721 class ScrollToPositionEvent : Event {
5722 	enum EventString = "scrolltoposition";
5723 
5724 	this(Widget target, int value) {
5725 		this.value = value;
5726 		super(EventString, target);
5727 	}
5728 
5729 	immutable int value;
5730 
5731 	override @property int intValue() {
5732 		return value;
5733 	}
5734 }
5735 
5736 //version(custom_widgets)
5737 //private
5738 class VerticalScrollbar : ScrollbarBase {
5739 
5740 	version(custom_widgets) {
5741 		override int getBarDim() {
5742 			return thumb.height;
5743 		}
5744 
5745 		private MouseTrackingWidget thumb;
5746 	}
5747 
5748 	override void setViewableArea(int a) {
5749 		super.setViewableArea(a);
5750 
5751 		version(win32_widgets) {
5752 			SCROLLINFO info;
5753 			info.cbSize = info.sizeof;
5754 			info.nPage = a + 1;
5755 			info.fMask = SIF_PAGE;
5756 			SetScrollInfo(hwnd, SB_CTL, &info, true);
5757 		} else version(custom_widgets) {
5758 			thumb.positionY = thumbPosition;
5759 			thumb.thumbHeight = thumbSize;
5760 			thumb.redraw();
5761 		} else static assert(0);
5762 
5763 	}
5764 
5765 	override void setMax(int a) {
5766 		super.setMax(a);
5767 		version(win32_widgets) {
5768 			SCROLLINFO info;
5769 			info.cbSize = info.sizeof;
5770 			info.nMin = 0;
5771 			info.nMax = max;
5772 			info.fMask = SIF_RANGE;
5773 			SetScrollInfo(hwnd, SB_CTL, &info, true);
5774 		} else version(custom_widgets) {
5775 			thumb.positionY = thumbPosition;
5776 			thumb.thumbHeight = thumbSize;
5777 			thumb.redraw();
5778 		}
5779 	}
5780 
5781 	override void setPosition(int a) {
5782 		super.setPosition(a);
5783 		version(win32_widgets) {
5784 			SCROLLINFO info;
5785 			info.cbSize = info.sizeof;
5786 			info.fMask = SIF_POS;
5787 			info.nPos = position;
5788 			SetScrollInfo(hwnd, SB_CTL, &info, true);
5789 		} else version(custom_widgets) {
5790 			thumb.positionY = thumbPosition;
5791 			thumb.thumbHeight = thumbSize;
5792 			thumb.redraw();
5793 		} else static assert(0);
5794 	}
5795 
5796 	this(Widget parent) {
5797 		super(parent);
5798 
5799 		version(win32_widgets) {
5800 			createWin32Window(this, "Scrollbar"w, "", 
5801 				0|WS_CHILD|WS_VISIBLE|SBS_VERT|SBS_RIGHTALIGN, 0);
5802 		} else version(custom_widgets) {
5803 			auto vl = new VerticalLayout(this);
5804 			auto upButton = new ArrowButton(ArrowDirection.up, vl);
5805 			upButton.setClickRepeat(scrollClickRepeatInterval);
5806 			thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.vertical, vl);
5807 			auto downButton = new ArrowButton(ArrowDirection.down, vl);
5808 			downButton.setClickRepeat(scrollClickRepeatInterval);
5809 
5810 			upButton.addEventListener(EventType.triggered, () {
5811 				this.emitCommand!"scrolltopreviousline"();
5812 				//informProgramThatUserChangedPosition(position - step());
5813 			});
5814 			downButton.addEventListener(EventType.triggered, () {
5815 				this.emitCommand!"scrolltonextline"();
5816 				//informProgramThatUserChangedPosition(position + step());
5817 			});
5818 
5819 			thumb.thumbWidth = this.minWidth;
5820 			thumb.thumbHeight = 16;
5821 
5822 			thumb.addEventListener(EventType.change, () {
5823 				auto sy = thumb.positionY * max() / thumb.height;
5824 
5825 				auto ev = new ScrollToPositionEvent(this, sy);
5826 				ev.dispatch();
5827 
5828 				//informProgramThatUserChangedPosition(sy);
5829 			});
5830 
5831 			upButton.tabStop = false;
5832 			downButton.tabStop = false;
5833 			thumb.tabStop = false;
5834 		}
5835 	}
5836 
5837 	override int minWidth() { return 16; }
5838 	override int maxWidth() { return 16; }
5839 	override int minHeight() { return 48; }
5840 }
5841 
5842 
5843 /++
5844 	EXPERIMENTAL
5845 
5846 	A widget specialized for being a container for other widgets.
5847 
5848 	History:
5849 		Added May 29, 2021. Not stabilized at this time.
5850 +/
5851 class WidgetContainer : Widget {
5852 	this(Widget parent) {
5853 		tabStop = false;
5854 		super(parent);
5855 	}
5856 
5857 	override int maxHeight() {
5858 		if(this.children.length == 1) {
5859 			return saturatedSum(this.children[0].maxHeight, this.children[0].marginTop, this.children[0].marginBottom);
5860 		} else {
5861 			return int.max;
5862 		}
5863 	}
5864 
5865 	override int maxWidth() {
5866 		if(this.children.length == 1) {
5867 			return saturatedSum(this.children[0].maxWidth, this.children[0].marginLeft, this.children[0].marginRight);
5868 		} else {
5869 			return int.max;
5870 		}
5871 	}
5872 
5873 	/+
5874 
5875 	override int minHeight() {
5876 		int largest = 0;
5877 		int margins = 0;
5878 		int lastMargin = 0;
5879 		foreach(child; children) {
5880 			auto mh = child.minHeight();
5881 			if(mh > largest)
5882 				largest = mh;
5883 			margins += mymax(lastMargin, child.marginTop());
5884 			lastMargin = child.marginBottom();
5885 		}
5886 		return largest + margins;
5887 	}
5888 
5889 	override int maxHeight() {
5890 		int largest = 0;
5891 		int margins = 0;
5892 		int lastMargin = 0;
5893 		foreach(child; children) {
5894 			auto mh = child.maxHeight();
5895 			if(mh == int.max)
5896 				return int.max;
5897 			if(mh > largest)
5898 				largest = mh;
5899 			margins += mymax(lastMargin, child.marginTop());
5900 			lastMargin = child.marginBottom();
5901 		}
5902 		return largest + margins;
5903 	}
5904 
5905 	override int minWidth() {
5906 		int min;
5907 		foreach(child; children) {
5908 			auto cm = child.minWidth;
5909 			if(cm > min)
5910 				min = cm;
5911 		}
5912 		return min + paddingLeft + paddingRight;
5913 	}
5914 
5915 	override int minHeight() {
5916 		int min;
5917 		foreach(child; children) {
5918 			auto cm = child.minHeight;
5919 			if(cm > min)
5920 				min = cm;
5921 		}
5922 		return min + paddingTop + paddingBottom;
5923 	}
5924 
5925 	override int maxHeight() {
5926 		int largest = 0;
5927 		int margins = 0;
5928 		int lastMargin = 0;
5929 		foreach(child; children) {
5930 			auto mh = child.maxHeight();
5931 			if(mh == int.max)
5932 				return int.max;
5933 			if(mh > largest)
5934 				largest = mh;
5935 			margins += mymax(lastMargin, child.marginTop());
5936 			lastMargin = child.marginBottom();
5937 		}
5938 		return largest + margins;
5939 	}
5940 
5941 	override int heightStretchiness() {
5942 		int max;
5943 		foreach(child; children) {
5944 			auto c = child.heightStretchiness;
5945 			if(c > max)
5946 				max = c;
5947 		}
5948 		return max;
5949 	}
5950 
5951 	override int marginTop() {
5952 		if(this.children.length)
5953 			return this.children[0].marginTop;
5954 		return 0;
5955 	}
5956 	+/
5957 }
5958 
5959 ///
5960 abstract class Layout : Widget {
5961 	this(Widget parent) {
5962 		tabStop = false;
5963 		super(parent);
5964 	}
5965 }
5966 
5967 /++
5968 	Makes all children minimum width and height, placing them down
5969 	left to right, top to bottom.
5970 
5971 	Useful if you want to make a list of buttons that automatically
5972 	wrap to a new line when necessary.
5973 +/
5974 class InlineBlockLayout : Layout {
5975 	///
5976 	this(Widget parent) { super(parent); }
5977 
5978 	override void recomputeChildLayout() {
5979 		registerMovement();
5980 
5981 		int x = this.paddingLeft, y = this.paddingTop;
5982 
5983 		int lineHeight;
5984 		int previousMargin = 0;
5985 		int previousMarginBottom = 0;
5986 
5987 		foreach(child; children) {
5988 			if(child.hidden)
5989 				continue;
5990 			if(cast(FixedPosition) child) {
5991 				child.recomputeChildLayout();
5992 				continue;
5993 			}
5994 			child.width = child.flexBasisWidth();
5995 			if(child.width == 0)
5996 				child.width = child.minWidth();
5997 			if(child.width == 0)
5998 				child.width = 32;
5999 
6000 			child.height = child.flexBasisHeight();
6001 			if(child.height == 0)
6002 				child.height = child.minHeight();
6003 			if(child.height == 0)
6004 				child.height = 32;
6005 
6006 			if(x + child.width + paddingRight > this.width) {
6007 				x = this.paddingLeft;
6008 				y += lineHeight;
6009 				lineHeight = 0;
6010 				previousMargin = 0;
6011 				previousMarginBottom = 0;
6012 			}
6013 
6014 			auto margin = child.marginLeft;
6015 			if(previousMargin > margin)
6016 				margin = previousMargin;
6017 
6018 			x += margin;
6019 
6020 			child.x = x;
6021 			child.y = y;
6022 
6023 			int marginTopApplied;
6024 			if(child.marginTop > previousMarginBottom) {
6025 				child.y += child.marginTop;
6026 				marginTopApplied = child.marginTop;
6027 			}
6028 
6029 			x += child.width;
6030 			previousMargin = child.marginRight;
6031 
6032 			if(child.marginBottom > previousMarginBottom)
6033 				previousMarginBottom = child.marginBottom;
6034 
6035 			auto h = child.height + previousMarginBottom + marginTopApplied;
6036 			if(h > lineHeight)
6037 				lineHeight = h;
6038 
6039 			child.recomputeChildLayout();
6040 		}
6041 
6042 	}
6043 
6044 	override int minWidth() {
6045 		int min;
6046 		foreach(child; children) {
6047 			auto cm = child.minWidth;
6048 			if(cm > min)
6049 				min = cm;
6050 		}
6051 		return min + paddingLeft + paddingRight;
6052 	}
6053 
6054 	override int minHeight() {
6055 		int min;
6056 		foreach(child; children) {
6057 			auto cm = child.minHeight;
6058 			if(cm > min)
6059 				min = cm;
6060 		}
6061 		return min + paddingTop + paddingBottom;
6062 	}
6063 }
6064 
6065 /++
6066 	A tab widget is a set of clickable tab buttons followed by a content area.
6067 
6068 
6069 	Tabs can change existing content or can be new pages.
6070 
6071 	When the user picks a different tab, a `change` message is generated.
6072 +/
6073 class TabWidget : Widget {
6074 	this(Widget parent) {
6075 		super(parent);
6076 
6077 		tabStop = false;
6078 
6079 		version(win32_widgets) {
6080 			createWin32Window(this, WC_TABCONTROL, "", 0);
6081 		} else version(custom_widgets) {
6082 			addEventListener((ClickEvent event) {
6083 				if(event.target !is this) return;
6084 				if(event.clientY < tabBarHeight) {
6085 					auto t = (event.clientX / tabWidth);
6086 					if(t >= 0 && t < children.length)
6087 						setCurrentTab(t);
6088 				}
6089 			});
6090 		} else static assert(0);
6091 	}
6092 
6093 	override int marginTop() { return 4; }
6094 	override int paddingBottom() { return 4; }
6095 
6096 	override int minHeight() {
6097 		int max = 0;
6098 		foreach(child; children)
6099 			max = mymax(child.minHeight, max);
6100 
6101 
6102 		version(win32_widgets) {
6103 			RECT rect;
6104 			rect.right = this.width;
6105 			rect.bottom = max;
6106 			TabCtrl_AdjustRect(hwnd, true, &rect);
6107 
6108 			max = rect.bottom;
6109 		} else {
6110 			max += defaultLineHeight + 4;
6111 		}
6112 
6113 
6114 		return max;
6115 	}
6116 
6117 	version(win32_widgets)
6118 	override int handleWmNotify(NMHDR* hdr, int code, out int mustReturn) {
6119 		switch(code) {
6120 			case TCN_SELCHANGE:
6121 				auto sel = TabCtrl_GetCurSel(hwnd);
6122 				showOnly(sel);
6123 			break;
6124 			default:
6125 		}
6126 		return 0;
6127 	}
6128 
6129 	override void addChild(Widget child, int pos = int.max) {
6130 		if(auto twp = cast(TabWidgetPage) child) {
6131 			super.addChild(child, pos);
6132 			if(pos == int.max)
6133 				pos = cast(int) this.children.length - 1;
6134 
6135 			version(win32_widgets) {
6136 				TCITEM item;
6137 				item.mask = TCIF_TEXT;
6138 				WCharzBuffer buf = WCharzBuffer(twp.title);
6139 				item.pszText = buf.ptr;
6140 				SendMessage(hwnd, TCM_INSERTITEM, pos, cast(LPARAM) &item);
6141 			} else version(custom_widgets) {
6142 			}
6143 
6144 			if(pos != getCurrentTab) {
6145 				child.showing = false;
6146 			}
6147 		} else {
6148 			assert(0, "Don't add children directly to a tab widget, instead add them to a page (see addPage)");
6149 		}
6150 	}
6151 
6152 	override void recomputeChildLayout() {
6153 		version(win32_widgets) {
6154 			this.registerMovement();
6155 
6156 			RECT rect;
6157 			GetWindowRect(hwnd, &rect);
6158 
6159 			auto left = rect.left;
6160 			auto top = rect.top;
6161 
6162 			TabCtrl_AdjustRect(hwnd, false, &rect);
6163 			foreach(child; children) {
6164 				if(!child.showing) continue;
6165 				child.x = rect.left - left;
6166 				child.y = rect.top - top;
6167 				child.width = rect.right - rect.left;
6168 				child.height = rect.bottom - rect.top;
6169 				child.recomputeChildLayout();
6170 			}
6171 		} else version(custom_widgets) {
6172 			this.registerMovement();
6173 			foreach(child; children) {
6174 				if(!child.showing) continue;
6175 				child.x = 2;
6176 				child.y = tabBarHeight + 2; // for the border
6177 				child.width = width - 4; // for the border
6178 				child.height = height - tabBarHeight - 2 - 2; // for the border
6179 				child.recomputeChildLayout();
6180 			}
6181 		} else static assert(0);
6182 	}
6183 
6184 	version(custom_widgets) {
6185 		private int currentTab_;
6186 		private int tabBarHeight() { return defaultLineHeight; }
6187 		int tabWidth = 80;
6188 	}
6189 
6190 	version(win32_widgets)
6191 	override void paint(WidgetPainter painter) {}
6192 
6193 	version(custom_widgets)
6194 	override void paint(WidgetPainter painter) {
6195 		auto cs = getComputedStyle();
6196 
6197 		draw3dFrame(0, tabBarHeight - 2, width, height - tabBarHeight + 2, painter, FrameStyle.risen, cs.background.color);
6198 
6199 		int posX = 0;
6200 		foreach(idx, child; children) {
6201 			if(auto twp = cast(TabWidgetPage) child) {
6202 				auto isCurrent = idx == getCurrentTab();
6203 
6204 				painter.setClipRectangle(Point(posX, 0), tabWidth, tabBarHeight);
6205 
6206 				draw3dFrame(posX, 0, tabWidth, tabBarHeight, painter, isCurrent ? FrameStyle.risen : FrameStyle.sunk, isCurrent ? cs.windowBackgroundColor : darken(cs.windowBackgroundColor, 0.1));
6207 				painter.outlineColor = cs.foregroundColor;
6208 				painter.drawText(Point(posX + 4, 2), twp.title);
6209 
6210 				if(isCurrent) {
6211 					painter.outlineColor = cs.windowBackgroundColor;
6212 					painter.fillColor = Color.transparent;
6213 					painter.drawLine(Point(posX + 2, tabBarHeight - 1), Point(posX + tabWidth, tabBarHeight - 1));
6214 					painter.drawLine(Point(posX + 2, tabBarHeight - 2), Point(posX + tabWidth, tabBarHeight - 2));
6215 
6216 					painter.outlineColor = Color.white;
6217 					painter.drawPixel(Point(posX + 1, tabBarHeight - 1));
6218 					painter.drawPixel(Point(posX + 1, tabBarHeight - 2));
6219 					painter.outlineColor = cs.activeTabColor;
6220 					painter.drawPixel(Point(posX, tabBarHeight - 1));
6221 				}
6222 
6223 				posX += tabWidth - 2;
6224 			}
6225 		}
6226 	}
6227 
6228 	///
6229 	@scriptable
6230 	void setCurrentTab(int item) {
6231 		version(win32_widgets)
6232 			TabCtrl_SetCurSel(hwnd, item);
6233 		else version(custom_widgets)
6234 			currentTab_ = item;
6235 		else static assert(0);
6236 
6237 		showOnly(item);
6238 	}
6239 
6240 	///
6241 	@scriptable
6242 	int getCurrentTab() {
6243 		version(win32_widgets)
6244 			return TabCtrl_GetCurSel(hwnd);
6245 		else version(custom_widgets)
6246 			return currentTab_; // FIXME
6247 		else static assert(0);
6248 	}
6249 
6250 	///
6251 	@scriptable
6252 	void removeTab(int item) {
6253 		if(item && item == getCurrentTab())
6254 			setCurrentTab(item - 1);
6255 
6256 		version(win32_widgets) {
6257 			TabCtrl_DeleteItem(hwnd, item);
6258 		}
6259 
6260 		for(int a = item; a < children.length - 1; a++)
6261 			this._children[a] = this._children[a + 1];
6262 		this._children = this._children[0 .. $-1];
6263 	}
6264 
6265 	///
6266 	@scriptable
6267 	TabWidgetPage addPage(string title) {
6268 		return new TabWidgetPage(title, this);
6269 	}
6270 
6271 	private void showOnly(int item) {
6272 		foreach(idx, child; children) {
6273 			child.showing(false, false); // batch the recalculates for the end
6274 		}
6275 
6276 		foreach(idx, child; children) {
6277 			if(idx == item) {
6278 				child.showing(true, false);
6279 				if(parentWindow) {
6280 					auto f = parentWindow.getFirstFocusable(child);
6281 					if(f)
6282 						f.focus();
6283 				}
6284 				recomputeChildLayout();
6285 			}
6286 		}
6287 
6288 		version(win32_widgets) {
6289 			InvalidateRect(hwnd, null, true);
6290 		} else version(custom_widgets) {
6291 			this.redraw();
6292 		}
6293 	}
6294 }
6295 
6296 /++
6297 	A page widget is basically a tab widget with hidden tabs.
6298 
6299 	You add [TabWidgetPage]s to it.
6300 +/
6301 class PageWidget : Widget {
6302 	this(Widget parent) {
6303 		super(parent);
6304 	}
6305 
6306 	override int minHeight() {
6307 		int max = 0;
6308 		foreach(child; children)
6309 			max = mymax(child.minHeight, max);
6310 
6311 		return max;
6312 	}
6313 
6314 
6315 	override void addChild(Widget child, int pos = int.max) {
6316 		if(auto twp = cast(TabWidgetPage) child) {
6317 			super.addChild(child, pos);
6318 			if(pos == int.max)
6319 				pos = cast(int) this.children.length - 1;
6320 
6321 			if(pos != getCurrentTab) {
6322 				child.showing = false;
6323 			}
6324 		} else {
6325 			assert(0, "Don't add children directly to a page widget, instead add them to a page (see addPage)");
6326 		}
6327 	}
6328 
6329 	override void recomputeChildLayout() {
6330 		this.registerMovement();
6331 		foreach(child; children) {
6332 			child.x = 0;
6333 			child.y = 0;
6334 			child.width = width;
6335 			child.height = height;
6336 			child.recomputeChildLayout();
6337 		}
6338 	}
6339 
6340 	private int currentTab_;
6341 
6342 	///
6343 	@scriptable
6344 	void setCurrentTab(int item) {
6345 		currentTab_ = item;
6346 
6347 		showOnly(item);
6348 	}
6349 
6350 	///
6351 	@scriptable
6352 	int getCurrentTab() {
6353 		return currentTab_;
6354 	}
6355 
6356 	///
6357 	@scriptable
6358 	void removeTab(int item) {
6359 		if(item && item == getCurrentTab())
6360 			setCurrentTab(item - 1);
6361 
6362 		for(int a = item; a < children.length - 1; a++)
6363 			this._children[a] = this._children[a + 1];
6364 		this._children = this._children[0 .. $-1];
6365 	}
6366 
6367 	///
6368 	@scriptable
6369 	TabWidgetPage addPage(string title) {
6370 		return new TabWidgetPage(title, this);
6371 	}
6372 
6373 	private void showOnly(int item) {
6374 		foreach(idx, child; children)
6375 			if(idx == item) {
6376 				child.show();
6377 				child.recomputeChildLayout();
6378 			} else {
6379 				child.hide();
6380 			}
6381 	}
6382 
6383 }
6384 
6385 /++
6386 
6387 +/
6388 class TabWidgetPage : Widget {
6389 	string title;
6390 	this(string title, Widget parent) {
6391 		this.title = title;
6392 		this.tabStop = false;
6393 		super(parent);
6394 
6395 		///*
6396 		version(win32_widgets) {
6397 			createWin32Window(this, Win32Class!"arsd_minigui_TabWidgetPage"w, "", 0);
6398 		}
6399 		//*/
6400 	}
6401 
6402 	override int minHeight() {
6403 		int sum = 0;
6404 		foreach(child; children)
6405 			sum += child.minHeight();
6406 		return sum;
6407 	}
6408 }
6409 
6410 version(none)
6411 /++
6412 	A collapsable sidebar is a container that shows if its assigned width is greater than its minimum and otherwise shows as a button.
6413 
6414 	I think I need to modify the layout algorithms to support this.
6415 +/
6416 class CollapsableSidebar : Widget {
6417 
6418 }
6419 
6420 /// Stacks the widgets vertically, taking all the available width for each child.
6421 class VerticalLayout : Layout {
6422 	// most of this is intentionally blank - widget's default is vertical layout right now
6423 	///
6424 	this(Widget parent) { super(parent); }
6425 }
6426 
6427 /// Stacks the widgets horizontally, taking all the available height for each child.
6428 class HorizontalLayout : Layout {
6429 	///
6430 	this(Widget parent) { super(parent); }
6431 	override void recomputeChildLayout() {
6432 		.recomputeChildLayout!"width"(this);
6433 	}
6434 
6435 	override int minHeight() {
6436 		int largest = 0;
6437 		int margins = 0;
6438 		int lastMargin = 0;
6439 		foreach(child; children) {
6440 			auto mh = child.minHeight();
6441 			if(mh > largest)
6442 				largest = mh;
6443 			margins += mymax(lastMargin, child.marginTop());
6444 			lastMargin = child.marginBottom();
6445 		}
6446 		return largest + margins;
6447 	}
6448 
6449 	override int maxHeight() {
6450 		int largest = 0;
6451 		int margins = 0;
6452 		int lastMargin = 0;
6453 		foreach(child; children) {
6454 			auto mh = child.maxHeight();
6455 			if(mh == int.max)
6456 				return int.max;
6457 			if(mh > largest)
6458 				largest = mh;
6459 			margins += mymax(lastMargin, child.marginTop());
6460 			lastMargin = child.marginBottom();
6461 		}
6462 		return largest + margins;
6463 	}
6464 
6465 	override int heightStretchiness() {
6466 		int max;
6467 		foreach(child; children) {
6468 			auto c = child.heightStretchiness;
6469 			if(c > max)
6470 				max = c;
6471 		}
6472 		return max;
6473 	}
6474 
6475 }
6476 
6477 private wstring Win32Class(wstring name)() {
6478 	static bool classRegistered;
6479 	if(!classRegistered) {
6480 		HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
6481 		WNDCLASSEX wc;
6482 		wc.cbSize = wc.sizeof;
6483 		wc.hInstance = hInstance;
6484 		wc.hbrBackground = cast(HBRUSH) (COLOR_3DFACE+1); // GetStockObject(WHITE_BRUSH);
6485 		wc.lpfnWndProc = &DefWindowProc;
6486 		wc.lpszClassName = name.ptr;
6487 		if(!RegisterClassExW(&wc))
6488 			throw new Exception("RegisterClass ");// ~ to!string(GetLastError()));
6489 		classRegistered = true;
6490 	}
6491 
6492 		return name;
6493 }
6494 
6495 /+
6496 version(win32_widgets)
6497 extern(Windows)
6498 private
6499 LRESULT CustomDrawWindowProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
6500 	switch(iMessage) {
6501 		case WM_PAINT:
6502 			if(auto te = hWnd in Widget.nativeMapping) {
6503 				try {
6504 					//te.redraw();
6505 					import std.stdio; writeln(te, " drawing");
6506 				} catch(Exception) {}
6507 			}
6508 			return DefWindowProc(hWnd, iMessage, wParam, lParam);
6509 		default:
6510 			return DefWindowProc(hWnd, iMessage, wParam, lParam);
6511 	}
6512 }
6513 +/
6514 
6515 
6516 /++
6517 	A widget specifically designed to hold other widgets.
6518 
6519 	History:
6520 		Added July 1, 2021
6521 +/
6522 class ContainerWidget : Widget {
6523 	this(Widget parent) {
6524 		super(parent);
6525 		this.tabStop = false;
6526 
6527 		version(win32_widgets) {
6528 			createWin32Window(this, Win32Class!"arsd_minigui_ContainerWidget"w, "", 0);
6529 		}
6530 	}
6531 }
6532 
6533 /++
6534 	A widget that takes your widget, puts scroll bars around it, and sends
6535 	messages to it when the user scrolls. Unlike [ScrollableWidget], it makes
6536 	no effort to automatically scroll or clip its child widgets - it just sends
6537 	the messages.
6538 
6539 
6540 	A ScrollMessageWidget notifies you with a [ScrollEvent] that it has changed.
6541 	The scroll coordinates are all given in a unit you interpret as you wish. One
6542 	of these units is moved on each press of the arrow buttons and represents the
6543 	smallest amount the user can scroll. The intention is for this to be one line,
6544 	one item in a list, one row in a table, etc. Whatever makes sense for your widget
6545 	in each direction that the user might be interested in.
6546 
6547 	You can set a "page size" with the [step] property. (Yes, I regret the name...)
6548 	This is the amount it jumps when the user pressed page up and page down, or clicks
6549 	in the exposed part of the scroll bar.
6550 
6551 	You should add child content to the ScrollMessageWidget. However, it is important to
6552 	note that the coordinates are always independent of the scroll position! It is YOUR
6553 	responsibility to do any necessary transforms, clipping, etc., while drawing the
6554 	content and interpreting mouse events if they are supposed to change with the scroll.
6555 	This is in contrast to the (likely to be deprecated) [ScrollableWidget], which tries
6556 	to maintain the illusion that there's an infinite space. The [ScrollMessageWidget] gives
6557 	you more control (which can be considerably more efficient and adapted to your actual data)
6558 	at the expense of you also needing to be aware of its reality.
6559 
6560 	Please note that it does NOT react to mouse wheel events or various keyboard events as of
6561 	version 10.3. Maybe this will change in the future....
6562 +/
6563 class ScrollMessageWidget : Widget {
6564 	this(Widget parent) {
6565 		super(parent);
6566 
6567 		container = new Widget(this);
6568 		hsb = new HorizontalScrollbar(this);
6569 		vsb = new VerticalScrollbar(this);
6570 
6571 		hsb.addEventListener("scrolltonextline", {
6572 			hsb.setPosition(hsb.position + 1);
6573 			notify();
6574 		});
6575 		hsb.addEventListener("scrolltopreviousline", {
6576 			hsb.setPosition(hsb.position - 1);
6577 			notify();
6578 		});
6579 		vsb.addEventListener("scrolltonextline", {
6580 			vsb.setPosition(vsb.position + 1);
6581 			notify();
6582 		});
6583 		vsb.addEventListener("scrolltopreviousline", {
6584 			vsb.setPosition(vsb.position - 1);
6585 			notify();
6586 		});
6587 		hsb.addEventListener("scrolltonextpage", {
6588 			hsb.setPosition(hsb.position + hsb.step_);
6589 			notify();
6590 		});
6591 		hsb.addEventListener("scrolltopreviouspage", {
6592 			hsb.setPosition(hsb.position - hsb.step_);
6593 			notify();
6594 		});
6595 		vsb.addEventListener("scrolltonextpage", {
6596 			vsb.setPosition(vsb.position + vsb.step_);
6597 			notify();
6598 		});
6599 		vsb.addEventListener("scrolltopreviouspage", {
6600 			vsb.setPosition(vsb.position - vsb.step_);
6601 			notify();
6602 		});
6603 		hsb.addEventListener("scrolltoposition", (Event event) {
6604 			hsb.setPosition(event.intValue);
6605 			notify();
6606 		});
6607 		vsb.addEventListener("scrolltoposition", (Event event) {
6608 			vsb.setPosition(event.intValue);
6609 			notify();
6610 		});
6611 
6612 
6613 		tabStop = false;
6614 		container.tabStop = false;
6615 		magic = true;
6616 	}
6617 
6618 	/++
6619 		Add default event listeners for keyboard and mouse wheel scrolling shortcuts.
6620 
6621 
6622 		The defaults for [addDefaultWheelListeners] are:
6623 
6624 			$(LIST
6625 				* Mouse wheel scrolls vertically
6626 				* Alt key + mouse wheel scrolls horiontally
6627 				* Shift + mouse wheel scrolls faster.
6628 				* Any mouse click or wheel event will focus the inner widget if it has `tabStop = true`
6629 			)
6630 
6631 		The defaults for [addDefaultKeyboardListeners] are:
6632 
6633 			$(LIST
6634 				* Arrow keys scroll by the given amounts
6635 				* Shift+arrow keys scroll by the given amounts times the given shiftMultiplier
6636 				* Page up and down scroll by the vertical viewable area
6637 				* Home and end scroll to the start and end of the verticle viewable area.
6638 				* Alt + page up / page down / home / end will horizonally scroll instead of vertical.
6639 			)
6640 
6641 		My recommendation is to change the scroll amounts if you are scrolling by pixels, but otherwise keep them at one line.
6642 
6643 		Params:
6644 			horizontalArrowScrollAmount =
6645 			verticalArrowScrollAmount =
6646 			verticalWheelScrollAmount = how much should be scrolled vertically on each tick of the mouse wheel
6647 			horizontalWheelScrollAmount = how much should be scrolled horizontally when alt is held on each tick of the mouse wheel
6648 			shiftMultiplier = multiplies the scroll amount by this when shift is held
6649 	+/
6650 	void addDefaultKeyboardListeners(int verticalArrowScrollAmount = 1, int horizontalArrowScrollAmount = 1, int shiftMultiplier = 3) {
6651 		auto _this = this;
6652 
6653 		container.addEventListener((scope KeyDownEvent ke) {
6654 			switch(ke.key) {
6655 				case Key.Left:
6656 					_this.scrollLeft(horizontalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
6657 				break;
6658 				case Key.Right:
6659 					_this.scrollRight(horizontalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
6660 				break;
6661 				case Key.Up:
6662 					_this.scrollUp(verticalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
6663 				break;
6664 				case Key.Down:
6665 					_this.scrollDown(verticalArrowScrollAmount * (ke.shiftKey ? shiftMultiplier : 1));
6666 				break;
6667 				case Key.PageUp:
6668 					if(ke.altKey)
6669 						_this.scrollLeft(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
6670 					else
6671 						_this.scrollUp(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
6672 				break;
6673 				case Key.PageDown:
6674 					if(ke.altKey)
6675 						_this.scrollRight(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
6676 					else
6677 						_this.scrollDown(_this.vsb.viewableArea_ * (ke.shiftKey ? shiftMultiplier : 1));
6678 				break;
6679 				case Key.Home:
6680 					if(ke.altKey)
6681 						_this.scrollLeft(short.max * 16);
6682 					else
6683 						_this.scrollUp(short.max * 16);
6684 				break;
6685 				case Key.End:
6686 					if(ke.altKey)
6687 						_this.scrollRight(short.max * 16);
6688 					else
6689 						_this.scrollDown(short.max * 16);
6690 				break;
6691 
6692 				default:
6693 					// ignore, not for us.
6694 			}
6695 
6696 		});
6697 	}
6698 
6699 	/// ditto
6700 	void addDefaultWheelListeners(int verticalWheelScrollAmount = 1, int horizontalWheelScrollAmount = 1, int shiftMultiplier = 3) {
6701 		auto _this = this;
6702 		container.addEventListener((scope ClickEvent ce) {
6703 
6704 			if(ce.target && ce.target.tabStop)
6705 				ce.target.focus();
6706 
6707 			// ctrl is reserved for the application
6708 			if(ce.ctrlKey)
6709 				return;
6710 
6711 			if(horizontalWheelScrollAmount == 0 && ce.altKey)
6712 				return;
6713 
6714 			if(shiftMultiplier == 0 && ce.shiftKey)
6715 				return;
6716 
6717 			if(ce.button == MouseButton.wheelDown) {
6718 				if(ce.altKey)
6719 					_this.scrollRight(horizontalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
6720 				else
6721 					_this.scrollDown(verticalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
6722 			} else if(ce.button == MouseButton.wheelUp) {
6723 				if(ce.altKey)
6724 					_this.scrollLeft(horizontalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
6725 				else
6726 					_this.scrollUp(verticalWheelScrollAmount * (ce.shiftKey ? shiftMultiplier : 1));
6727 			}
6728 		});
6729 	}
6730 
6731 	/++
6732 		Scrolls the given amount.
6733 
6734 		History:
6735 			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.
6736 	+/
6737 	void scrollUp(int amount = 1) {
6738 		vsb.setPosition(vsb.position - amount);
6739 		notify();
6740 	}
6741 	/// ditto
6742 	void scrollDown(int amount = 1) {
6743 		vsb.setPosition(vsb.position + amount);
6744 		notify();
6745 	}
6746 	/// ditto
6747 	void scrollLeft(int amount = 1) {
6748 		hsb.setPosition(hsb.position - amount);
6749 		notify();
6750 	}
6751 	/// ditto
6752 	void scrollRight(int amount = 1) {
6753 		hsb.setPosition(hsb.position + amount);
6754 		notify();
6755 	}
6756 
6757 	///
6758 	VerticalScrollbar verticalScrollBar() { return vsb; }
6759 	///
6760 	HorizontalScrollbar horizontalScrollBar() { return hsb; }
6761 
6762 	void notify() {
6763 		this.emit!ScrollEvent();
6764 	}
6765 
6766 	mixin Emits!ScrollEvent;
6767 
6768 	///
6769 	Point position() {
6770 		return Point(hsb.position, vsb.position);
6771 	}
6772 
6773 	///
6774 	void setPosition(int x, int y) {
6775 		hsb.setPosition(x);
6776 		vsb.setPosition(y);
6777 	}
6778 
6779 	///
6780 	void setPageSize(int unitsX, int unitsY) {
6781 		hsb.setStep(unitsX);
6782 		vsb.setStep(unitsY);
6783 	}
6784 
6785 	///
6786 	void setTotalArea(int width, int height) {
6787 		hsb.setMax(width);
6788 		vsb.setMax(height);
6789 	}
6790 
6791 	///
6792 	void setViewableArea(int width, int height) {
6793 		hsb.setViewableArea(width);
6794 		vsb.setViewableArea(height);
6795 
6796 		bool needsNotify = false;
6797 
6798 		// FIXME: if at any point the rhs is outside the scrollbar, we need
6799 		// to reset to 0. but it should remember the old position in case the
6800 		// window resizes again, so it can kinda return ot where it was.
6801 		//
6802 		// so there's an inner position and a exposed position. the exposed one is always in bounds and thus may be (0,0)
6803 		if(width > hsb.max) {
6804 			// there's plenty of room to display it all so we need to reset to zero
6805 			// FIXME: adjust so it matches the note above
6806 			hsb.setPosition(0);
6807 			needsNotify = true;
6808 		}
6809 		if(height > vsb.max) {
6810 			// there's plenty of room to display it all so we need to reset to zero
6811 			// FIXME: adjust so it matches the note above
6812 			vsb.setPosition(0);
6813 			needsNotify = true;
6814 		}
6815 		if(needsNotify)
6816 			notify();
6817 	}
6818 
6819 	private bool magic;
6820 	override void addChild(Widget w, int position = int.max) {
6821 		if(magic)
6822 			container.addChild(w, position);
6823 		else
6824 			super.addChild(w, position);
6825 	}
6826 
6827 	override void recomputeChildLayout() {
6828 		if(hsb is null || vsb is null || container is null) return;
6829 
6830 		registerMovement();
6831 
6832 		hsb.height = 16; // FIXME? are tese 16s sane?
6833 		hsb.x = 0;
6834 		hsb.y = this.height - hsb.height;
6835 		hsb.width = this.width - 16;
6836 		hsb.recomputeChildLayout();
6837 
6838 		vsb.width = 16; // FIXME?
6839 		vsb.x = this.width - vsb.width;
6840 		vsb.y = 0;
6841 		vsb.height = this.height - 16;
6842 		vsb.recomputeChildLayout();
6843 
6844 		if(this.header is null) {
6845 			container.x = 0;
6846 			container.y = 0;
6847 			container.width = this.width - vsb.width;
6848 			container.height = this.height - hsb.height;
6849 			container.recomputeChildLayout();
6850 		} else {
6851 			header.x = 0;
6852 			header.y = 0;
6853 			header.width = this.width - vsb.width;
6854 			header.height = 16; // size of the button
6855 			header.recomputeChildLayout();
6856 
6857 			container.x = 0;
6858 			container.y = 16;
6859 			container.width = this.width - vsb.width;
6860 			container.height = this.height - hsb.height - 16;
6861 			container.recomputeChildLayout();
6862 		}
6863 	}
6864 
6865 	HorizontalScrollbar hsb;
6866 	VerticalScrollbar vsb;
6867 	Widget container;
6868 	private Widget header;
6869 
6870 	/++
6871 		Adds a fixed-size "header" widget. This will be positioned to align with the scroll up button.
6872 
6873 		History:
6874 			Added September 27, 2021 (dub v10.3)
6875 	+/
6876 	Widget getHeader() {
6877 		if(this.header is null) {
6878 			magic = false;
6879 			scope(exit) magic = true;
6880 			this.header = new Widget(this);
6881 			recomputeChildLayout();
6882 		}
6883 		return this.header;
6884 	}
6885 }
6886 
6887 /++
6888 	Bypasses automatic layout for its children, using manual positioning and sizing only.
6889 	While you need to manually position them, you must ensure they are inside the StaticLayout's
6890 	bounding box to avoid undefined behavior.
6891 
6892 	You should almost never use this.
6893 +/
6894 class StaticLayout : Layout {
6895 	///
6896 	this(Widget parent) { super(parent); }
6897 	override void recomputeChildLayout() {
6898 		registerMovement();
6899 		foreach(child; children)
6900 			child.recomputeChildLayout();
6901 	}
6902 }
6903 
6904 /++
6905 	Bypasses automatic positioning when being laid out. It is your responsibility to make
6906 	room for this widget in the parent layout.
6907 
6908 	Its children are laid out normally, unless there is exactly one, in which case it takes
6909 	on the full size of the `StaticPosition` object (if you plan to put stuff on the edge, you
6910 	can do that with `padding`).
6911 +/
6912 class StaticPosition : Layout {
6913 	///
6914 	this(Widget parent) { super(parent); }
6915 
6916 	override void recomputeChildLayout() {
6917 		registerMovement();
6918 		if(this.children.length == 1) {
6919 			auto child = children[0];
6920 			child.x = 0;
6921 			child.y = 0;
6922 			child.width = this.width;
6923 			child.height = this.height;
6924 			child.recomputeChildLayout();
6925 		} else
6926 		foreach(child; children)
6927 			child.recomputeChildLayout();
6928 	}
6929 
6930 	alias width = typeof(super).width;
6931 	alias height = typeof(super).height;
6932 
6933 	@property int width(int w) @nogc pure @safe nothrow {
6934 		return this._width = w;
6935 	}
6936 
6937 	@property int height(int w) @nogc pure @safe nothrow {
6938 		return this._height = w;
6939 	}
6940 
6941 }
6942 
6943 /++
6944 	FixedPosition is like [StaticPosition], but its coordinates
6945 	are always relative to the viewport, meaning they do not scroll with
6946 	the parent content.
6947 +/
6948 class FixedPosition : StaticPosition {
6949 	///
6950 	this(Widget parent) { super(parent); }
6951 }
6952 
6953 version(win32_widgets)
6954 int processWmCommand(HWND parentWindow, HWND handle, ushort cmd, ushort idm) {
6955 	if(true) {
6956 		// cmd == 0 = menu, cmd == 1 = accelerator
6957 		if(auto item = idm in Action.mapping) {
6958 			foreach(handler; (*item).triggered)
6959 				handler();
6960 		/*
6961 			auto event = new Event("triggered", *item);
6962 			event.button = idm;
6963 			event.dispatch();
6964 		*/
6965 			return 0;
6966 		}
6967 	}
6968 	if(handle)
6969 	if(auto widgetp = handle in Widget.nativeMapping) {
6970 		(*widgetp).handleWmCommand(cmd, idm);
6971 		return 0;
6972 	}
6973 	return 1;
6974 }
6975 
6976 
6977 ///
6978 class Window : Widget {
6979 	int mouseCaptureCount = 0;
6980 	Widget mouseCapturedBy;
6981 	void captureMouse(Widget byWhom) {
6982 		assert(mouseCapturedBy is null || byWhom is mouseCapturedBy);
6983 		mouseCaptureCount++;
6984 		mouseCapturedBy = byWhom;
6985 		win.grabInput();
6986 	}
6987 	void releaseMouseCapture() {
6988 		mouseCaptureCount--;
6989 		mouseCapturedBy = null;
6990 		win.releaseInputGrab();
6991 	}
6992 
6993 	///
6994 	@scriptable
6995 	@property bool focused() {
6996 		return win.focused;
6997 	}
6998 
6999 	static class Style : Widget.Style {
7000 		override WidgetBackground background() {
7001 			version(custom_widgets)
7002 				return WidgetBackground(WidgetPainter.visualTheme.windowBackgroundColor);
7003 			else version(win32_widgets)
7004 				return WidgetBackground(Color.transparent);
7005 			else static assert(0);
7006 		}
7007 	}
7008 	mixin OverrideStyle!Style;
7009 
7010 	/++
7011 		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.
7012 	+/
7013 	static int lineHeight() {
7014 		OperatingSystemFont font;
7015 		if(auto vt = WidgetPainter.visualTheme) {
7016 			font = vt.defaultFontCached();
7017 		}
7018 
7019 		if(font is null) {
7020 			static int defaultHeightCache;
7021 			if(defaultHeightCache == 0) {
7022 				font = new OperatingSystemFont;
7023 				font.loadDefault;
7024 				defaultHeightCache = font.height() * 5 / 4;
7025 			}
7026 			return defaultHeightCache;
7027 		}
7028 
7029 		return font.height() * 5 / 4;
7030 	}
7031 
7032 	Widget focusedWidget;
7033 
7034 	private SimpleWindow win_;
7035 
7036 	@property {
7037 		/++
7038 			Provides access to the underlying [SimpleWindow]. Note that changing properties on this window may disconnect minigui's event dispatchers.
7039 
7040 			History:
7041 				Prior to June 21, 2021, it was a public (but undocumented) member. Now it a semi-protected property.
7042 		+/
7043 		public SimpleWindow win() {
7044 			return win_;
7045 		}
7046 		///
7047 		protected void win(SimpleWindow w) {
7048 			win_ = w;
7049 		}
7050 	}
7051 
7052 	/// YOU ALMOST CERTAINLY SHOULD NOT USE THIS. This is really only for special purposes like pseudowindows or popup windows doing their own thing.
7053 	this(Widget p) {
7054 		tabStop = false;
7055 		super(p);
7056 	}
7057 
7058 
7059 
7060 	private void actualRedraw() {
7061 		if(recomputeChildLayoutRequired)
7062 			recomputeChildLayoutEntry();
7063 		if(!showing) return;
7064 
7065 		assert(parentWindow !is null);
7066 
7067 		auto w = drawableWindow;
7068 		if(w is null)
7069 			w = parentWindow.win;
7070 
7071 		if(w.closed())
7072 			return;
7073 
7074 		auto ugh = this.parent;
7075 		int lox, loy;
7076 		while(ugh) {
7077 			lox += ugh.x;
7078 			loy += ugh.y;
7079 			ugh = ugh.parent;
7080 		}
7081 		auto painter = w.draw();
7082 		privatePaint(WidgetPainter(painter, this), lox, loy, Rectangle(0, 0, int.max, int.max));
7083 	}
7084 
7085 
7086 	private bool skipNextChar = false;
7087 
7088 	/++
7089 		Creates a window from an existing [SimpleWindow]. This constructor attaches various event handlers to the SimpleWindow object which may overwrite your existing handlers.
7090 
7091 		This constructor is intended primarily for internal use and may be changed to `protected` later.
7092 	+/
7093 	this(SimpleWindow win) {
7094 
7095 		static if(UsingSimpledisplayX11) {
7096 			win.discardAdditionalConnectionState = &discardXConnectionState;
7097 			win.recreateAdditionalConnectionState = &recreateXConnectionState;
7098 		}
7099 
7100 		tabStop = false;
7101 		super(null);
7102 		this.win = win;
7103 
7104 		win.addEventListener((Widget.RedrawEvent) {
7105 			if(win.eventQueued!RecomputeEvent) {
7106 				// import std.stdio; writeln("skipping");
7107 				return; // let the recompute event do the actual redraw
7108 			}
7109 			this.actualRedraw();
7110 		});
7111 
7112 		win.addEventListener((Widget.RecomputeEvent) {
7113 			recomputeChildLayoutEntry();
7114 			if(win.eventQueued!RedrawEvent)
7115 				return; // let the queued one do it
7116 			else {
7117 				// import std.stdio; writeln("drawing");
7118 				this.actualRedraw(); // if not queued, it needs to be done now anyway
7119 			}
7120 		});
7121 
7122 		this.width = win.width;
7123 		this.height = win.height;
7124 		this.parentWindow = this;
7125 
7126 		win.closeQuery = () {
7127 			if(this.emit!ClosingEvent())
7128 				win.close();
7129 		};
7130 		win.onClosing = () {
7131 			this.emit!ClosedEvent();
7132 		};
7133 
7134 		win.windowResized = (int w, int h) {
7135 			this.width = w;
7136 			this.height = h;
7137 			recomputeChildLayout();
7138 			version(win32_widgets)
7139 				InvalidateRect(hwnd, null, true);
7140 			redraw();
7141 		};
7142 
7143 		win.onFocusChange = (bool getting) {
7144 			if(this.focusedWidget) {
7145 				if(getting) {
7146 					this.focusedWidget.emit!FocusEvent();
7147 					this.focusedWidget.emit!FocusInEvent();
7148 				} else {
7149 					this.focusedWidget.emit!BlurEvent();
7150 					this.focusedWidget.emit!FocusOutEvent();
7151 				}
7152 			}
7153 
7154 			if(getting) {
7155 				this.emit!FocusEvent();
7156 				this.emit!FocusInEvent();
7157 			} else {
7158 				this.emit!BlurEvent();
7159 				this.emit!FocusOutEvent();
7160 			}
7161 		};
7162 
7163 		win.onDpiChanged = {
7164 			this.queueRecomputeChildLayout();
7165 		};
7166 
7167 		win.setEventHandlers(
7168 			(MouseEvent e) {
7169 				dispatchMouseEvent(e);
7170 			},
7171 			(KeyEvent e) {
7172 				//import std.stdio;
7173 				//writefln("%x   %s", cast(uint) e.key, e.key);
7174 				dispatchKeyEvent(e);
7175 			},
7176 			(dchar e) {
7177 				if(e == 13) e = 10; // hack?
7178 				if(e == 127) return; // linux sends this, windows doesn't. we don't want it.
7179 				dispatchCharEvent(e);
7180 			},
7181 		);
7182 
7183 		addEventListener("char", (Widget, Event ev) {
7184 			if(skipNextChar) {
7185 				ev.preventDefault();
7186 				skipNextChar = false;
7187 			}
7188 		});
7189 
7190 		version(win32_widgets)
7191 		win.handleNativeEvent = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam, out int mustReturn) {
7192 			if(hwnd !is this.win.impl.hwnd)
7193 				return 1; // we don't care... pass it on
7194 			auto ret = WindowProcedureHelper(this, hwnd, msg, wParam, lParam, mustReturn);
7195 			if(mustReturn)
7196 				return ret;
7197 			return 1; // pass it on
7198 		};
7199 	}
7200 
7201 	version(win32_widgets)
7202 	override void paint(WidgetPainter painter) {
7203 		/*
7204 		RECT rect;
7205 		rect.right = this.width;
7206 		rect.bottom = this.height;
7207 		DrawThemeBackground(theme, painter.impl.hdc, 4, 1, &rect, null);
7208 		*/
7209 		// 3dface is used as window backgrounds by Windows too, so that's why I'm using it here
7210 		auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
7211 		auto p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
7212 		// since the pen is null, to fill the whole space, we need the +1 on both.
7213 		gdi.Rectangle(painter.impl.hdc, 0, 0, this.width + 1, this.height + 1);
7214 		SelectObject(painter.impl.hdc, p);
7215 		SelectObject(painter.impl.hdc, b);
7216 	}
7217 	version(custom_widgets)
7218 	override void paint(WidgetPainter painter) {
7219 		auto cs = getComputedStyle();
7220 		painter.fillColor = cs.windowBackgroundColor;
7221 		painter.outlineColor = cs.windowBackgroundColor;
7222 		painter.drawRectangle(Point(0, 0), this.width, this.height);
7223 	}
7224 
7225 
7226 	override void defaultEventHandler_keydown(KeyDownEvent event) {
7227 		Widget _this = event.target;
7228 
7229 		if(event.key == Key.Tab) {
7230 			/* Window tab ordering is a recursive thingy with each group */
7231 
7232 			// FIXME inefficient
7233 			Widget[] helper(Widget p) {
7234 				if(p.hidden)
7235 					return null;
7236 				Widget[] childOrdering;
7237 
7238 				auto children = p.children.dup;
7239 
7240 				while(true) {
7241 					// UIs should be generally small, so gonna brute force it a little
7242 					// note that it must be a stable sort here; if all are index 0, it should be in order of declaration
7243 
7244 					Widget smallestTab;
7245 					foreach(ref c; children) {
7246 						if(c is null) continue;
7247 						if(smallestTab is null || c.tabOrder < smallestTab.tabOrder) {
7248 							smallestTab = c;
7249 							c = null;
7250 						}
7251 					}
7252 					if(smallestTab !is null) {
7253 						if(smallestTab.tabStop && !smallestTab.hidden)
7254 							childOrdering ~= smallestTab;
7255 						if(!smallestTab.hidden)
7256 							childOrdering ~= helper(smallestTab);
7257 					} else
7258 						break;
7259 
7260 				}
7261 
7262 				return childOrdering;
7263 			}
7264 
7265 			Widget[] tabOrdering = helper(this);
7266 
7267 			Widget recipient;
7268 
7269 			if(tabOrdering.length) {
7270 				bool seenThis = false;
7271 				Widget previous;
7272 				foreach(idx, child; tabOrdering) {
7273 					if(child is focusedWidget) {
7274 
7275 						if(event.shiftKey) {
7276 							if(idx == 0)
7277 								recipient = tabOrdering[$-1];
7278 							else
7279 								recipient = tabOrdering[idx - 1];
7280 							break;
7281 						}
7282 
7283 						seenThis = true;
7284 						if(idx + 1 == tabOrdering.length) {
7285 							// we're at the end, either move to the next group
7286 							// or start back over
7287 							recipient = tabOrdering[0];
7288 						}
7289 						continue;
7290 					}
7291 					if(seenThis) {
7292 						recipient = child;
7293 						break;
7294 					}
7295 					previous = child;
7296 				}
7297 			}
7298 
7299 			if(recipient !is null) {
7300 				// import std.stdio; writeln(typeid(recipient));
7301 				recipient.focus();
7302 
7303 				skipNextChar = true;
7304 			}
7305 		}
7306 
7307 		debug if(event.key == Key.F12) {
7308 			if(devTools) {
7309 				devTools.close();
7310 				devTools = null;
7311 			} else {
7312 				devTools = new DevToolWindow(this);
7313 				devTools.show();
7314 			}
7315 		}
7316 	}
7317 
7318 	debug DevToolWindow devTools;
7319 
7320 
7321 	/++
7322 		Creates a window. Please note windows are created in a hidden state, so you must call [show] or [loop] to get it to display.
7323 
7324 		History:
7325 			Prior to May 12, 2021, the default title was "D Application" (simpledisplay.d's default). After that, the default is `Runtime.args[0]` instead.
7326 
7327 			The width and height arguments were added to the overload that takes `string` first on June 21, 2021.
7328 	+/
7329 	this(int width = 500, int height = 500, string title = null) {
7330 		if(title is null) {
7331 			import core.runtime;
7332 			if(Runtime.args.length)
7333 				title = Runtime.args[0];
7334 		}
7335 		win = new SimpleWindow(width, height, title, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.normal, WindowFlags.dontAutoShow);
7336 
7337 		this(win);
7338 	}
7339 
7340 	/// ditto
7341 	this(string title, int width = 500, int height = 500) {
7342 		this(width, height, title);
7343 	}
7344 
7345 	///
7346 	@scriptable
7347 	void close() {
7348 		win.close();
7349 		// I synchronize here upon window closing to ensure all child windows
7350 		// get updated too before the event loop. This avoids some random X errors.
7351 		static if(UsingSimpledisplayX11) {
7352 			runInGuiThread( {
7353 				XSync(XDisplayConnection.get, false);
7354 			});
7355 		}
7356 	}
7357 
7358 	bool dispatchKeyEvent(KeyEvent ev) {
7359 		auto wid = focusedWidget;
7360 		if(wid is null)
7361 			wid = this;
7362 		KeyEventBase event = ev.pressed ? new KeyDownEvent(wid) : new KeyUpEvent(wid);
7363 		event.originalKeyEvent = ev;
7364 		event.key = ev.key;
7365 		event.state = ev.modifierState;
7366 		event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false;
7367 		event.altKey = (ev.modifierState & ModifierState.alt) ? true : false;
7368 		event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false;
7369 		event.dispatch();
7370 
7371 		return true;
7372 	}
7373 
7374 	bool dispatchCharEvent(dchar ch) {
7375 		if(focusedWidget) {
7376 			auto event = new CharEvent(focusedWidget, ch);
7377 			event.dispatch();
7378 		}
7379 		return true;
7380 	}
7381 
7382 	Widget mouseLastOver;
7383 	Widget mouseLastDownOn;
7384 	bool lastWasDoubleClick;
7385 	bool dispatchMouseEvent(MouseEvent ev) {
7386 		auto eleR = widgetAtPoint(this, ev.x, ev.y);
7387 		auto ele = eleR.widget;
7388 
7389 		auto captureEle = ele;
7390 
7391 		if(mouseCapturedBy !is null) {
7392 			if(ele !is mouseCapturedBy && !mouseCapturedBy.isAParentOf(ele))
7393 				captureEle = mouseCapturedBy;
7394 		}
7395 
7396 		// a hack to get it relative to the widget.
7397 		eleR.x = ev.x;
7398 		eleR.y = ev.y;
7399 		auto pain = captureEle;
7400 		while(pain) {
7401 			eleR.x -= pain.x;
7402 			eleR.y -= pain.y;
7403 			pain.addScrollPosition(eleR.x, eleR.y);
7404 			pain = pain.parent;
7405 		}
7406 
7407 		void populateMouseEventBase(MouseEventBase event) {
7408 			event.button = ev.button;
7409 			event.buttonLinear = ev.buttonLinear;
7410 			event.state = ev.modifierState;
7411 			event.clientX = eleR.x;
7412 			event.clientY = eleR.y;
7413 
7414 			event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false;
7415 			event.altKey = (ev.modifierState & ModifierState.alt) ? true : false;
7416 			event.ctrlKey = (ev.modifierState & ModifierState.ctrl) ? true : false;
7417 		}
7418 
7419 		if(ev.type == MouseEventType.buttonPressed) {
7420 			{
7421 				auto event = new MouseDownEvent(captureEle);
7422 				populateMouseEventBase(event);
7423 				event.dispatch();
7424 			}
7425 
7426 			if(ev.button != MouseButton.wheelDown && ev.button != MouseButton.wheelUp && mouseLastDownOn is ele && ev.doubleClick) {
7427 				auto event = new DoubleClickEvent(captureEle);
7428 				populateMouseEventBase(event);
7429 				event.dispatch();
7430 				lastWasDoubleClick = ev.doubleClick;
7431 			} else {
7432 				lastWasDoubleClick = false;
7433 			}
7434 
7435 			mouseLastDownOn = ele;
7436 		} else if(ev.type == MouseEventType.buttonReleased) {
7437 			{
7438 				auto event = new MouseUpEvent(captureEle);
7439 				populateMouseEventBase(event);
7440 				event.dispatch();
7441 			}
7442 			if(!lastWasDoubleClick && mouseLastDownOn is ele) {
7443 				auto event = new ClickEvent(captureEle);
7444 				populateMouseEventBase(event);
7445 				event.dispatch();
7446 			}
7447 		} else if(ev.type == MouseEventType.motion) {
7448 			// motion
7449 			{
7450 				auto event = new MouseMoveEvent(captureEle);
7451 				populateMouseEventBase(event); // fills in button which is meaningless but meh
7452 				event.dispatch();
7453 			}
7454 
7455 			if(mouseLastOver !is ele) {
7456 				if(ele !is null) {
7457 					if(!isAParentOf(ele, mouseLastOver)) {
7458 						ele.setDynamicState(DynamicState.hover, true);
7459 						auto event = new MouseEnterEvent(ele);
7460 						event.relatedTarget = mouseLastOver;
7461 						event.sendDirectly();
7462 
7463 						ele.useStyleProperties((scope Widget.Style s) {
7464 							ele.parentWindow.win.cursor = s.cursor;
7465 						});
7466 					}
7467 				}
7468 
7469 				if(mouseLastOver !is null) {
7470 					if(!isAParentOf(mouseLastOver, ele)) {
7471 						mouseLastOver.setDynamicState(DynamicState.hover, false);
7472 						auto event = new MouseLeaveEvent(mouseLastOver);
7473 						event.relatedTarget = ele;
7474 						event.sendDirectly();
7475 					}
7476 				}
7477 
7478 				if(ele !is null) {
7479 					auto event = new MouseOverEvent(ele);
7480 					event.relatedTarget = mouseLastOver;
7481 					event.dispatch();
7482 				}
7483 
7484 				if(mouseLastOver !is null) {
7485 					auto event = new MouseOutEvent(mouseLastOver);
7486 					event.relatedTarget = ele;
7487 					event.dispatch();
7488 				}
7489 
7490 				mouseLastOver = ele;
7491 			}
7492 		}
7493 
7494 		return true;
7495 	}
7496 
7497 	/// Shows the window and runs the application event loop.
7498 	@scriptable
7499 	void loop() {
7500 		show();
7501 		win.eventLoop(0);
7502 	}
7503 
7504 	private bool firstShow = true;
7505 
7506 	@scriptable
7507 	override void show() {
7508 		bool rd = false;
7509 		if(firstShow) {
7510 			firstShow = false;
7511 			recomputeChildLayout();
7512 			auto f = getFirstFocusable(this); // FIXME: autofocus?
7513 			if(f)
7514 				f.focus();
7515 			redraw();
7516 		}
7517 		win.show();
7518 		super.show();
7519 	}
7520 	@scriptable
7521 	override void hide() {
7522 		win.hide();
7523 		super.hide();
7524 	}
7525 
7526 	static Widget getFirstFocusable(Widget start) {
7527 		if(start.tabStop && !start.hidden)
7528 			return start;
7529 
7530 		if(!start.hidden)
7531 		foreach(child; start.children) {
7532 			auto f = getFirstFocusable(child);
7533 			if(f !is null)
7534 				return f;
7535 		}
7536 		return null;
7537 	}
7538 
7539 	mixin Emits!ClosingEvent;
7540 	mixin Emits!ClosedEvent;
7541 }
7542 
7543 debug private class DevToolWindow : Window {
7544 	Window p;
7545 
7546 	TextEdit parentList;
7547 	TextEdit logWindow;
7548 	TextLabel clickX, clickY;
7549 
7550 	this(Window p) {
7551 		this.p = p;
7552 		super(400, 300, "Developer Toolbox");
7553 
7554 		logWindow = new TextEdit(this);
7555 		parentList = new TextEdit(this);
7556 
7557 		auto hl = new HorizontalLayout(this);
7558 		clickX = new TextLabel("", TextAlignment.Right, hl);
7559 		clickY = new TextLabel("", TextAlignment.Right, hl);
7560 
7561 		parentListeners ~= p.addEventListener("*", (Event ev) {
7562 			log(typeid(ev.source).name, " emitted ", typeid(ev).name);
7563 		});
7564 
7565 		parentListeners ~= p.addEventListener((ClickEvent ev) {
7566 			auto s = ev.srcElement;
7567 			string list = s.toString();
7568 			s = s.parent;
7569 			while(s) {
7570 				list ~= "\n";
7571 				list ~= s.toString();
7572 				s = s.parent;
7573 			}
7574 			parentList.content = list;
7575 
7576 			clickX.label = toInternal!string(ev.clientX);
7577 			clickY.label = toInternal!string(ev.clientY);
7578 		});
7579 	}
7580 
7581 	EventListener[] parentListeners;
7582 
7583 	override void close() {
7584 		assert(p !is null);
7585 		foreach(p; parentListeners)
7586 			p.disconnect();
7587 		parentListeners = null;
7588 		p.devTools = null;
7589 		p = null;
7590 		super.close();
7591 	}
7592 
7593 	override void defaultEventHandler_keydown(KeyDownEvent ev) {
7594 		if(ev.key == Key.F12) {
7595 			this.close();
7596 			if(p)
7597 				p.devTools = null;
7598 		} else {
7599 			super.defaultEventHandler_keydown(ev);
7600 		}
7601 	}
7602 
7603 	void log(T...)(T t) {
7604 		string str;
7605 		import std.conv;
7606 		foreach(i; t)
7607 			str ~= to!string(i);
7608 		str ~= "\n";
7609 		logWindow.addText(str);
7610 
7611 		version(custom_widgets)
7612 		logWindow.ensureVisibleInScroll(logWindow.textLayout.caretBoundingBox());
7613 	}
7614 }
7615 
7616 /++
7617 	A dialog is a transient window that intends to get information from
7618 	the user before being dismissed.
7619 +/
7620 abstract class Dialog : Window {
7621 	///
7622 	this(int width, int height, string title = null) {
7623 		super(width, height, title);
7624 	}
7625 
7626 	///
7627 	abstract void OK();
7628 
7629 	///
7630 	void Cancel() {
7631 		this.close();
7632 	}
7633 }
7634 
7635 /++
7636 	A custom widget similar to the HTML5 <details> tag.
7637 +/
7638 version(none)
7639 class DetailsView : Widget {
7640 
7641 }
7642 
7643 // FIXME: maybe i should expose the other list views Windows offers too
7644 
7645 /++
7646 	A TableView is a widget made to display a table of data strings.
7647 
7648 
7649 	Future_Directions:
7650 		Each item should be able to take an icon too and maybe I'll allow more of the view modes Windows offers.
7651 
7652 		I will add a selection changed event at some point, as well as item clicked events.
7653 	History:
7654 		Added September 24, 2021. Initial api stabilized in dub v10.4, but it isn't completely feature complete yet.
7655 	See_Also:
7656 		[ListWidget] which displays a list of strings without additional columns.
7657 +/
7658 class TableView : Widget {
7659 	/++
7660 
7661 	+/
7662 	this(Widget parent) {
7663 		super(parent);
7664 
7665 		version(win32_widgets) {
7666 			createWin32Window(this, WC_LISTVIEW, "", LVS_REPORT | LVS_OWNERDATA);//| LVS_OWNERDRAWFIXED);
7667 		} else version(custom_widgets) {
7668 			auto smw = new ScrollMessageWidget(this);
7669 			smw.addDefaultKeyboardListeners();
7670 			smw.addDefaultWheelListeners(1, 16);
7671 			tvwi = new TableViewWidgetInner(this, smw);
7672 		}
7673 	}
7674 
7675 	// FIXME: auto-size columns on double click of header thing like in Windows
7676 	// it need only make the currently displayed things fit well.
7677 
7678 
7679 	private ColumnInfo[] columns;
7680 	private int itemCount;
7681 
7682 	version(custom_widgets) private {
7683 		TableViewWidgetInner tvwi;
7684 	}
7685 
7686 	/// Passed to [setColumnInfo]
7687 	static struct ColumnInfo {
7688 		const(char)[] name; /// the name displayed in the header
7689 		int width; /// the default width, in pixels
7690 		TextAlignment alignment; /// alignment of the text in the cell
7691 
7692 		/++
7693 			After all the pixel widths have been assigned, any left over
7694 			space is divided up among all columns and distributed to according
7695 			to the widthPercent field.
7696 
7697 			For example, if you have two fields, both with width 50 and one with
7698 			widthPercent of 25 and the other with widthPercent of 75, and the
7699 			container is 200 pixels wide, first both get their width of 50.
7700 			then the 100 remaining pixels are split up, so the one gets a total
7701 			of 75 pixels and the other gets a total of 125.
7702 
7703 			This is automatically applied as the window is resized.
7704 
7705 			If there is not enough space - that is, when a horizontal scrollbar
7706 			needs to appear - there are 0 pixels divided up, and thus everyone
7707 			gets 0. This can cause a column to shrink out of proportion when
7708 			passing the scroll threshold.
7709 
7710 			It is important to still set a fixed width (that is, to populate the
7711 			`width` field) even if you use the percents because that will be the
7712 			default minimum in the event of a scroll bar appearing.
7713 
7714 			The percents total in the column can never exceed 100 or be less than 0.
7715 			Doing this will trigger an assert error.
7716 
7717 			History:
7718 				Added November 10, 2021 (dub v10.4)
7719 		+/
7720 		int widthPercent;
7721 
7722 
7723 		private int calculatedWidth;
7724 	}
7725 	/++
7726 		Sets the number of columns along with information about the headers.
7727 
7728 		Please note: on Windows, the first column ignores your alignment preference
7729 		and is always left aligned.
7730 	+/
7731 	void setColumnInfo(ColumnInfo[] columns...) {
7732 
7733 		foreach(ref c; this.columns)
7734 			c.name = c.name.idup;
7735 		this.columns = columns.dup;
7736 
7737 		updateCalculatedWidth(false);
7738 
7739 		version(custom_widgets) {
7740 			tvwi.header.updateHeaders();
7741 			tvwi.updateScrolls();
7742 		} else version(win32_widgets)
7743 		foreach(i, ref column; columns) {
7744 			LVCOLUMN lvColumn;
7745 			lvColumn.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
7746 			lvColumn.cx = column.calculatedWidth;
7747 
7748 			auto bfr = WCharzBuffer(column.name);
7749 			lvColumn.pszText = bfr.ptr;
7750 
7751 			if(column.alignment & TextAlignment.Center)
7752 				lvColumn.fmt = LVCFMT_CENTER;
7753 			else if(column.alignment & TextAlignment.Right)
7754 				lvColumn.fmt = LVCFMT_RIGHT;
7755 			else
7756 				lvColumn.fmt = LVCFMT_LEFT;
7757 
7758 			if(SendMessage(hwnd, LVM_INSERTCOLUMN, cast(WPARAM) i, cast(LPARAM) &lvColumn) == -1)
7759 				throw new WindowsApiException("Insert Column Fail");
7760 		}
7761 	}
7762 
7763 	private void updateCalculatedWidth(bool informWindows) {
7764 
7765 		int remaining = this.width;
7766 		foreach(column; columns)
7767 			remaining -= column.width;
7768 		if(remaining < 0)
7769 			remaining = 0;
7770 
7771 		int percentTotal;
7772 		foreach(i, ref column; columns) {
7773 			percentTotal += column.widthPercent;
7774 			auto c = column.width + (remaining * column.widthPercent) / 100;
7775 			column.calculatedWidth = c;
7776 			version(win32_widgets)
7777 			if(informWindows)
7778 				SendMessage(hwnd, LVM_SETCOLUMNWIDTH, i, c); // LVSCW_AUTOSIZE or LVSCW_AUTOSIZE_USEHEADER are amazing omg
7779 		}
7780 
7781 		assert(percentTotal >= 0, "The total percents in your column definitions were negative. They must add up to something between 0 and 100.");
7782 		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).");
7783 			
7784 
7785 	}
7786 
7787 	override void registerMovement() {
7788 		super.registerMovement();
7789 
7790 		updateCalculatedWidth(true);
7791 	}
7792 
7793 	/++
7794 		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.
7795 	+/
7796 	void setItemCount(int count) {
7797 		this.itemCount = count;
7798 		version(custom_widgets) {
7799 			tvwi.updateScrolls();
7800 			redraw();
7801 		} else version(win32_widgets) {
7802 			SendMessage(hwnd, LVM_SETITEMCOUNT, count, 0);
7803 		}
7804 	}
7805 
7806 	/++
7807 		Clears all items;
7808 	+/
7809 	void clear() {
7810 		this.itemCount = 0;
7811 		this.columns = null;
7812 		version(custom_widgets) {
7813 			tvwi.header.updateHeaders();
7814 			tvwi.updateScrolls();
7815 			redraw();
7816 		} else version(win32_widgets) {
7817 			SendMessage(hwnd, LVM_DELETEALLITEMS, 0, 0);
7818 		}
7819 	}
7820 
7821 	/+
7822 	version(win32_widgets)
7823 	override int handleWmDrawItem(DRAWITEMSTRUCT* dis) 
7824 		auto itemId = dis.itemID;
7825 		auto hdc = dis.hDC;
7826 		auto rect = dis.rcItem;
7827 		switch(dis.itemAction) {
7828 			case ODA_DRAWENTIRE:
7829 
7830 				// FIXME: do other items
7831 				// FIXME: do the focus rectangle i guess
7832 				// FIXME: alignment
7833 				// FIXME: column width
7834 				// FIXME: padding left
7835 				// FIXME: check dpi scaling
7836 				// FIXME: don't owner draw unless it is necessary.
7837 
7838 				auto padding = GetSystemMetrics(SM_CXEDGE); // FIXME: for dpi
7839 				RECT itemRect;
7840 				itemRect.top = 1; // subitem idx, 1-based
7841 				itemRect.left = LVIR_BOUNDS;
7842 
7843 				SendMessage(hwnd, LVM_GETSUBITEMRECT, itemId, cast(LPARAM) &itemRect);
7844 				itemRect.left += padding;
7845 
7846 				getData(itemId, 0, (in char[] data) {
7847 					auto wdata = WCharzBuffer(data);
7848 					DrawTextW(hdc, wdata.ptr, wdata.length, &itemRect, DT_RIGHT| DT_END_ELLIPSIS);
7849 
7850 				});
7851 			goto case;
7852 			case ODA_FOCUS:
7853 				if(dis.itemState & ODS_FOCUS)
7854 					DrawFocusRect(hdc, &rect);
7855 			break;
7856 			case ODA_SELECT:
7857 				// itemState & ODS_SELECTED
7858 			break;
7859 			default:
7860 		}
7861 		return 1;
7862 	}
7863 	+/
7864 
7865 
7866 	version(win32_widgets)
7867 	override int handleWmNotify(NMHDR* hdr, int code, out int mustReturn) {
7868 		switch(code) {
7869 			case NM_CUSTOMDRAW:
7870 				auto s = cast(NMLVCUSTOMDRAW*) hdr;
7871 				switch(s.nmcd.dwDrawStage) {
7872 					case CDDS_PREPAINT:
7873 						if(getCellStyle is null)
7874 							return 0;
7875 
7876 						mustReturn = true;
7877 						return CDRF_NOTIFYITEMDRAW;
7878 					case CDDS_ITEMPREPAINT:
7879 						mustReturn = true;
7880 						return CDRF_NOTIFYSUBITEMDRAW;
7881 					case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
7882 						mustReturn = true;
7883 
7884 						if(getCellStyle is null) // this SHOULD never happen...
7885 							return 0;
7886 
7887 						auto style = getCellStyle(s.nmcd.dwItemSpec, s.iSubItem);
7888 						if(style == CellStyle.init)
7889 							return 0; // allow default processing to continue
7890 
7891 						if(style.flags & CellStyle.Flags.textColorSet)
7892 							s.clrText = style.textColor.asWindowsColorRef;
7893 						else
7894 							s.clrText = 0; // reset in case it was set from last iteration not a fan
7895 						if(style.flags & CellStyle.Flags.backgroundColorSet)
7896 							s.clrTextBk = style.backgroundColor.asWindowsColorRef;
7897 						else
7898 							s.clrTextBk = Color.white.asWindowsColorRef; // need to reset it... not a fan of this
7899 
7900 						return CDRF_NEWFONT;
7901 					default:
7902 						return 0;
7903 
7904 				}
7905 			case NM_RETURN: // no need since i subclass keydown
7906 			break;
7907 			case LVN_COLUMNCLICK:
7908 				auto info = cast(LPNMLISTVIEW) hdr;
7909 				this.emit!HeaderClickedEvent(info.iSubItem);
7910 			break;
7911 			case NM_CLICK:
7912 			case NM_DBLCLK:
7913 			case NM_RCLICK:
7914 			case NM_RDBLCLK:
7915 				// the item/subitem is set here and that can be a useful notification
7916 				// even beyond the normal click notification
7917 			break;
7918 			case LVN_GETDISPINFO:
7919 				LV_DISPINFO* info = cast(LV_DISPINFO*) hdr;
7920 				if(info.item.mask & LVIF_TEXT) {
7921 					if(getData) {
7922 						getData(info.item.iItem, info.item.iSubItem, (in char[] dataReceived) {
7923 							auto bfr = WCharzBuffer(dataReceived);
7924 							auto len = info.item.cchTextMax;
7925 							if(bfr.length < len)
7926 								len = cast(typeof(len)) bfr.length;
7927 							info.item.pszText[0 .. len] = bfr.ptr[0 .. len];
7928 							info.item.pszText[len] = 0;
7929 						});
7930 					} else {
7931 						info.item.pszText[0] = 0;
7932 					}
7933 					//info.item.iItem
7934 					//if(info.item.iSubItem)
7935 				}
7936 			break;
7937 			default:
7938 		}
7939 		return 0;
7940 	}
7941 
7942 	override bool encapsulatedChildren() {
7943 		return true;
7944 	}
7945 
7946 	/++
7947 		Informs the control that content has changed.
7948 
7949 		History:
7950 			Added November 10, 2021 (dub v10.4)
7951 	+/
7952 	void update() {
7953 		version(custom_widgets)
7954 			redraw();
7955 		else {
7956 			SendMessage(hwnd, LVM_REDRAWITEMS, 0, SendMessage(hwnd, LVM_GETITEMCOUNT, 0, 0));
7957 			UpdateWindow(hwnd);
7958 		}
7959 			
7960 
7961 	}
7962 
7963 	/++
7964 		Called by the system to request the text content of an individual cell. You
7965 		should pass the text into the provided `sink` delegate. This function will be
7966 		called for each visible cell as-needed when drawing.
7967 	+/
7968 	void delegate(int row, int column, scope void delegate(in char[]) sink) getData;
7969 
7970 	/++
7971 		Available per-cell style customization options. Use one of the constructors
7972 		provided to set the values conveniently, or default construct it and set individual
7973 		values yourself. Just remember to set the `flags` so your values are actually used.
7974 		If the flag isn't set, the field is ignored and the system default is used instead.
7975 
7976 		This is returned by the [getCellStyle] delegate.
7977 
7978 		Examples:
7979 			---
7980 			// assumes you have a variables called `my_data` which is an array of arrays of numbers
7981 			auto table = new TableView(window);
7982 			// snip: you would set up columns here
7983 
7984 			// this is how you provide data to the table view class
7985 			table.getData = delegate(int row, int column, scope void delegate(in char[]) sink) {
7986 				import std.conv;
7987 				sink(to!string(my_data[row][column]));
7988 			};
7989 
7990 			// and this is how you customize the colors
7991 			table.getCellStyle = delegate(int row, int column) {
7992 				return (my_data[row][column] < 0) ?
7993 					TableView.CellStyle(Color.red); // make negative numbers red
7994 					: TableView.CellStyle.init; // leave the rest alone
7995 			};
7996 			// snip: you would call table.setItemCount here then continue with the rest of your window setup work
7997 			---
7998 
7999 		History:
8000 			Added November 27, 2021 (dub v10.4)
8001 	+/
8002 	struct CellStyle {
8003 		/// 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.
8004 		this(Color textColor) {
8005 			this.textColor = textColor;
8006 			this.flags |= Flags.textColorSet;
8007 		}
8008 		/// Sets a custom text and background color.
8009 		this(Color textColor, Color backgroundColor) {
8010 			this.textColor = textColor;
8011 			this.backgroundColor = backgroundColor;
8012 			this.flags |= Flags.textColorSet | Flags.backgroundColorSet;
8013 		}
8014 
8015 		Color textColor;
8016 		Color backgroundColor;
8017 		int flags; /// bitmask of [Flags]
8018 		/// available options to combine into [flags]
8019 		enum Flags {
8020 			textColorSet = 1 << 0,
8021 			backgroundColorSet = 1 << 1,
8022 		}
8023 	}
8024 	/++
8025 		Companion delegate to [getData] that allows you to custom style each
8026 		cell of the table.
8027 
8028 		Returns:
8029 			A [CellStyle] structure that describes the desired style for the
8030 			given cell. `return CellStyle.init` if you want the default style.
8031 
8032 		History:
8033 			Added November 27, 2021 (dub v10.4)
8034 	+/
8035 	CellStyle delegate(int row, int column) getCellStyle;
8036 
8037 
8038 
8039 	// i want to be able to do things like draw little colored things to show red for negative numbers
8040 	// or background color indicators or even in-cell charts
8041 	// void delegate(int row, int column, WidgetPainter painter, int width, int height, in char[] text) drawCell;
8042 
8043 	/++
8044 		When the user clicks on a header, this event is emitted. It has a meber to identify which header (by index) was clicked.
8045 	+/
8046 	mixin Emits!HeaderClickedEvent;
8047 }
8048 
8049 /++
8050 	This is emitted by the [TableView] when a user clicks on a column header.
8051 
8052 	Its member `columnIndex` has the zero-based index of the column that was clicked.
8053 
8054 	The default behavior of this event is to do nothing, so `preventDefault` has no effect.
8055 
8056 	History:
8057 		Added November 27, 2021 (dub v10.4)
8058 +/
8059 class HeaderClickedEvent : Event {
8060 	enum EventString = "HeaderClicked";
8061 	this(Widget target, int columnIndex) {
8062 		this.columnIndex = columnIndex;
8063 		super(EventString, target);
8064 	}
8065 
8066 	/// The index of the column
8067 	int columnIndex;
8068 
8069 	///
8070 	override @property int intValue() {
8071 		return columnIndex;
8072 	}
8073 }
8074 
8075 version(custom_widgets)
8076 private class TableViewWidgetInner : Widget {
8077 
8078 // wrap this thing in a ScrollMessageWidget
8079 
8080 	TableView tvw;
8081 	ScrollMessageWidget smw;
8082 	HeaderWidget header;
8083 
8084 	this(TableView tvw, ScrollMessageWidget smw) {
8085 		this.tvw = tvw;
8086 		this.smw = smw;
8087 		super(smw);
8088 
8089 		this.tabStop = true;
8090 
8091 		header = new HeaderWidget(this, smw.getHeader());
8092 
8093 		smw.addEventListener("scroll", () {
8094 			this.redraw();
8095 			header.redraw();
8096 		});
8097 
8098 
8099 		// I need headers outside the scroll area but rendered on the same line as the up arrow
8100 		// FIXME: add a fixed header to the SMW
8101 	}
8102 
8103 	void updateScrolls() {
8104 		int w;
8105 		foreach(column; tvw.columns)
8106 			w += column.width;
8107 		smw.setTotalArea(w, tvw.itemCount);
8108 		columnsWidth = w;
8109 	}
8110 
8111 	private int columnsWidth;
8112 
8113 		int lh = 16; // FIXME lineHeight
8114 
8115 	override void registerMovement() {
8116 		super.registerMovement();
8117 		// FIXME: actual column width. it might need to be done per-pixel instead of per-colum
8118 		smw.setViewableArea(this.width, this.height / lh);
8119 	}
8120 
8121 	override Rectangle paintContent(WidgetPainter painter, const Rectangle bounds) {
8122 		int x;
8123 		int y;
8124 
8125 		int row = smw.position.y;
8126 
8127 		enum padding = 3;
8128 
8129 		foreach(lol; 0 .. this.height / lh) {
8130 			if(row >= tvw.itemCount)
8131 				break;
8132 			x = 0;
8133 			foreach(columnNumber, column; tvw.columns) {
8134 				auto x2 = x + column.calculatedWidth;
8135 				auto smwx = smw.position.x;
8136 
8137 				if(x2 > smwx /* if right side of it is visible at all */ || (x >= smwx && x < smwx + this.width) /* left side is visible at all*/) {
8138 					auto startX = x;
8139 					auto endX = x + column.calculatedWidth;
8140 					switch (column.alignment & (TextAlignment.Left | TextAlignment.Center | TextAlignment.Right)) {
8141 						case TextAlignment.Left: startX += padding; break;
8142 						case TextAlignment.Center: startX += padding; endX -= padding; break;
8143 						case TextAlignment.Right: endX -= padding; break;
8144 						default: /* broken */ break;
8145 					}
8146 					tvw.getData(row, cast(int) columnNumber, (info) {
8147 						// auto clip = painter.setClipRectangle(
8148 
8149 						void dotext(WidgetPainter painter) {
8150 							painter.drawText(Point(startX - smw.position.x, y), info, Point(endX - smw.position.x, y + lh), column.alignment);
8151 						}
8152 
8153 						if(tvw.getCellStyle !is null) {
8154 							auto style = tvw.getCellStyle(row, cast(int) columnNumber);
8155 
8156 							if(style.flags & TableView.CellStyle.Flags.backgroundColorSet) {
8157 								auto tempPainter = painter;
8158 								tempPainter.fillColor = style.backgroundColor;
8159 								tempPainter.outlineColor = style.backgroundColor;
8160 
8161 								tempPainter.drawRectangle(Point(startX - smw.position.x, y), 
8162 									Point(endX - smw.position.x, y + lh));
8163 							}
8164 							auto tempPainter = painter;
8165 							if(style.flags & TableView.CellStyle.Flags.textColorSet)
8166 								tempPainter.outlineColor = style.textColor;
8167 
8168 							dotext(tempPainter);
8169 						} else {
8170 							dotext(painter);
8171 						}
8172 					});
8173 				}
8174 
8175 				x += column.calculatedWidth;
8176 			}
8177 			row++;
8178 			y += lh;
8179 		}
8180 		return bounds;
8181 	}
8182 
8183 	static class Style : Widget.Style {
8184 		override WidgetBackground background() {
8185 			return WidgetBackground(WidgetPainter.visualTheme.widgetBackgroundColor);
8186 		}
8187 	}
8188 	mixin OverrideStyle!Style;
8189 
8190 	private static class HeaderWidget : Widget {
8191 		this(TableViewWidgetInner tvw, Widget parent) {
8192 			super(parent);
8193 			this.tvw = tvw;
8194 
8195 			this.remainder = new Button("", this);
8196 
8197 			this.addEventListener((scope ClickEvent ev) {
8198 				int header = -1;
8199 				foreach(idx, child; this.children[1 .. $]) {
8200 					if(child is ev.target) {
8201 						header = cast(int) idx;
8202 						break;
8203 					}
8204 				}
8205 
8206 				if(header != -1) {
8207 					auto hce = new HeaderClickedEvent(tvw.tvw, header);
8208 					hce.dispatch();
8209 				}
8210 
8211 			});
8212 		}
8213 
8214 		void updateHeaders() {
8215 			foreach(child; children[1 .. $])
8216 				child.removeWidget();
8217 
8218 			foreach(column; tvw.tvw.columns) {
8219 				// the cast is ok because I dup it above, just the type is never changed.
8220 				// all this is private so it should never get messed up.
8221 				new Button(ImageLabel(cast(string) column.name, column.alignment), this);
8222 			}
8223 		}
8224 
8225 		Button remainder;
8226 		TableViewWidgetInner tvw;
8227 
8228 		override void recomputeChildLayout() {
8229 			registerMovement();
8230 			int pos;
8231 			foreach(idx, child; children[1 .. $]) {
8232 				if(idx >= tvw.tvw.columns.length)
8233 					continue;
8234 				child.x = pos;
8235 				child.y = 0;
8236 				child.width = tvw.tvw.columns[idx].calculatedWidth;
8237 				child.height = 16;// this.height;
8238 				pos += child.width;
8239 
8240 				child.recomputeChildLayout();
8241 			}
8242 
8243 			if(remainder is null)
8244 				return;
8245 
8246 			remainder.x = pos;
8247 			remainder.y = 0;
8248 			if(pos < this.width)
8249 				remainder.width = this.width - pos;// + 4;
8250 			else
8251 				remainder.width = 0;
8252 			remainder.height = 16;
8253 
8254 			remainder.recomputeChildLayout();
8255 		}
8256 
8257 		// for the scrollable children mixin
8258 		Point scrollOrigin() {
8259 			return Point(tvw.smw.position.x, 0);
8260 		}
8261 		void paintFrameAndBackground(WidgetPainter painter) { }
8262 
8263 		mixin ScrollableChildren;
8264 	}
8265 }
8266 
8267 /+
8268 
8269 // given struct / array / number / string / etc, make it viewable and editable
8270 class DataViewerWidget : Widget {
8271 
8272 }
8273 
8274 // this is just the tab list with no associated page
8275 class TabMessageWidget : Widget {
8276 
8277 }
8278 +/
8279 
8280 /++
8281 	A line edit box with an associated label.
8282 
8283 	History:
8284 		On May 17, 2021, the default internal layout was changed from horizontal to vertical.
8285 
8286 		```
8287 		Old: ________
8288 
8289 		New:
8290 		____________
8291 		```
8292 
8293 		To restore the old behavior, use `new LabeledLineEdit("label", TextAlignment.Right, parent);`
8294 
8295 		You can also use `new LabeledLineEdit("label", TextAlignment.Left, parent);` if you want a
8296 		horizontal label but left aligned. You may also consider a [GridLayout].
8297 +/
8298 alias LabeledLineEdit = Labeled!LineEdit;
8299 
8300 /++
8301 	History:
8302 		Added May 19, 2021
8303 +/
8304 class Labeled(T) : Widget {
8305 	///
8306 	this(string label, Widget parent) {
8307 		super(parent);
8308 		initialize!VerticalLayout(label, TextAlignment.Left, parent);
8309 	}
8310 
8311 	/++
8312 		History:
8313 			The alignment parameter was added May 17, 2021
8314 	+/
8315 	this(string label, TextAlignment alignment, Widget parent) {
8316 		super(parent);
8317 		initialize!HorizontalLayout(label, alignment, parent);
8318 	}
8319 
8320 	private void initialize(L)(string label, TextAlignment alignment, Widget parent) {
8321 		tabStop = false;
8322 		horizontal = is(L == HorizontalLayout);
8323 		auto hl = new L(this);
8324 		this.label = new TextLabel(label, alignment, hl);
8325 		this.lineEdit = new T(hl);
8326 
8327 		this.label.labelFor = this.lineEdit;
8328 	}
8329 
8330 	private bool horizontal;
8331 
8332 	TextLabel label; ///
8333 	T lineEdit; ///
8334 
8335 	override int flexBasisWidth() { return 250; }
8336 
8337 	override int minHeight() { return (horizontal ? 1 : 2) * defaultLineHeight + 4; }
8338 	override int maxHeight() { return (horizontal ? 1 : 2) * defaultLineHeight + 4; }
8339 	override int marginTop() { return 4; }
8340 	override int marginBottom() { return 4; }
8341 
8342 	///
8343 	@property string content() {
8344 		return lineEdit.content;
8345 	}
8346 	///
8347 	@property void content(string c) {
8348 		return lineEdit.content(c);
8349 	}
8350 
8351 	///
8352 	void selectAll() {
8353 		lineEdit.selectAll();
8354 	}
8355 
8356 	override void focus() {
8357 		lineEdit.focus();
8358 	}
8359 }
8360 
8361 /++
8362 	A labeled password edit.
8363 
8364 	History:
8365 		Added as a class on January 25, 2021, changed into an alias of the new [Labeled] template on May 19, 2021
8366 
8367 		The default parameters for the constructors were also removed on May 19, 2021
8368 +/
8369 alias LabeledPasswordEdit = Labeled!PasswordEdit;
8370 
8371 private string toMenuLabel(string s) {
8372 	string n;
8373 	n.reserve(s.length);
8374 	foreach(c; s)
8375 		if(c == '_')
8376 			n ~= ' ';
8377 		else
8378 			n ~= c;
8379 	return n;
8380 }
8381 
8382 private void delegate() makeAutomaticHandler(alias fn, T)(T t) {
8383 	static if(is(T : void delegate())) {
8384 		return t;
8385 	} else {
8386 		static if(is(typeof(fn) Params == __parameters))
8387 		struct S {
8388 			static if(!__traits(compiles, mixin(`{ static foreach(i; 1..4) {} }`))) {
8389 				pragma(msg, "warning: automatic handler of params not yet implemented on your compiler");
8390 			} else mixin(q{
8391 			static foreach(idx, ignore; Params) {
8392 				mixin("Params[idx] " ~ __traits(identifier, Params[idx .. idx + 1]) ~ ";");
8393 			}
8394 			});
8395 		}
8396 		return () {
8397 			dialog((S s) {
8398 				cast(void) t(s.tupleof);
8399 			}, null, __traits(identifier, fn));
8400 		};
8401 	}
8402 }
8403 
8404 private template hasAnyRelevantAnnotations(a...) {
8405 	bool helper() {
8406 		bool any;
8407 		foreach(attr; a) {
8408 			static if(is(typeof(attr) == .menu))
8409 				any = true;
8410 			else static if(is(typeof(attr) == .toolbar))
8411 				any = true;
8412 			else static if(is(attr == .separator))
8413 				any = true;
8414 			else static if(is(typeof(attr) == .accelerator))
8415 				any = true;
8416 			else static if(is(typeof(attr) == .hotkey))
8417 				any = true;
8418 			else static if(is(typeof(attr) == .icon))
8419 				any = true;
8420 			else static if(is(typeof(attr) == .label))
8421 				any = true;
8422 			else static if(is(typeof(attr) == .tip))
8423 				any = true;
8424 		}
8425 		return any;
8426 	}
8427 
8428 	enum bool hasAnyRelevantAnnotations = helper();
8429 }
8430 
8431 /++
8432 	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.
8433 +/
8434 class MainWindow : Window {
8435 	///
8436 	this(string title = null, int initialWidth = 500, int initialHeight = 500) {
8437 		super(initialWidth, initialHeight, title);
8438 
8439 		_clientArea = new ClientAreaWidget();
8440 		_clientArea.x = 0;
8441 		_clientArea.y = 0;
8442 		_clientArea.width = this.width;
8443 		_clientArea.height = this.height;
8444 		_clientArea.tabStop = false;
8445 
8446 		super.addChild(_clientArea);
8447 
8448 		statusBar = new StatusBar(this);
8449 	}
8450 
8451 	/++
8452 		Adds a menu and toolbar from annotated functions.
8453 
8454 	---
8455         struct Commands {
8456                 @menu("File") {
8457                         void New() {}
8458                         void Open() {}
8459                         void Save() {}
8460                         @separator
8461                         void Exit() @accelerator("Alt+F4") @hotkey('x') {
8462                                 window.close();
8463                         }
8464                 }
8465 
8466                 @menu("Edit") {
8467                         void Undo() {
8468                                 undo();
8469                         }
8470                         @separator
8471                         void Cut() {}
8472                         void Copy() {}
8473                         void Paste() {}
8474                 }
8475 
8476                 @menu("Help") {
8477                         void About() {}
8478                 }
8479         }
8480 
8481         Commands commands;
8482 
8483         window.setMenuAndToolbarFromAnnotatedCode(commands);
8484 	---
8485 
8486 	Note that you can call this function multiple times and it will add the items in order to the given items.
8487 
8488 	+/
8489 	void setMenuAndToolbarFromAnnotatedCode(T)(ref T t) if(!is(T == class) && !is(T == interface)) {
8490 		setMenuAndToolbarFromAnnotatedCode_internal(t);
8491 	}
8492 	void setMenuAndToolbarFromAnnotatedCode(T)(T t) if(is(T == class) || is(T == interface)) {
8493 		setMenuAndToolbarFromAnnotatedCode_internal(t);
8494 	}
8495 	void setMenuAndToolbarFromAnnotatedCode_internal(T)(ref T t) {
8496 		Action[] toolbarActions;
8497 		auto menuBar = this.menuBar is null ? new MenuBar() : this.menuBar;
8498 		Menu[string] mcs;
8499 
8500 		foreach(menu; menuBar.subMenus) {
8501 			mcs[menu.label] = menu;
8502 		}
8503 
8504 		foreach(memberName; __traits(derivedMembers, T)) {
8505 			static if(memberName != "this")
8506 			static if(hasAnyRelevantAnnotations!(__traits(getAttributes, __traits(getMember, T, memberName)))) {
8507 				.menu menu;
8508 				.toolbar toolbar;
8509 				bool separator;
8510 				.accelerator accelerator;
8511 				.hotkey hotkey;
8512 				.icon icon;
8513 				string label;
8514 				string tip;
8515 				foreach(attr; __traits(getAttributes, __traits(getMember, T, memberName))) {
8516 					static if(is(typeof(attr) == .menu))
8517 						menu = attr;
8518 					else static if(is(typeof(attr) == .toolbar))
8519 						toolbar = attr;
8520 					else static if(is(attr == .separator))
8521 						separator = true;
8522 					else static if(is(typeof(attr) == .accelerator))
8523 						accelerator = attr;
8524 					else static if(is(typeof(attr) == .hotkey))
8525 						hotkey = attr;
8526 					else static if(is(typeof(attr) == .icon))
8527 						icon = attr;
8528 					else static if(is(typeof(attr) == .label))
8529 						label = attr.label;
8530 					else static if(is(typeof(attr) == .tip))
8531 						tip = attr.tip;
8532 				}
8533 
8534 				if(menu !is .menu.init || toolbar !is .toolbar.init) {
8535 					ushort correctIcon = icon.id; // FIXME
8536 					if(label.length == 0)
8537 						label = memberName.toMenuLabel;
8538 
8539 					auto handler = makeAutomaticHandler!(__traits(getMember, T, memberName))(&__traits(getMember, t, memberName));
8540 
8541 					auto action = new Action(label, correctIcon, handler);
8542 
8543 					if(accelerator.keyString.length) {
8544 						auto ke = KeyEvent.parse(accelerator.keyString);
8545 						action.accelerator = ke;
8546 						accelerators[ke.toStr] = handler;
8547 					}
8548 
8549 					if(toolbar !is .toolbar.init)
8550 						toolbarActions ~= action;
8551 					if(menu !is .menu.init) {
8552 						Menu mc;
8553 						if(menu.name in mcs) {
8554 							mc = mcs[menu.name];
8555 						} else {
8556 							mc = new Menu(menu.name, this);
8557 							menuBar.addItem(mc);
8558 							mcs[menu.name] = mc;
8559 						}
8560 
8561 						if(separator)
8562 							mc.addSeparator();
8563 						mc.addItem(new MenuItem(action));
8564 					}
8565 				}
8566 			}
8567 		}
8568 
8569 		this.menuBar = menuBar;
8570 
8571 		if(toolbarActions.length) {
8572 			auto tb = new ToolBar(toolbarActions, this);
8573 		}
8574 	}
8575 
8576 	void delegate()[string] accelerators;
8577 
8578 	override void defaultEventHandler_keydown(KeyDownEvent event) {
8579 		auto str = event.originalKeyEvent.toStr;
8580 		if(auto acl = str in accelerators)
8581 			(*acl)();
8582 		super.defaultEventHandler_keydown(event);
8583 	}
8584 
8585 	override void defaultEventHandler_mouseover(MouseOverEvent event) {
8586 		super.defaultEventHandler_mouseover(event);
8587 		if(this.statusBar !is null && event.target.statusTip.length)
8588 			this.statusBar.parts[0].content = event.target.statusTip;
8589 		else if(this.statusBar !is null && this.statusTip.length)
8590 			this.statusBar.parts[0].content = this.statusTip; // ~ " " ~ event.target.toString();
8591 	}
8592 
8593 	override void addChild(Widget c, int position = int.max) {
8594 		if(auto tb = cast(ToolBar) c)
8595 			version(win32_widgets)
8596 				super.addChild(c, 0);
8597 			else version(custom_widgets)
8598 				super.addChild(c, menuBar ? 1 : 0);
8599 			else static assert(0);
8600 		else
8601 			clientArea.addChild(c, position);
8602 	}
8603 
8604 	ToolBar _toolBar;
8605 	///
8606 	ToolBar toolBar() { return _toolBar; }
8607 	///
8608 	ToolBar toolBar(ToolBar t) {
8609 		_toolBar = t;
8610 		foreach(child; this.children)
8611 			if(child is t)
8612 				return t;
8613 		version(win32_widgets)
8614 			super.addChild(t, 0);
8615 		else version(custom_widgets)
8616 			super.addChild(t, menuBar ? 1 : 0);
8617 		else static assert(0);
8618 		return t;
8619 	}
8620 
8621 	MenuBar _menu;
8622 	///
8623 	MenuBar menuBar() { return _menu; }
8624 	///
8625 	MenuBar menuBar(MenuBar m) {
8626 		if(m is _menu) {
8627 			version(custom_widgets)
8628 				recomputeChildLayout();
8629 			return m;
8630 		}
8631 
8632 		if(_menu !is null) {
8633 			// make sure it is sanely removed
8634 			// FIXME
8635 		}
8636 
8637 		_menu = m;
8638 
8639 		version(win32_widgets) {
8640 			SetMenu(parentWindow.win.impl.hwnd, m.handle);
8641 		} else version(custom_widgets) {
8642 			super.addChild(m, 0);
8643 
8644 		//	clientArea.y = menu.height;
8645 		//	clientArea.height = this.height - menu.height;
8646 
8647 			recomputeChildLayout();
8648 		} else static assert(false);
8649 
8650 		return _menu;
8651 	}
8652 	private Widget _clientArea;
8653 	///
8654 	@property Widget clientArea() { return _clientArea; }
8655 	protected @property void clientArea(Widget wid) {
8656 		_clientArea = wid;
8657 	}
8658 
8659 	private StatusBar _statusBar;
8660 	///
8661 	@property StatusBar statusBar() { return _statusBar; }
8662 	///
8663 	@property void statusBar(StatusBar bar) {
8664 		_statusBar = bar;
8665 		super.addChild(_statusBar);
8666 	}
8667 
8668 	///
8669 	@property string title() { return parentWindow.win.title; }
8670 	///
8671 	@property void title(string title) { parentWindow.win.title = title; }
8672 }
8673 
8674 /+
8675 	This is really an implementation detail of [MainWindow]
8676 +/
8677 private class ClientAreaWidget : Widget {
8678 	this() {
8679 		this.tabStop = false;
8680 		super(null);
8681 		//sa = new ScrollableWidget(this);
8682 	}
8683 	/*
8684 	ScrollableWidget sa;
8685 	override void addChild(Widget w, int position) {
8686 		if(sa is null)
8687 			super.addChild(w, position);
8688 		else {
8689 			sa.addChild(w, position);
8690 			sa.setContentSize(this.minWidth + 1, this.minHeight);
8691 			import std.stdio; writeln(sa.contentWidth, "x", sa.contentHeight);
8692 		}
8693 	}
8694 	*/
8695 }
8696 
8697 /**
8698 	Toolbars are lists of buttons (typically icons) that appear under the menu.
8699 	Each button ought to correspond to a menu item, represented by [Action] objects.
8700 */
8701 class ToolBar : Widget {
8702 	version(win32_widgets) {
8703 		private const int idealHeight;
8704 		override int minHeight() { return idealHeight; }
8705 		override int maxHeight() { return idealHeight; }
8706 	} else version(custom_widgets) {
8707 		override int minHeight() { return toolbarIconSize; }// defaultLineHeight * 3/2; }
8708 		override int maxHeight() { return toolbarIconSize; } //defaultLineHeight * 3/2; }
8709 	} else static assert(false);
8710 	override int heightStretchiness() { return 0; }
8711 
8712 	version(win32_widgets) 
8713 		HIMAGELIST imageList;
8714 
8715 	this(Widget parent) {
8716 		this(null, parent);
8717 	}
8718 
8719 	///
8720 	this(Action[] actions, Widget parent) {
8721 		super(parent);
8722 
8723 		tabStop = false;
8724 
8725 		version(win32_widgets) {
8726 			// so i like how the flat thing looks on windows, but not on wine
8727 			// and eh, with windows visual styles enabled it looks cool anyway soooo gonna
8728 			// leave it commented
8729 			createWin32Window(this, "ToolbarWindow32"w, "", TBSTYLE_LIST|/*TBSTYLE_FLAT|*/TBSTYLE_TOOLTIPS);
8730 			
8731 			SendMessageW(hwnd, TB_SETEXTENDEDSTYLE, 0, 8/*TBSTYLE_EX_MIXEDBUTTONS*/);
8732 
8733 			imageList = ImageList_Create(
8734 				// width, height
8735 				16, 16,
8736 				ILC_COLOR16 | ILC_MASK,
8737 				16 /*numberOfButtons*/, 0);
8738 
8739 			SendMessageW(hwnd, TB_SETIMAGELIST, cast(WPARAM) 0, cast(LPARAM) imageList);
8740 			SendMessageW(hwnd, TB_LOADIMAGES, cast(WPARAM) IDB_STD_SMALL_COLOR, cast(LPARAM) HINST_COMMCTRL);
8741 			SendMessageW(hwnd, TB_SETMAXTEXTROWS, 0, 0);
8742 			SendMessageW(hwnd, TB_AUTOSIZE, 0, 0);
8743 
8744 			TBBUTTON[] buttons;
8745 
8746 			// FIXME: I_IMAGENONE is if here is no icon
8747 			foreach(action; actions)
8748 				buttons ~= TBBUTTON(
8749 					MAKELONG(cast(ushort)(action.iconId ? (action.iconId - 1) : -2 /* I_IMAGENONE */), 0),
8750 					action.id,
8751 					TBSTATE_ENABLED, // state
8752 					0, // style
8753 					0, // reserved array, just zero it out
8754 					0, // dwData
8755 					cast(size_t) toWstringzInternal(action.label) // INT_PTR
8756 				);
8757 
8758 			SendMessageW(hwnd, TB_BUTTONSTRUCTSIZE, cast(WPARAM)TBBUTTON.sizeof, 0);
8759 			SendMessageW(hwnd, TB_ADDBUTTONSW, cast(WPARAM) buttons.length, cast(LPARAM)buttons.ptr);
8760 
8761 			SIZE size;
8762 			import core.sys.windows.commctrl;
8763 			SendMessageW(hwnd, TB_GETMAXSIZE, 0, cast(LPARAM) &size);
8764 			idealHeight = size.cy + 4; // the plus 4 is a hack
8765 
8766 			/*
8767 			RECT rect;
8768 			GetWindowRect(hwnd, &rect);
8769 			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
8770 			*/
8771 
8772 			assert(idealHeight);
8773 		} else version(custom_widgets) {
8774 			foreach(action; actions)
8775 				new ToolButton(action, this);
8776 		} else static assert(false);
8777 	}
8778 
8779 	override void recomputeChildLayout() {
8780 		.recomputeChildLayout!"width"(this);
8781 	}
8782 }
8783 
8784 enum toolbarIconSize = 24;
8785 
8786 /// 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.
8787 class ToolButton : Button {
8788 	///
8789 	this(string label, Widget parent) {
8790 		super(label, parent);
8791 		tabStop = false;
8792 	}
8793 	///
8794 	this(Action action, Widget parent) {
8795 		super(action.label, parent);
8796 		tabStop = false;
8797 		this.action = action;
8798 	}
8799 
8800 	version(custom_widgets)
8801 	override void defaultEventHandler_click(ClickEvent event) {
8802 		foreach(handler; action.triggered)
8803 			handler();
8804 	}
8805 
8806 	Action action;
8807 
8808 	override int maxWidth() { return toolbarIconSize; }
8809 	override int minWidth() { return toolbarIconSize; }
8810 	override int maxHeight() { return toolbarIconSize; }
8811 	override int minHeight() { return toolbarIconSize; }
8812 
8813 	version(custom_widgets)
8814 	override void paint(WidgetPainter painter) {
8815 	painter.drawThemed(delegate Rectangle (const Rectangle bounds) {
8816 		painter.outlineColor = Color.black;
8817 
8818 		// I want to get from 16 to 24. that's * 3 / 2
8819 		static assert(toolbarIconSize >= 16);
8820 		enum multiplier = toolbarIconSize / 8;
8821 		enum divisor = 2 + ((toolbarIconSize % 8) ? 1 : 0);
8822 		switch(action.iconId) {
8823 			case GenericIcons.New:
8824 				painter.fillColor = Color.white;
8825 				painter.drawPolygon(
8826 					Point(3, 2) * multiplier / divisor, Point(3, 13) * multiplier / divisor, Point(12, 13) * multiplier / divisor, Point(12, 6) * multiplier / divisor,
8827 					Point(8, 2) * multiplier / divisor, Point(8, 6) * multiplier / divisor, Point(12, 6) * multiplier / divisor, Point(8, 2) * multiplier / divisor,
8828 					Point(3, 2) * multiplier / divisor, Point(3, 13) * multiplier / divisor
8829 				);
8830 			break;
8831 			case GenericIcons.Save:
8832 				painter.fillColor = Color.white;
8833 				painter.outlineColor = Color.black;
8834 				painter.drawRectangle(Point(2, 2) * multiplier / divisor, Point(13, 13) * multiplier / divisor);
8835 
8836 				// the label
8837 				painter.drawRectangle(Point(4, 8) * multiplier / divisor, Point(11, 13) * multiplier / divisor);
8838 
8839 				// the slider
8840 				painter.fillColor = Color.black;
8841 				painter.outlineColor = Color.black;
8842 				painter.drawRectangle(Point(4, 3) * multiplier / divisor, Point(10, 6) * multiplier / divisor);
8843 
8844 				painter.fillColor = Color.white;
8845 				painter.outlineColor = Color.white;
8846 				// the disc window
8847 				painter.drawRectangle(Point(5, 3) * multiplier / divisor, Point(6, 5) * multiplier / divisor);
8848 			break;
8849 			case GenericIcons.Open:
8850 				painter.fillColor = Color.white;
8851 				painter.drawPolygon(
8852 					Point(4, 4) * multiplier / divisor, Point(4, 12) * multiplier / divisor, Point(13, 12) * multiplier / divisor, Point(13, 3) * multiplier / divisor,
8853 					Point(9, 3) * multiplier / divisor, Point(9, 4) * multiplier / divisor, Point(4, 4) * multiplier / divisor);
8854 				painter.drawPolygon(
8855 					Point(2, 6) * multiplier / divisor, Point(11, 6) * multiplier / divisor,
8856 					Point(12, 12) * multiplier / divisor, Point(4, 12) * multiplier / divisor,
8857 					Point(2, 6) * multiplier / divisor);
8858 				//painter.drawLine(Point(9, 6) * multiplier / divisor, Point(13, 7) * multiplier / divisor);
8859 			break;
8860 			case GenericIcons.Copy:
8861 				painter.fillColor = Color.white;
8862 				painter.drawRectangle(Point(3, 2) * multiplier / divisor, Point(9, 10) * multiplier / divisor);
8863 				painter.drawRectangle(Point(6, 5) * multiplier / divisor, Point(12, 13) * multiplier / divisor);
8864 			break;
8865 			case GenericIcons.Cut:
8866 				painter.fillColor = Color.transparent;
8867 				painter.outlineColor = getComputedStyle.foregroundColor();
8868 				painter.drawLine(Point(3, 2) * multiplier / divisor, Point(10, 9) * multiplier / divisor);
8869 				painter.drawLine(Point(4, 9) * multiplier / divisor, Point(11, 2) * multiplier / divisor);
8870 				painter.drawRectangle(Point(3, 9) * multiplier / divisor, Point(5, 13) * multiplier / divisor);
8871 				painter.drawRectangle(Point(9, 9) * multiplier / divisor, Point(11, 12) * multiplier / divisor);
8872 			break;
8873 			case GenericIcons.Paste:
8874 				painter.fillColor = Color.white;
8875 				painter.drawRectangle(Point(2, 3) * multiplier / divisor, Point(11, 11) * multiplier / divisor);
8876 				painter.drawRectangle(Point(6, 8) * multiplier / divisor, Point(13, 13) * multiplier / divisor);
8877 				painter.drawLine(Point(6, 2) * multiplier / divisor, Point(4, 5) * multiplier / divisor);
8878 				painter.drawLine(Point(6, 2) * multiplier / divisor, Point(9, 5) * multiplier / divisor);
8879 				painter.fillColor = Color.black;
8880 				painter.drawRectangle(Point(4, 5) * multiplier / divisor, Point(9, 6) * multiplier / divisor);
8881 			break;
8882 			case GenericIcons.Help:
8883 				painter.outlineColor = getComputedStyle.foregroundColor();
8884 				painter.drawText(Point(0, 0), "?", Point(width, height), TextAlignment.Center | TextAlignment.VerticalCenter);
8885 			break;
8886 			case GenericIcons.Undo:
8887 				painter.fillColor = Color.transparent;
8888 				painter.drawArc(Point(3, 4) * multiplier / divisor, 9 * multiplier / divisor, 9 * multiplier / divisor, 0, 360 * 64);
8889 				painter.outlineColor = Color.black;
8890 				painter.fillColor = Color.black;
8891 				painter.drawPolygon(
8892 					Point(4, 4) * multiplier / divisor,
8893 					Point(8, 2) * multiplier / divisor,
8894 					Point(8, 6) * multiplier / divisor,
8895 					Point(4, 4) * multiplier / divisor,
8896 				);
8897 			break;
8898 			case GenericIcons.Redo:
8899 				painter.fillColor = Color.transparent;
8900 				painter.drawArc(Point(3, 4) * multiplier / divisor, 9 * multiplier / divisor, 9 * multiplier / divisor, 0, 360 * 64);
8901 				painter.outlineColor = Color.black;
8902 				painter.fillColor = Color.black;
8903 				painter.drawPolygon(
8904 					Point(10, 4) * multiplier / divisor,
8905 					Point(6, 2) * multiplier / divisor,
8906 					Point(6, 6) * multiplier / divisor,
8907 					Point(10, 4) * multiplier / divisor,
8908 				);
8909 			break;
8910 			default:
8911 				painter.drawText(Point(0, 0), action.label, Point(width, height), TextAlignment.Center | TextAlignment.VerticalCenter);
8912 		}
8913 		return bounds;
8914 		});
8915 	}
8916 
8917 }
8918 
8919 
8920 ///
8921 class MenuBar : Widget {
8922 	MenuItem[] items;
8923 	Menu[] subMenus;
8924 
8925 	version(win32_widgets) {
8926 		HMENU handle;
8927 		///
8928 		this(Widget parent = null) {
8929 			super(parent);
8930 
8931 			handle = CreateMenu();
8932 			tabStop = false;
8933 		}
8934 	} else version(custom_widgets) {
8935 		///
8936 		this(Widget parent = null) {
8937 			tabStop = false; // these are selected some other way
8938 			super(parent);
8939 		}
8940 
8941 		mixin Padding!q{2};
8942 	} else static assert(false);
8943 
8944 	version(custom_widgets)
8945 	override void paint(WidgetPainter painter) {
8946 		draw3dFrame(this, painter, FrameStyle.risen, getComputedStyle().background.color);
8947 	}
8948 
8949 	///
8950 	MenuItem addItem(MenuItem item) {
8951 		this.addChild(item);
8952 		items ~= item;
8953 		version(win32_widgets) {
8954 			AppendMenuW(handle, MF_STRING, item.action is null ? 9000 : item.action.id, toWstringzInternal(item.label));
8955 		}
8956 		return item;
8957 	}
8958 
8959 
8960 	///
8961 	Menu addItem(Menu item) {
8962 
8963 		subMenus ~= item;
8964 
8965 		auto mbItem = new MenuItem(item.label, null);// this.parentWindow); // I'ma add the child down below so hopefully this isn't too insane
8966 
8967 		addChild(mbItem);
8968 		items ~= mbItem;
8969 
8970 		version(win32_widgets) {
8971 			AppendMenuW(handle, MF_STRING | MF_POPUP, cast(UINT) item.handle, toWstringzInternal(item.label));
8972 		} else version(custom_widgets) {
8973 			mbItem.defaultEventHandlers["mousedown"] = (Widget e, Event ev) {
8974 				item.popup(mbItem);
8975 			};
8976 		} else static assert(false);
8977 
8978 		return item;
8979 	}
8980 
8981 	override void recomputeChildLayout() {
8982 		.recomputeChildLayout!"width"(this);
8983 	}
8984 
8985 	override int maxHeight() { return defaultLineHeight + 4; }
8986 	override int minHeight() { return defaultLineHeight + 4; }
8987 }
8988 
8989 
8990 /**
8991 	Status bars appear at the bottom of a MainWindow.
8992 	They are made out of Parts, with a width and content.
8993 
8994 	They can have multiple parts or be in simple mode. FIXME: implement
8995 
8996 
8997 	sb.parts[0].content = "Status bar text!";
8998 */
8999 class StatusBar : Widget {
9000 	private Part[] partsArray;
9001 	///
9002 	struct Parts {
9003 		@disable this();
9004 		this(StatusBar owner) { this.owner = owner; }
9005 		//@disable this(this);
9006 		///
9007 		@property int length() { return cast(int) owner.partsArray.length; }
9008 		private StatusBar owner;
9009 		private this(StatusBar owner, Part[] parts) {
9010 			this.owner.partsArray = parts;
9011 			this.owner = owner;
9012 		}
9013 		///
9014 		Part opIndex(int p) {
9015 			if(owner.partsArray.length == 0)
9016 				this ~= new StatusBar.Part(300);
9017 			return owner.partsArray[p];
9018 		}
9019 
9020 		///
9021 		Part opOpAssign(string op : "~" )(Part p) {
9022 			assert(owner.partsArray.length < 255);
9023 			p.owner = this.owner;
9024 			p.idx = cast(int) owner.partsArray.length;
9025 			owner.partsArray ~= p;
9026 			version(win32_widgets) {
9027 				int[256] pos;
9028 				int cpos = 0;
9029 				foreach(idx, part; owner.partsArray) {
9030 					if(part.width)
9031 						cpos += part.width;
9032 					else
9033 						cpos += 100;
9034 
9035 					if(idx + 1 == owner.partsArray.length)
9036 						pos[idx] = -1;
9037 					else
9038 						pos[idx] = cpos;
9039 				}
9040 				SendMessageW(owner.hwnd, WM_USER + 4 /*SB_SETPARTS*/, owner.partsArray.length, cast(size_t) pos.ptr);
9041 			} else version(custom_widgets) {
9042 				owner.redraw();
9043 			} else static assert(false);
9044 
9045 			return p;
9046 		}
9047 	}
9048 
9049 	private Parts _parts;
9050 	///
9051 	final @property Parts parts() {
9052 		return _parts;
9053 	}
9054 
9055 	///
9056 	static class Part {
9057 		int width;
9058 		StatusBar owner;
9059 
9060 		///
9061 		this(int w = 100) { width = w; }
9062 
9063 		private int idx;
9064 		private string _content;
9065 		///
9066 		@property string content() { return _content; }
9067 		///
9068 		@property void content(string s) {
9069 			version(win32_widgets) {
9070 				_content = s;
9071 				WCharzBuffer bfr = WCharzBuffer(s);
9072 				SendMessageW(owner.hwnd, SB_SETTEXT, idx, cast(LPARAM) bfr.ptr);
9073 			} else version(custom_widgets) {
9074 				if(_content != s) {
9075 					_content = s;
9076 					owner.redraw();
9077 				}
9078 			} else static assert(false);
9079 		}
9080 	}
9081 	string simpleModeContent;
9082 	bool inSimpleMode;
9083 
9084 
9085 	///
9086 	this(Widget parent) {
9087 		super(null); // FIXME
9088 		_parts = Parts(this);
9089 		tabStop = false;
9090 		version(win32_widgets) {
9091 			parentWindow = parent.parentWindow;
9092 			createWin32Window(this, "msctls_statusbar32"w, "", 0);
9093 
9094 			RECT rect;
9095 			GetWindowRect(hwnd, &rect);
9096 			idealHeight = rect.bottom - rect.top;
9097 			assert(idealHeight);
9098 		} else version(custom_widgets) {
9099 		} else static assert(false);
9100 	}
9101 
9102 	version(custom_widgets)
9103 	override void paint(WidgetPainter painter) {
9104 		auto cs = getComputedStyle();
9105 		this.draw3dFrame(painter, FrameStyle.sunk, cs.background.color);
9106 		int cpos = 0;
9107 		int remainingLength = this.width;
9108 		foreach(idx, part; this.partsArray) {
9109 			auto partWidth = part.width ? part.width : ((idx + 1 == this.partsArray.length) ? remainingLength : 100);
9110 			painter.setClipRectangle(Point(cpos, 0), partWidth, height);
9111 			draw3dFrame(cpos, 0, partWidth, height, painter, FrameStyle.sunk, cs.background.color);
9112 			painter.setClipRectangle(Point(cpos + 2, 2), partWidth - 4, height - 4);
9113 
9114 			painter.outlineColor = cs.foregroundColor();
9115 			painter.fillColor = cs.foregroundColor();
9116 
9117 			painter.drawText(Point(cpos + 4, 0), part.content, Point(width, height), TextAlignment.VerticalCenter);
9118 			cpos += partWidth;
9119 			remainingLength -= partWidth;
9120 		}
9121 	}
9122 
9123 
9124 	version(win32_widgets) {
9125 		private const int idealHeight;
9126 		override int maxHeight() { return idealHeight; }
9127 		override int minHeight() { return idealHeight; }
9128 	} else version(custom_widgets) {
9129 		override int maxHeight() { return defaultLineHeight + 4; }
9130 		override int minHeight() { return defaultLineHeight + 4; }
9131 	} else static assert(false);
9132 }
9133 
9134 /// Displays an in-progress indicator without known values
9135 version(none)
9136 class IndefiniteProgressBar : Widget {
9137 	version(win32_widgets)
9138 	this(Widget parent) {
9139 		super(parent);
9140 		createWin32Window(this, "msctls_progress32"w, "", 8 /* PBS_MARQUEE */);
9141 		tabStop = false;
9142 	}
9143 	override int minHeight() { return 10; }
9144 }
9145 
9146 /// A progress bar with a known endpoint and completion amount
9147 class ProgressBar : Widget {
9148 	this(Widget parent) {
9149 		version(win32_widgets) {
9150 			super(parent);
9151 			createWin32Window(this, "msctls_progress32"w, "", 0);
9152 			tabStop = false;
9153 		} else version(custom_widgets) {
9154 			super(parent);
9155 			max = 100;
9156 			step = 10;
9157 			tabStop = false;
9158 		} else static assert(0);
9159 	}
9160 
9161 	version(custom_widgets)
9162 	override void paint(WidgetPainter painter) {
9163 		auto cs = getComputedStyle();
9164 		this.draw3dFrame(painter, FrameStyle.sunk, cs.background.color);
9165 		painter.fillColor = cs.progressBarColor;
9166 		painter.drawRectangle(Point(0, 0), width * current / max, height);
9167 	}
9168 
9169 
9170 	version(custom_widgets) {
9171 		int current;
9172 		int max;
9173 		int step;
9174 	}
9175 
9176 	///
9177 	void advanceOneStep() {
9178 		version(win32_widgets)
9179 			SendMessageW(hwnd, PBM_STEPIT, 0, 0);
9180 		else version(custom_widgets)
9181 			addToPosition(step);
9182 		else static assert(false);
9183 	}
9184 
9185 	///
9186 	void setStepIncrement(int increment) {
9187 		version(win32_widgets)
9188 			SendMessageW(hwnd, PBM_SETSTEP, increment, 0);
9189 		else version(custom_widgets)
9190 			step = increment;
9191 		else static assert(false);
9192 	}
9193 
9194 	///
9195 	void addToPosition(int amount) {
9196 		version(win32_widgets)
9197 			SendMessageW(hwnd, PBM_DELTAPOS, amount, 0);
9198 		else version(custom_widgets)
9199 			setPosition(current + amount);
9200 		else static assert(false);
9201 	}
9202 
9203 	///
9204 	void setPosition(int pos) {
9205 		version(win32_widgets)
9206 			SendMessageW(hwnd, PBM_SETPOS, pos, 0);
9207 		else version(custom_widgets) {
9208 			current = pos;
9209 			if(current > max)
9210 				current = max;
9211 			redraw();
9212 		}
9213 		else static assert(false);
9214 	}
9215 
9216 	///
9217 	void setRange(ushort min, ushort max) {
9218 		version(win32_widgets)
9219 			SendMessageW(hwnd, PBM_SETRANGE, 0, MAKELONG(min, max));
9220 		else version(custom_widgets) {
9221 			this.max = max;
9222 		}
9223 		else static assert(false);
9224 	}
9225 
9226 	override int minHeight() { return 10; }
9227 }
9228 
9229 version(custom_widgets)
9230 private void extractWindowsStyleLabel(scope const char[] label, out string thisLabel, out dchar thisAccelerator) {
9231 	thisLabel.reserve(label.length);
9232 	bool justSawAmpersand;
9233 	foreach(ch; label) {
9234 		if(justSawAmpersand) {
9235 			justSawAmpersand = false;
9236 			if(ch == '&') {
9237 				goto plain;
9238 			}
9239 			thisAccelerator = ch;
9240 		} else {
9241 			if(ch == '&') {
9242 				justSawAmpersand = true;
9243 				continue;
9244 			}
9245 			plain:
9246 			thisLabel ~= ch;
9247 		}
9248 	}
9249 }
9250 
9251 /++
9252 	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.
9253 
9254 
9255 	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
9256 
9257 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
9258 
9259 	History:
9260 		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.
9261 +/
9262 class Fieldset : Widget {
9263 	// FIXME: on Windows,it doesn't draw the background on the label
9264 	// on X, it doesn't fix the clipping rectangle for it
9265 	version(win32_widgets)
9266 		override int paddingTop() { return defaultLineHeight; }
9267 	else version(custom_widgets)
9268 		override int paddingTop() { return defaultLineHeight + 2; }
9269 	else static assert(false);
9270 	override int paddingBottom() { return 6; }
9271 	override int paddingLeft() { return 6; }
9272 	override int paddingRight() { return 6; }
9273 
9274 	override int marginLeft() { return 6; }
9275 	override int marginRight() { return 6; }
9276 	override int marginTop() { return 2; }
9277 	override int marginBottom() { return 2; }
9278 
9279 	string legend;
9280 
9281 	version(custom_widgets) private dchar accelerator;
9282 
9283 	this(string legend, Widget parent) {
9284 		version(win32_widgets) {
9285 			super(parent);
9286 			this.legend = legend;
9287 			createWin32Window(this, "button"w, legend, BS_GROUPBOX);
9288 			tabStop = false;
9289 		} else version(custom_widgets) {
9290 			super(parent);
9291 			tabStop = false;
9292 
9293 			legend.extractWindowsStyleLabel(this.legend, this.accelerator);
9294 		} else static assert(0);
9295 	}
9296 
9297 	version(custom_widgets)
9298 	override void paint(WidgetPainter painter) {
9299 		painter.fillColor = Color.transparent;
9300 		auto cs = getComputedStyle();
9301 		painter.pen = Pen(cs.foregroundColor, 1);
9302 		painter.drawRectangle(Point(0, defaultLineHeight / 2), width, height - Window.lineHeight / 2);
9303 
9304 		auto tx = painter.textSize(legend);
9305 		painter.outlineColor = Color.transparent;
9306 
9307 		static if(UsingSimpledisplayX11) {
9308 			painter.fillColor = getComputedStyle().windowBackgroundColor;
9309 			painter.drawRectangle(Point(8, 0), tx.width, tx.height);
9310 		} else version(Windows) {
9311 			auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
9312 			painter.drawRectangle(Point(8, -tx.height/2), tx.width, tx.height);
9313 			SelectObject(painter.impl.hdc, b);
9314 		} else static assert(0);
9315 		painter.outlineColor = cs.foregroundColor;
9316 		painter.drawText(Point(8, 0), legend);
9317 	}
9318 
9319 
9320 	override int maxHeight() {
9321 		auto m = paddingTop() + paddingBottom();
9322 		foreach(child; children) {
9323 			auto mh = child.maxHeight();
9324 			if(mh == int.max)
9325 				return int.max;
9326 			m += mh;
9327 			m += child.marginBottom();
9328 			m += child.marginTop();
9329 		}
9330 		m += 6;
9331 		if(m < minHeight)
9332 			return minHeight;
9333 		return m;
9334 	}
9335 
9336 	override int minHeight() {
9337 		auto m = paddingTop() + paddingBottom();
9338 		foreach(child; children) {
9339 			m += child.minHeight();
9340 			m += child.marginBottom();
9341 			m += child.marginTop();
9342 		}
9343 		return m + 6;
9344 	}
9345 }
9346 
9347 /// Draws a line
9348 class HorizontalRule : Widget {
9349 	mixin Margin!q{ 2 };
9350 	override int minHeight() { return 2; }
9351 	override int maxHeight() { return 2; }
9352 
9353 	///
9354 	this(Widget parent) {
9355 		super(parent);
9356 	}
9357 
9358 	override void paint(WidgetPainter painter) {
9359 		auto cs = getComputedStyle();
9360 		painter.outlineColor = cs.darkAccentColor;
9361 		painter.drawLine(Point(0, 0), Point(width, 0));
9362 		painter.outlineColor = cs.lightAccentColor;
9363 		painter.drawLine(Point(0, 1), Point(width, 1));
9364 	}
9365 }
9366 
9367 /// ditto
9368 class VerticalRule : Widget {
9369 	mixin Margin!q{ 2 };
9370 	override int minWidth() { return 2; }
9371 	override int maxWidth() { return 2; }
9372 
9373 	///
9374 	this(Widget parent) {
9375 		super(parent);
9376 	}
9377 
9378 	override void paint(WidgetPainter painter) {
9379 		auto cs = getComputedStyle();
9380 		painter.outlineColor = cs.darkAccentColor;
9381 		painter.drawLine(Point(0, 0), Point(0, height));
9382 		painter.outlineColor = cs.lightAccentColor;
9383 		painter.drawLine(Point(1, 0), Point(1, height));
9384 	}
9385 }
9386 
9387 
9388 ///
9389 class Menu : Window {
9390 	void remove() {
9391 		foreach(i, child; parentWindow.children)
9392 			if(child is this) {
9393 				parentWindow._children = parentWindow._children[0 .. i] ~ parentWindow._children[i + 1 .. $];
9394 				break;
9395 			}
9396 		parentWindow.redraw();
9397 
9398 		parentWindow.releaseMouseCapture();
9399 	}
9400 
9401 	///
9402 	void addSeparator() {
9403 		version(win32_widgets)
9404 			AppendMenu(handle, MF_SEPARATOR, 0, null);
9405 		else version(custom_widgets)
9406 			auto hr = new HorizontalRule(this);
9407 		else static assert(0);
9408 	}
9409 
9410 	override int paddingTop() { return 4; }
9411 	override int paddingBottom() { return 4; }
9412 	override int paddingLeft() { return 2; }
9413 	override int paddingRight() { return 2; }
9414 
9415 	version(win32_widgets) {}
9416 	else version(custom_widgets) {
9417 		SimpleWindow dropDown;
9418 		Widget menuParent;
9419 		void popup(Widget parent, int offsetX = 0, int offsetY = int.min) {
9420 			this.menuParent = parent;
9421 
9422 			int w = 150;
9423 			int h = paddingTop + paddingBottom;
9424 			if(this.children.length) {
9425 				// hacking it to get the ideal height out of recomputeChildLayout
9426 				this.width = w;
9427 				this.height = h;
9428 				this.recomputeChildLayout();
9429 				h = this.children[$-1].y + this.children[$-1].height + this.children[$-1].marginBottom;
9430 				h += paddingBottom;
9431 
9432 				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
9433 			}
9434 
9435 			if(offsetY == int.min)
9436 				offsetY = parent.defaultLineHeight;
9437 
9438 			auto coord = parent.globalCoordinates();
9439 			dropDown.moveResize(coord.x + offsetX, coord.y + offsetY, w, h);
9440 			this.x = 0;
9441 			this.y = 0;
9442 			this.width = dropDown.width;
9443 			this.height = dropDown.height;
9444 			this.drawableWindow = dropDown;
9445 			this.recomputeChildLayout();
9446 
9447 			static if(UsingSimpledisplayX11)
9448 				XSync(XDisplayConnection.get, 0);
9449 
9450 			dropDown.visibilityChanged = (bool visible) {
9451 				if(visible) {
9452 					this.redraw();
9453 					dropDown.grabInput();
9454 				} else {
9455 					dropDown.releaseInputGrab();
9456 				}
9457 			};
9458 
9459 			dropDown.show();
9460 
9461 			clickListener = this.addEventListener((scope ClickEvent ev) {
9462 				unpopup();
9463 				// need to unlock asap just in case other user handlers block...
9464 				static if(UsingSimpledisplayX11)
9465 					flushGui();
9466 			}, true /* again for asap action */);
9467 		}
9468 
9469 		EventListener clickListener;
9470 	}
9471 	else static assert(false);
9472 
9473 	version(custom_widgets)
9474 	void unpopup() {
9475 		mouseLastOver = mouseLastDownOn = null;
9476 		dropDown.hide();
9477 		if(!menuParent.parentWindow.win.closed) {
9478 			if(auto maw = cast(MouseActivatedWidget) menuParent) {
9479 				maw.setDynamicState(DynamicState.depressed, false);
9480 				maw.redraw();
9481 			}
9482 			menuParent.parentWindow.win.focus();
9483 		}
9484 		clickListener.disconnect();
9485 	}
9486 
9487 	MenuItem[] items;
9488 
9489 	///
9490 	MenuItem addItem(MenuItem item) {
9491 		addChild(item);
9492 		items ~= item;
9493 		version(win32_widgets) {
9494 			AppendMenuW(handle, MF_STRING, item.action is null ? 9000 : item.action.id, toWstringzInternal(item.label));
9495 		}
9496 		return item;
9497 	}
9498 
9499 	string label;
9500 
9501 	version(win32_widgets) {
9502 		HMENU handle;
9503 		///
9504 		this(string label, Widget parent) {
9505 			// not actually passing the parent since it effs up the drawing
9506 			super(cast(Widget) null);// parent);
9507 			this.label = label;
9508 			handle = CreatePopupMenu();
9509 		}
9510 	} else version(custom_widgets) {
9511 		///
9512 		this(string label, Widget parent) {
9513 
9514 			if(dropDown) {
9515 				dropDown.close();
9516 			}
9517 			dropDown = new SimpleWindow(
9518 				150, 4,
9519 				null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.dropdownMenu, WindowFlags.dontAutoShow, parent ? parent.parentWindow.win : null);
9520 
9521 			this.label = label;
9522 
9523 			super(dropDown);
9524 		}
9525 	} else static assert(false);
9526 
9527 	override int maxHeight() { return defaultLineHeight; }
9528 	override int minHeight() { return defaultLineHeight; }
9529 
9530 	version(custom_widgets)
9531 	override void paint(WidgetPainter painter) {
9532 		this.draw3dFrame(painter, FrameStyle.risen, getComputedStyle.background.color);
9533 	}
9534 }
9535 
9536 /++
9537 	A MenuItem belongs to a [Menu] - use [Menu.addItem] to add one - and calls an [Action] when it is clicked.
9538 +/
9539 class MenuItem : MouseActivatedWidget {
9540 	Menu submenu;
9541 
9542 	Action action;
9543 	string label;
9544 
9545 	override int paddingLeft() { return 4; }
9546 
9547 	override int maxHeight() { return defaultLineHeight + 4; }
9548 	override int minHeight() { return defaultLineHeight + 4; }
9549 	override int minWidth() { return defaultLineHeight * cast(int) label.length + 8; }
9550 	override int maxWidth() {
9551 		if(cast(MenuBar) parent) {
9552 			return defaultLineHeight / 2 * cast(int) label.length + 8;
9553 		}
9554 		return int.max;
9555 	}
9556 	/// This should ONLY be used if there is no associated action, for example, if the menu item is just a submenu.
9557 	this(string lbl, Widget parent = null) {
9558 		super(parent);
9559 		//label = lbl; // FIXME
9560 		foreach(char ch; lbl) // FIXME
9561 			if(ch != '&') // FIXME
9562 				label ~= ch; // FIXME
9563 		tabStop = false; // these are selected some other way
9564 	}
9565 
9566 	///
9567 	this(Action action, Widget parent = null) {
9568 		assert(action !is null);
9569 		this(action.label, parent);
9570 		this.action = action;
9571 		tabStop = false; // these are selected some other way
9572 	}
9573 
9574 	version(custom_widgets)
9575 	override void paint(WidgetPainter painter) {
9576 		auto cs = getComputedStyle();
9577 		if(dynamicState & DynamicState.depressed)
9578 			this.draw3dFrame(painter, FrameStyle.sunk, cs.background.color);
9579 		if(dynamicState & DynamicState.hover)
9580 			painter.outlineColor = cs.activeMenuItemColor;
9581 		else
9582 			painter.outlineColor = cs.foregroundColor;
9583 		painter.fillColor = Color.transparent;
9584 		painter.drawText(Point(cast(MenuBar) this.parent ? 4 : 20, 2), label, Point(width, height), TextAlignment.Left);
9585 		if(action && action.accelerator !is KeyEvent.init) {
9586 			painter.drawText(Point(cast(MenuBar) this.parent ? 4 : 20, 2), action.accelerator.toStr(), Point(width - 4, height), TextAlignment.Right);
9587 
9588 		}
9589 	}
9590 
9591 	static class Style : Widget.Style {
9592 		override bool variesWithState(ulong dynamicStateFlags) {
9593 			return super.variesWithState(dynamicStateFlags) || (dynamicStateFlags & (DynamicState.depressed | DynamicState.hover));
9594 		}
9595 	}
9596 	mixin OverrideStyle!Style;
9597 
9598 	override void defaultEventHandler_triggered(Event event) {
9599 		if(action)
9600 		foreach(handler; action.triggered)
9601 			handler();
9602 
9603 		if(auto pmenu = cast(Menu) this.parent)
9604 			pmenu.remove();
9605 
9606 		super.defaultEventHandler_triggered(event);
9607 	}
9608 }
9609 
9610 version(win32_widgets)
9611 /// A "mouse activiated widget" is really just an abstract variant of button.
9612 class MouseActivatedWidget : Widget {
9613 	@property bool isChecked() {
9614 		assert(hwnd);
9615 		return SendMessageW(hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED;
9616 
9617 	}
9618 	@property void isChecked(bool state) {
9619 		assert(hwnd);
9620 		SendMessageW(hwnd, BM_SETCHECK, state ? BST_CHECKED : BST_UNCHECKED, 0);
9621 
9622 	}
9623 
9624 	override void handleWmCommand(ushort cmd, ushort id) {
9625 		if(cmd == 0) {
9626 			auto event = new Event(EventType.triggered, this);
9627 			event.dispatch();
9628 		}
9629 	}
9630 
9631 	this(Widget parent) {
9632 		super(parent);
9633 	}
9634 }
9635 else version(custom_widgets)
9636 /// ditto
9637 class MouseActivatedWidget : Widget {
9638 	@property bool isChecked() { return isChecked_; }
9639 	@property bool isChecked(bool b) { return isChecked_ = b; }
9640 
9641 	private bool isChecked_;
9642 
9643 	this(Widget parent) {
9644 		super(parent);
9645 
9646 		addEventListener((MouseDownEvent ev) {
9647 			if(ev.button == MouseButton.left) {
9648 				setDynamicState(DynamicState.depressed, true);
9649 				setDynamicState(DynamicState.hover, true);
9650 				redraw();
9651 			}
9652 		});
9653 
9654 		addEventListener((MouseUpEvent ev) {
9655 			if(ev.button == MouseButton.left) {
9656 				setDynamicState(DynamicState.depressed, false);
9657 				setDynamicState(DynamicState.hover, false);
9658 				redraw();
9659 			}
9660 		});
9661 
9662 		addEventListener((MouseMoveEvent mme) {
9663 			if(!(mme.state & ModifierState.leftButtonDown)) {
9664 				if(dynamicState_ & DynamicState.depressed) {
9665 					setDynamicState(DynamicState.depressed, false);
9666 					redraw();
9667 				}
9668 			}
9669 		});
9670 	}
9671 
9672 	override void defaultEventHandler_focus(Event ev) {
9673 		super.defaultEventHandler_focus(ev);
9674 		this.redraw();
9675 	}
9676 	override void defaultEventHandler_blur(Event ev) {
9677 		super.defaultEventHandler_blur(ev);
9678 		setDynamicState(DynamicState.depressed, false);
9679 		this.redraw();
9680 	}
9681 	override void defaultEventHandler_keydown(KeyDownEvent ev) {
9682 		super.defaultEventHandler_keydown(ev);
9683 		if(ev.key == Key.Space || ev.key == Key.Enter || ev.key == Key.PadEnter) {
9684 			setDynamicState(DynamicState.depressed, true);
9685 			setDynamicState(DynamicState.hover, true);
9686 			this.redraw();
9687 		}
9688 	}
9689 	override void defaultEventHandler_keyup(KeyUpEvent ev) {
9690 		super.defaultEventHandler_keyup(ev);
9691 		if(!(dynamicState & DynamicState.depressed))
9692 			return;
9693 		setDynamicState(DynamicState.depressed, false);
9694 		setDynamicState(DynamicState.hover, false);
9695 		this.redraw();
9696 
9697 		auto event = new Event(EventType.triggered, this);
9698 		event.sendDirectly();
9699 	}
9700 	override void defaultEventHandler_click(ClickEvent ev) {
9701 		super.defaultEventHandler_click(ev);
9702 		if(ev.button == MouseButton.left) {
9703 			auto event = new Event(EventType.triggered, this);
9704 			event.sendDirectly();
9705 		}
9706 	}
9707 
9708 }
9709 else static assert(false);
9710 
9711 /*
9712 /++
9713 	Like the tablet thing, it would have a label, a description, and a switch slider thingy.
9714 
9715 	Basically the same as a checkbox.
9716 +/
9717 class OnOffSwitch : MouseActivatedWidget {
9718 
9719 }
9720 */
9721 
9722 /++
9723 	History:
9724 		Added June 15, 2021 (dub v10.1)
9725 +/
9726 struct ImageLabel {
9727 	/++
9728 		History:
9729 			The `alignment` parameter was added on September 27, 2021
9730 	+/
9731 	this(string label, TextAlignment alignment = TextAlignment.Center) {
9732 		this.label = label;
9733 		this.displayFlags = DisplayFlags.displayText;
9734 		this.alignment = alignment;
9735 	}
9736 
9737 	this(string label, MemoryImage image, TextAlignment alignment = TextAlignment.Center) {
9738 		this.label = label;
9739 		this.image = image;
9740 		this.displayFlags = DisplayFlags.displayText | DisplayFlags.displayImage;
9741 		this.alignment = alignment;
9742 	}
9743 
9744 	this(MemoryImage image, TextAlignment alignment = TextAlignment.Center) {
9745 		this.image = image;
9746 		this.displayFlags = DisplayFlags.displayImage;
9747 		this.alignment = alignment;
9748 	}
9749 
9750 	this(string label, MemoryImage image, int displayFlags, TextAlignment alignment = TextAlignment.Center) {
9751 		this.label = label;
9752 		this.image = image;
9753 		this.alignment = alignment;
9754 		this.displayFlags = displayFlags;
9755 	}
9756 
9757 	string label;
9758 	MemoryImage image;
9759 
9760 	enum DisplayFlags {
9761 		displayText = 1 << 0,
9762 		displayImage = 1 << 1,
9763 	}
9764 
9765 	int displayFlags = DisplayFlags.displayText | DisplayFlags.displayImage;
9766 
9767 	TextAlignment alignment;
9768 }
9769 
9770 /++
9771 	A basic checked or not checked box with an attached label.
9772 
9773 
9774 	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
9775 
9776 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
9777 
9778 	History:
9779 		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.
9780 +/
9781 class Checkbox : MouseActivatedWidget {
9782 	version(win32_widgets) {
9783 		override int maxHeight() { return scaleWithDpi(16); }
9784 		override int minHeight() { return scaleWithDpi(16); }
9785 	} else version(custom_widgets) {
9786 		override int maxHeight() { return defaultLineHeight; }
9787 		override int minHeight() { return defaultLineHeight; }
9788 	} else static assert(0);
9789 
9790 	override int marginLeft() { return 4; }
9791 
9792 	override int flexBasisWidth() { return 24 + cast(int) label.length * 7; }
9793 
9794 	/++
9795 		Just an alias because I keep typing checked out of web habit.
9796 
9797 		History:
9798 			Added May 31, 2021
9799 	+/
9800 	alias checked = isChecked;
9801 
9802 	private string label;
9803 	private dchar accelerator;
9804 
9805 	/++
9806 	+/
9807 	this(string label, Widget parent) {
9808 		this(ImageLabel(label), Appearance.checkbox, parent);
9809 	}
9810 
9811 	/// ditto
9812 	this(string label, Appearance appearance, Widget parent) {
9813 		this(ImageLabel(label), appearance, parent);
9814 	}
9815 
9816 	/++
9817 		Changes the look and may change the ideal size of the widget without changing its behavior. The precise look is platform-specific.
9818 
9819 		History:
9820 			Added June 29, 2021 (dub v10.2)
9821 	+/
9822 	enum Appearance {
9823 		checkbox, /// a normal checkbox
9824 		pushbutton, /// a button that is showed as pushed when checked and up when unchecked. Similar to the bold button in a toolbar in Wordpad.
9825 		//sliderswitch,
9826 	}
9827 	private Appearance appearance;
9828 
9829 	/// ditto
9830 	private this(ImageLabel label, Appearance appearance, Widget parent) {
9831 		super(parent);
9832 		version(win32_widgets) {
9833 			this.label = label.label;
9834 
9835 			uint extraStyle;
9836 			final switch(appearance) {
9837 				case Appearance.checkbox:
9838 				break;
9839 				case Appearance.pushbutton:
9840 					extraStyle |= BS_PUSHLIKE;
9841 				break;
9842 			}
9843 
9844 			createWin32Window(this, "button"w, label.label, BS_CHECKBOX | extraStyle);
9845 		} else version(custom_widgets) {
9846 			label.label.extractWindowsStyleLabel(this.label, this.accelerator);
9847 		} else static assert(0);
9848 	}
9849 
9850 	version(custom_widgets)
9851 	override void paint(WidgetPainter painter) {
9852 		auto cs = getComputedStyle();
9853 		if(isFocused()) {
9854 			painter.pen = Pen(Color.black, 1, Pen.Style.Dotted);
9855 			painter.fillColor = cs.windowBackgroundColor;
9856 			painter.drawRectangle(Point(0, 0), width, height);
9857 			painter.pen = Pen(Color.black, 1, Pen.Style.Solid);
9858 		} else {
9859 			painter.pen = Pen(cs.windowBackgroundColor, 1, Pen.Style.Solid);
9860 			painter.fillColor = cs.windowBackgroundColor;
9861 			painter.drawRectangle(Point(0, 0), width, height);
9862 		}
9863 
9864 
9865 		enum buttonSize = 16;
9866 
9867 		painter.outlineColor = Color.black;
9868 		painter.fillColor = Color.white;
9869 		painter.drawRectangle(Point(2, 2), buttonSize - 2, buttonSize - 2);
9870 
9871 		if(isChecked) {
9872 			painter.pen = Pen(Color.black, 2);
9873 			// I'm using height so the checkbox is square
9874 			enum padding = 5;
9875 			painter.drawLine(Point(padding, padding), Point(buttonSize - (padding-2), buttonSize - (padding-2)));
9876 			painter.drawLine(Point(buttonSize-(padding-2), padding), Point(padding, buttonSize - (padding-2)));
9877 
9878 			painter.pen = Pen(Color.black, 1);
9879 		}
9880 
9881 		if(label !is null) {
9882 			painter.outlineColor = cs.foregroundColor();
9883 			painter.fillColor = cs.foregroundColor();
9884 
9885 			// FIXME: should prolly just align the baseline or something
9886 			painter.drawText(Point(buttonSize + 4, 2), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
9887 		}
9888 	}
9889 
9890 	override void defaultEventHandler_triggered(Event ev) {
9891 		isChecked = !isChecked;
9892 
9893 		this.emit!(ChangeEvent!bool)(&isChecked);
9894 
9895 		redraw();
9896 	}
9897 
9898 	/// Emits a change event with the checked state
9899 	mixin Emits!(ChangeEvent!bool);
9900 }
9901 
9902 /// Adds empty space to a layout.
9903 class VerticalSpacer : Widget {
9904 	///
9905 	this(Widget parent) {
9906 		super(parent);
9907 	}
9908 }
9909 
9910 /// ditto
9911 class HorizontalSpacer : Widget {
9912 	///
9913 	this(Widget parent) {
9914 		super(parent);
9915 		this.tabStop = false;
9916 	}
9917 }
9918 
9919 
9920 /++
9921 	Creates a radio button with an associated label. These are usually put inside a [Fieldset].
9922 
9923 
9924 	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
9925 
9926 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
9927 
9928 	History:
9929 		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.
9930 +/
9931 class Radiobox : MouseActivatedWidget {
9932 
9933 	version(win32_widgets) {
9934 		override int maxHeight() { return scaleWithDpi(16); }
9935 		override int minHeight() { return scaleWithDpi(16); }
9936 	} else version(custom_widgets) {
9937 		override int maxHeight() { return defaultLineHeight; }
9938 		override int minHeight() { return defaultLineHeight; }
9939 	} else static assert(0);
9940 
9941 	override int marginLeft() { return 4; }
9942 
9943 	private string label;
9944 	private dchar accelerator;
9945 
9946 	version(win32_widgets)
9947 	this(string label, Widget parent) {
9948 		super(parent);
9949 		this.label = label;
9950 		createWin32Window(this, "button"w, label, BS_AUTORADIOBUTTON);
9951 	}
9952 	else version(custom_widgets)
9953 	this(string label, Widget parent) {
9954 		super(parent);
9955 		label.extractWindowsStyleLabel(this.label, this.accelerator);
9956 		height = 16;
9957 		width = height + 4 + cast(int) label.length * 16;
9958 	}
9959 	else static assert(false);
9960 
9961 	version(custom_widgets)
9962 	override void paint(WidgetPainter painter) {
9963 		auto cs = getComputedStyle();
9964 		if(isFocused) {
9965 			painter.fillColor = cs.windowBackgroundColor;
9966 			painter.pen = Pen(Color.black, 1, Pen.Style.Dotted);
9967 		} else {
9968 			painter.fillColor = cs.windowBackgroundColor;
9969 			painter.outlineColor = cs.windowBackgroundColor;
9970 		}
9971 		painter.drawRectangle(Point(0, 0), width, height);
9972 
9973 		painter.pen = Pen(Color.black, 1, Pen.Style.Solid);
9974 
9975 		enum buttonSize = 16;
9976 
9977 		painter.outlineColor = Color.black;
9978 		painter.fillColor = Color.white;
9979 		painter.drawEllipse(Point(2, 2), Point(buttonSize - 2, buttonSize - 2));
9980 		if(isChecked) {
9981 			painter.outlineColor = Color.black;
9982 			painter.fillColor = Color.black;
9983 			// I'm using height so the checkbox is square
9984 			painter.drawEllipse(Point(5, 5), Point(buttonSize - 5, buttonSize - 5));
9985 		}
9986 
9987 		painter.outlineColor = cs.foregroundColor();
9988 		painter.fillColor = cs.foregroundColor();
9989 
9990 		painter.drawText(Point(buttonSize + 4, 0), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
9991 	}
9992 
9993 
9994 	override void defaultEventHandler_triggered(Event ev) {
9995 		isChecked = true;
9996 
9997 		if(this.parent) {
9998 			foreach(child; this.parent.children) {
9999 				if(child is this) continue;
10000 				if(auto rb = cast(Radiobox) child) {
10001 					rb.isChecked = false;
10002 					rb.emit!(ChangeEvent!bool)(&rb.isChecked);
10003 					rb.redraw();
10004 				}
10005 			}
10006 		}
10007 
10008 		this.emit!(ChangeEvent!bool)(&this.isChecked);
10009 
10010 		redraw();
10011 	}
10012 
10013 	/// 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.
10014 	mixin Emits!(ChangeEvent!bool);
10015 }
10016 
10017 
10018 /++
10019 	Creates a push button with unbounded size. When it is clicked, it emits a `triggered` event.
10020 
10021 
10022 	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
10023 
10024 	Use double-ampersand, "First && Second", to be displayed as a single one, "First & Second".
10025 
10026 	History:
10027 		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.
10028 +/
10029 class Button : MouseActivatedWidget {
10030 	override int heightStretchiness() { return 3; }
10031 	override int widthStretchiness() { return 3; }
10032 
10033 	/++
10034 		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.
10035 
10036 		History:
10037 			Added July 2, 2021
10038 	+/
10039 	public bool triggersOnMultiClick;
10040 
10041 	private string label_;
10042 	private TextAlignment alignment;
10043 	private dchar accelerator;
10044 
10045 	///
10046 	string label() { return label_; }
10047 	///
10048 	void label(string l) {
10049 		label_ = l;
10050 		version(win32_widgets) {
10051 			WCharzBuffer bfr = WCharzBuffer(l);
10052 			SetWindowTextW(hwnd, bfr.ptr);
10053 		} else version(custom_widgets) {
10054 			redraw();
10055 		}
10056 	}
10057 
10058 	override void defaultEventHandler_dblclick(DoubleClickEvent ev) {
10059 		super.defaultEventHandler_dblclick(ev);
10060 		if(triggersOnMultiClick) {
10061 			if(ev.button == MouseButton.left) {
10062 				auto event = new Event(EventType.triggered, this);
10063 				event.sendDirectly();
10064 			}
10065 		}
10066 	}
10067 
10068 	private Sprite sprite;
10069 	private int displayFlags;
10070 
10071 	/++
10072 		Creates a push button with the given label, which may be an image or some text.
10073 
10074 		Bugs:
10075 			If the image is bigger than the button, it may not be displayed in the right position on Linux.
10076 
10077 		History:
10078 			The [ImageLabel] overload was added on June 21, 2021 (dub v10.1).
10079 	+/
10080 	this(ImageLabel label, Widget parent) {
10081 		version(win32_widgets) {
10082 			// FIXME: use ideal button size instead
10083 			width = 50;
10084 			height = 30;
10085 			super(parent);
10086 
10087 			// BS_BITMAP is set when we want image only, so checking for exactly that combination
10088 			enum imgFlags = ImageLabel.DisplayFlags.displayImage;
10089 			auto extraStyle = ((label.displayFlags & imgFlags) == imgFlags) ? BS_BITMAP : 0;
10090 
10091 			createWin32Window(this, "button"w, label.label, BS_PUSHBUTTON | extraStyle);
10092 
10093 			if(label.image) {
10094 				sprite = Sprite.fromMemoryImage(parentWindow.win, label.image);
10095 
10096 				SendMessageW(hwnd, BM_SETIMAGE, IMAGE_BITMAP, cast(LPARAM) sprite.nativeHandle);
10097 			}
10098 
10099 			this.label = label.label;
10100 		} else version(custom_widgets) {
10101 			width = 50;
10102 			height = 30;
10103 			super(parent);
10104 
10105 			label.label.extractWindowsStyleLabel(this.label_, this.accelerator);
10106 
10107 			if(label.image) {
10108 				this.sprite = Sprite.fromMemoryImage(parentWindow.win, label.image);
10109 				this.displayFlags = label.displayFlags;
10110 			}
10111 
10112 			this.alignment = label.alignment;
10113 		}
10114 	}
10115 
10116 	///
10117 	this(string label, Widget parent) {
10118 		this(ImageLabel(label), parent);
10119 	}
10120 
10121 	override int minHeight() { return defaultLineHeight + 4; }
10122 
10123 	static class Style : Widget.Style {
10124 		override WidgetBackground background() {
10125 			auto cs = widget.getComputedStyle(); // FIXME: this is potentially recursive
10126 
10127 			auto pressed = DynamicState.depressed | DynamicState.hover;
10128 			if((widget.dynamicState & pressed) == pressed) {
10129 				return WidgetBackground(cs.depressedButtonColor());
10130 			} else if(widget.dynamicState & DynamicState.hover) {
10131 				return WidgetBackground(cs.hoveringColor());
10132 			} else {
10133 				return WidgetBackground(cs.buttonColor());
10134 			}
10135 		}
10136 
10137 		override FrameStyle borderStyle() {
10138 			auto pressed = DynamicState.depressed | DynamicState.hover;
10139 			if((widget.dynamicState & pressed) == pressed) {
10140 				return FrameStyle.sunk;
10141 			} else {
10142 				return FrameStyle.risen;
10143 			}
10144 
10145 		}
10146 
10147 		override bool variesWithState(ulong dynamicStateFlags) {
10148 			return super.variesWithState(dynamicStateFlags) || (dynamicStateFlags & (DynamicState.depressed | DynamicState.hover));
10149 		}
10150 	}
10151 	mixin OverrideStyle!Style;
10152 
10153 	version(custom_widgets)
10154 	override void paint(WidgetPainter painter) {
10155 		painter.drawThemed(delegate Rectangle(const Rectangle bounds) {
10156 			if(sprite) {
10157 				sprite.drawAt(
10158 					painter,
10159 					bounds.upperLeft + Point((bounds.width - sprite.width) / 2, (bounds.height - sprite.height) / 2),
10160 					Point(0, 0),
10161 					bounds.size
10162 				);
10163 			} else {
10164 				painter.drawText(bounds.upperLeft, label, bounds.lowerRight, alignment | TextAlignment.VerticalCenter);
10165 			}
10166 			return bounds;
10167 		});
10168 	}
10169 
10170 	override int flexBasisWidth() {
10171 		version(win32_widgets) {
10172 			SIZE size;
10173 			SendMessage(hwnd, BCM_GETIDEALSIZE, 0, cast(LPARAM) &size);
10174 			if(size.cx == 0)
10175 				goto fallback;
10176 			return size.cx + scaleWithDpi(16);
10177 		}
10178 		fallback:
10179 			return scaleWithDpi(cast(int) label.length * 8 + 16);
10180 	}
10181 
10182 	override int flexBasisHeight() {
10183 		version(win32_widgets) {
10184 			SIZE size;
10185 			SendMessage(hwnd, BCM_GETIDEALSIZE, 0, cast(LPARAM) &size);
10186 			if(size.cy == 0)
10187 				goto fallback;
10188 			return size.cy + scaleWithDpi(6);
10189 		}
10190 		fallback:
10191 			return defaultLineHeight + 4;
10192 	}
10193 }
10194 
10195 /++
10196 	A button with a consistent size, suitable for user commands like OK and cANCEL.
10197 +/
10198 class CommandButton : Button {
10199 	this(string label, Widget parent) {
10200 		super(label, parent);
10201 	}
10202 
10203 	override int maxHeight() {
10204 		return defaultLineHeight + 4;
10205 	}
10206 
10207 	override int maxWidth() {
10208 		return defaultLineHeight * 4;
10209 	}
10210 
10211 	override int marginLeft() { return 12; }
10212 	override int marginRight() { return 12; }
10213 	override int marginTop() { return 12; }
10214 	override int marginBottom() { return 12; }
10215 }
10216 
10217 ///
10218 enum ArrowDirection {
10219 	left, ///
10220 	right, ///
10221 	up, ///
10222 	down ///
10223 }
10224 
10225 ///
10226 version(custom_widgets)
10227 class ArrowButton : Button {
10228 	///
10229 	this(ArrowDirection direction, Widget parent) {
10230 		super("", parent);
10231 		this.direction = direction;
10232 		triggersOnMultiClick = true;
10233 	}
10234 
10235 	private ArrowDirection direction;
10236 
10237 	override int minHeight() { return 16; }
10238 	override int maxHeight() { return 16; }
10239 	override int minWidth() { return 16; }
10240 	override int maxWidth() { return 16; }
10241 
10242 	override void paint(WidgetPainter painter) {
10243 		super.paint(painter);
10244 
10245 		auto cs = getComputedStyle();
10246 
10247 		painter.outlineColor = cs.foregroundColor;
10248 		painter.fillColor = cs.foregroundColor;
10249 
10250 		auto offset = Point((this.width - 16) / 2, (this.height - 16) / 2);
10251 
10252 		final switch(direction) {
10253 			case ArrowDirection.up:
10254 				painter.drawPolygon(
10255 					Point(2, 10) + offset,
10256 					Point(7, 5) + offset,
10257 					Point(12, 10) + offset,
10258 					Point(2, 10) + offset
10259 				);
10260 			break;
10261 			case ArrowDirection.down:
10262 				painter.drawPolygon(
10263 					Point(2, 6) + offset,
10264 					Point(7, 11) + offset,
10265 					Point(12, 6) + offset,
10266 					Point(2, 6) + offset
10267 				);
10268 			break;
10269 			case ArrowDirection.left:
10270 				painter.drawPolygon(
10271 					Point(10, 2) + offset,
10272 					Point(5, 7) + offset,
10273 					Point(10, 12) + offset,
10274 					Point(10, 2) + offset
10275 				);
10276 			break;
10277 			case ArrowDirection.right:
10278 				painter.drawPolygon(
10279 					Point(6, 2) + offset,
10280 					Point(11, 7) + offset,
10281 					Point(6, 12) + offset,
10282 					Point(6, 2) + offset
10283 				);
10284 			break;
10285 		}
10286 	}
10287 }
10288 
10289 private
10290 int[2] getChildPositionRelativeToParentOrigin(Widget c) nothrow {
10291 	int x, y;
10292 	Widget par = c;
10293 	while(par) {
10294 		x += par.x;
10295 		y += par.y;
10296 		par = par.parent;
10297 	}
10298 	return [x, y];
10299 }
10300 
10301 version(win32_widgets)
10302 private
10303 int[2] getChildPositionRelativeToParentHwnd(Widget c) nothrow {
10304 // MapWindowPoints?
10305 	int x, y;
10306 	Widget par = c;
10307 	while(par) {
10308 		x += par.x;
10309 		y += par.y;
10310 		par = par.parent;
10311 		if(par !is null && par.useNativeDrawing())
10312 			break;
10313 	}
10314 	return [x, y];
10315 }
10316 
10317 ///
10318 class ImageBox : Widget {
10319 	private MemoryImage image_;
10320 
10321 	override int widthStretchiness() { return 1; }
10322 	override int heightStretchiness() { return 1; }
10323 	override int widthShrinkiness() { return 1; }
10324 	override int heightShrinkiness() { return 1; }
10325 
10326 	override int flexBasisHeight() {
10327 		return image_.height;
10328 	}
10329 
10330 	override int flexBasisWidth() {
10331 		return image_.width;
10332 	}
10333 
10334 	///
10335 	public void setImage(MemoryImage image){
10336 		this.image_ = image;
10337 		if(this.parentWindow && this.parentWindow.win)
10338 			sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_));
10339 		redraw();
10340 	}
10341 
10342 	/// How to fit the image in the box if they aren't an exact match in size?
10343 	enum HowToFit {
10344 		center, /// centers the image, cropping around all the edges as needed
10345 		crop, /// always draws the image in the upper left, cropping the lower right if needed
10346 		// stretch, /// not implemented
10347 	}
10348 
10349 	private Sprite sprite;
10350 	private HowToFit howToFit_;
10351 
10352 	private Color backgroundColor_;
10353 
10354 	///
10355 	this(MemoryImage image, HowToFit howToFit, Color backgroundColor, Widget parent) {
10356 		this.image_ = image;
10357 		this.tabStop = false;
10358 		this.howToFit_ = howToFit;
10359 		this.backgroundColor_ = backgroundColor;
10360 		super(parent);
10361 		updateSprite();
10362 	}
10363 
10364 	/// ditto
10365 	this(MemoryImage image, HowToFit howToFit, Widget parent) {
10366 		this(image, howToFit, Color.transparent, parent);
10367 	}
10368 
10369 	private void updateSprite() {
10370 		if(sprite is null && this.parentWindow && this.parentWindow.win) {
10371 			sprite = new Sprite(this.parentWindow.win, Image.fromMemoryImage(image_));
10372 		}
10373 	}
10374 
10375 	override void paint(WidgetPainter painter) {
10376 		updateSprite();
10377 		if(backgroundColor_.a) {
10378 			painter.fillColor = backgroundColor_;
10379 			painter.drawRectangle(Point(0, 0), width, height);
10380 		}
10381 		if(howToFit_ == HowToFit.crop)
10382 			sprite.drawAt(painter, Point(0, 0));
10383 		else if(howToFit_ == HowToFit.center) {
10384 			sprite.drawAt(painter, Point((width - image_.width) / 2, (height - image_.height) / 2));
10385 		}
10386 	}
10387 }
10388 
10389 ///
10390 class TextLabel : Widget {
10391 	override int maxHeight() { return defaultLineHeight; }
10392 	override int minHeight() { return defaultLineHeight; }
10393 	override int minWidth() { return 32; }
10394 
10395 	override int flexBasisHeight() { return minHeight(); }
10396 	override int flexBasisWidth() { return cast(int) label_.length * 7; }
10397 
10398 	string label_;
10399 
10400 	/++
10401 		Indicates which other control this label is here for. Similar to HTML `for` attribute.
10402 
10403 		In practice this means a click on the label will focus the `labelFor`. In future versions
10404 		it will also set screen reader hints but that is not yet implemented.
10405 
10406 		History:
10407 			Added October 3, 2021 (dub v10.4)
10408 	+/
10409 	Widget labelFor;
10410 
10411 	///
10412 	@scriptable
10413 	string label() { return label_; }
10414 
10415 	///
10416 	@scriptable
10417 	void label(string l) {
10418 		label_ = l;
10419 		version(win32_widgets) {
10420 			WCharzBuffer bfr = WCharzBuffer(l);
10421 			SetWindowTextW(hwnd, bfr.ptr);
10422 		} else version(custom_widgets)
10423 			redraw();
10424 	}
10425 
10426 	///
10427 	this(string label, TextAlignment alignment, Widget parent) {
10428 		this.label_ = label;
10429 		this.alignment = alignment;
10430 		this.tabStop = false;
10431 		super(parent);
10432 
10433 		version(win32_widgets)
10434 		createWin32Window(this, "static"w, label, (alignment & TextAlignment.Center) ? SS_CENTER : 0, (alignment & TextAlignment.Right) ? WS_EX_RIGHT : WS_EX_LEFT);
10435 	}
10436 
10437 	override void defaultEventHandler_click(scope ClickEvent ce) {
10438 		if(this.labelFor !is null)
10439 			this.labelFor.focus();
10440 	}
10441 
10442 	/++
10443 		WARNING: this currently sets TextAlignment.Right as the default. That will change in a future version.
10444 		For future-proofing of your code, if you rely on TextAlignment.Right, you MUST specify that explicitly.
10445 	+/
10446 	this(string label, Widget parent) {
10447 		this(label, TextAlignment.Right, parent);
10448 	}
10449 
10450 
10451 	TextAlignment alignment;
10452 
10453 	version(custom_widgets)
10454 	override void paint(WidgetPainter painter) {
10455 		painter.outlineColor = getComputedStyle().foregroundColor;
10456 		painter.drawText(Point(0, 0), this.label, Point(width, height), alignment);
10457 	}
10458 
10459 }
10460 
10461 version(custom_widgets)
10462 	private struct etc {
10463 		mixin ExperimentalTextComponent;
10464 	}
10465 
10466 version(win32_widgets)
10467 	alias EditableTextWidgetParent = Widget; ///
10468 else version(custom_widgets)
10469 	alias EditableTextWidgetParent = ScrollableWidget; ///
10470 else static assert(0);
10471 
10472 /// Contains the implementation of text editing
10473 abstract class EditableTextWidget : EditableTextWidgetParent {
10474 	this(Widget parent) {
10475 		super(parent);
10476 	}
10477 
10478 	bool wordWrapEnabled_ = false;
10479 	void wordWrapEnabled(bool enabled) {
10480 		version(win32_widgets) {
10481 			SendMessageW(hwnd, EM_FMTLINES, enabled ? 1 : 0, 0);
10482 		} else version(custom_widgets) {
10483 			wordWrapEnabled_ = enabled; // FIXME
10484 		} else static assert(false);
10485 	}
10486 
10487 	override int minWidth() { return 16; }
10488 	override int minHeight() { return defaultLineHeight + 0; } // the +0 is to leave room for the padding
10489 	override int widthStretchiness() { return 7; }
10490 
10491 	void selectAll() {
10492 		version(win32_widgets)
10493 			SendMessage(hwnd, EM_SETSEL, 0, -1);
10494 		else version(custom_widgets) {
10495 			textLayout.selectAll();
10496 			redraw();
10497 		}
10498 	}
10499 
10500 	@property string content() {
10501 		version(win32_widgets) {
10502 			wchar[4096] bufferstack;
10503 			wchar[] buffer;
10504 			auto len = GetWindowTextLength(hwnd);
10505 			if(len < bufferstack.length)
10506 				buffer = bufferstack[0 .. len + 1];
10507 			else
10508 				buffer = new wchar[](len + 1);
10509 
10510 			auto l = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
10511 			if(l >= 0)
10512 				return makeUtf8StringFromWindowsString(buffer[0 .. l]);
10513 			else
10514 				return null;
10515 		} else version(custom_widgets) {
10516 			return textLayout.getPlainText();
10517 		} else static assert(false);
10518 	}
10519 	@property void content(string s) {
10520 		version(win32_widgets) {
10521 			WCharzBuffer bfr = WCharzBuffer(s, WindowsStringConversionFlags.convertNewLines);
10522 			SetWindowTextW(hwnd, bfr.ptr);
10523 		} else version(custom_widgets) {
10524 			textLayout.clear();
10525 			textLayout.addText(s);
10526 
10527 			{
10528 			// FIXME: it should be able to get this info easier
10529 			auto painter = draw();
10530 			textLayout.redoLayout(painter);
10531 			}
10532 			auto cbb = textLayout.contentBoundingBox();
10533 			setContentSize(cbb.width, cbb.height);
10534 			/*
10535 			textLayout.addText(ForegroundColor.red, s);
10536 			textLayout.addText(ForegroundColor.blue, TextFormat.underline, "http://dpldocs.info/");
10537 			textLayout.addText(" is the best!");
10538 			*/
10539 			redraw();
10540 		}
10541 		else static assert(false);
10542 	}
10543 
10544 	void addText(string txt) {
10545 		version(custom_widgets) {
10546 
10547 			textLayout.addText(txt);
10548 
10549 			{
10550 			// FIXME: it should be able to get this info easier
10551 			auto painter = draw();
10552 			textLayout.redoLayout(painter);
10553 			}
10554 			auto cbb = textLayout.contentBoundingBox();
10555 			setContentSize(cbb.width, cbb.height);
10556 
10557 		} else version(win32_widgets) {
10558 			// get the current selection
10559 			DWORD StartPos, EndPos;
10560 			SendMessageW( hwnd, EM_GETSEL, cast(WPARAM)(&StartPos), cast(LPARAM)(&EndPos) );
10561 
10562 			// move the caret to the end of the text
10563 			int outLength = GetWindowTextLengthW(hwnd);
10564 			SendMessageW( hwnd, EM_SETSEL, outLength, outLength );
10565 
10566 			// insert the text at the new caret position
10567 			WCharzBuffer bfr = WCharzBuffer(txt, WindowsStringConversionFlags.convertNewLines);
10568 			SendMessageW( hwnd, EM_REPLACESEL, TRUE, cast(LPARAM) bfr.ptr );
10569 
10570 			// restore the previous selection
10571 			SendMessageW( hwnd, EM_SETSEL, StartPos, EndPos );
10572 		} else static assert(0);
10573 	}
10574 
10575 	version(custom_widgets)
10576 	override void paintFrameAndBackground(WidgetPainter painter) {
10577 		this.draw3dFrame(painter, FrameStyle.sunk, Color.white);
10578 	}
10579 
10580 	version(win32_widgets) { /* will do it with Windows calls in the classes */ }
10581 	else version(custom_widgets) {
10582 		// FIXME
10583 
10584 		static if(SimpledisplayTimerAvailable)
10585 			Timer caretTimer;
10586 		etc.TextLayout textLayout;
10587 
10588 		void setupCustomTextEditing() {
10589 			textLayout = new etc.TextLayout(Rectangle(4, 2, width - 8, height - 4));
10590 			textLayout.selectionXorColor = getComputedStyle().activeListXorColor;
10591 		}
10592 
10593 		override void paint(WidgetPainter painter) {
10594 			if(parentWindow.win.closed) return;
10595 
10596 			textLayout.boundingBox = Rectangle(4, 2, width - 8, height - 4);
10597 
10598 			/*
10599 			painter.outlineColor = Color.white;
10600 			painter.fillColor = Color.white;
10601 			painter.drawRectangle(Point(4, 4), contentWidth, contentHeight);
10602 			*/
10603 
10604 			painter.outlineColor = Color.black;
10605 			// painter.drawText(Point(4, 4), content, Point(width - 4, height - 4));
10606 
10607 			textLayout.caretShowingOnScreen = false;
10608 
10609 			textLayout.drawInto(painter, !parentWindow.win.closed && isFocused());
10610 		}
10611 
10612 		static class Style : Widget.Style {
10613 			override MouseCursor cursor() {
10614 				return GenericCursor.Text;
10615 			}
10616 		}
10617 		mixin OverrideStyle!Style;
10618 	}
10619 	else static assert(false);
10620 
10621 
10622 
10623 	version(custom_widgets)
10624 	override void defaultEventHandler_mousedown(MouseDownEvent ev) {
10625 		super.defaultEventHandler_mousedown(ev);
10626 		if(parentWindow.win.closed) return;
10627 		if(ev.button == MouseButton.left) {
10628 			if(textLayout.selectNone())
10629 				redraw();
10630 			textLayout.moveCaretToPixelCoordinates(ev.clientX, ev.clientY);
10631 			this.focus();
10632 			//this.parentWindow.win.grabInput();
10633 		} else if(ev.button == MouseButton.middle) {
10634 			static if(UsingSimpledisplayX11) {
10635 				getPrimarySelection(parentWindow.win, (txt) {
10636 					textLayout.insert(txt);
10637 					redraw();
10638 
10639 					auto cbb = textLayout.contentBoundingBox();
10640 					setContentSize(cbb.width, cbb.height);
10641 				});
10642 			}
10643 		}
10644 	}
10645 
10646 	version(custom_widgets)
10647 	override void defaultEventHandler_mouseup(MouseUpEvent ev) {
10648 		//this.parentWindow.win.releaseInputGrab();
10649 		super.defaultEventHandler_mouseup(ev);
10650 	}
10651 
10652 	version(custom_widgets)
10653 	override void defaultEventHandler_mousemove(MouseMoveEvent ev) {
10654 		super.defaultEventHandler_mousemove(ev);
10655 		if(ev.state & ModifierState.leftButtonDown) {
10656 			textLayout.selectToPixelCoordinates(ev.clientX, ev.clientY);
10657 			redraw();
10658 		}
10659 	}
10660 
10661 	version(custom_widgets)
10662 	override void defaultEventHandler_focus(Event ev) {
10663 		super.defaultEventHandler_focus(ev);
10664 		if(parentWindow.win.closed) return;
10665 		auto painter = this.draw();
10666 		textLayout.drawCaret(painter);
10667 
10668 		static if(SimpledisplayTimerAvailable)
10669 		if(caretTimer) {
10670 			caretTimer.destroy();
10671 			caretTimer = null;
10672 		}
10673 
10674 		bool blinkingCaret = true;
10675 		static if(UsingSimpledisplayX11)
10676 			if(!Image.impl.xshmAvailable)
10677 				blinkingCaret = false; // if on a remote connection, don't waste bandwidth on an expendable blink
10678 
10679 		if(blinkingCaret)
10680 		static if(SimpledisplayTimerAvailable)
10681 		caretTimer = new Timer(500, {
10682 			if(parentWindow.win.closed) {
10683 				caretTimer.destroy();
10684 				return;
10685 			}
10686 			if(isFocused()) {
10687 				auto painter = this.draw();
10688 				textLayout.drawCaret(painter);
10689 			} else if(textLayout.caretShowingOnScreen) {
10690 				auto painter = this.draw();
10691 				textLayout.eraseCaret(painter);
10692 			}
10693 		});
10694 	}
10695 
10696 	override void defaultEventHandler_blur(Event ev) {
10697 		super.defaultEventHandler_blur(ev);
10698 		if(parentWindow.win.closed) return;
10699 		version(custom_widgets) {
10700 			auto painter = this.draw();
10701 			textLayout.eraseCaret(painter);
10702 			static if(SimpledisplayTimerAvailable)
10703 			if(caretTimer) {
10704 				caretTimer.destroy();
10705 				caretTimer = null;
10706 			}
10707 		}
10708 
10709 		auto evt = new ChangeEvent!string(this, &this.content);
10710 		evt.dispatch();
10711 	}
10712 
10713 	version(custom_widgets)
10714 	override void defaultEventHandler_char(CharEvent ev) {
10715 		super.defaultEventHandler_char(ev);
10716 		textLayout.insert(ev.character);
10717 		redraw();
10718 
10719 		// FIXME: too inefficient
10720 		auto cbb = textLayout.contentBoundingBox();
10721 		setContentSize(cbb.width, cbb.height);
10722 	}
10723 	version(custom_widgets)
10724 	override void defaultEventHandler_keydown(KeyDownEvent ev) {
10725 		//super.defaultEventHandler_keydown(ev);
10726 		switch(ev.key) {
10727 			case Key.Delete:
10728 				textLayout.delete_();
10729 				redraw();
10730 			break;
10731 			case Key.Left:
10732 				textLayout.moveLeft();
10733 				redraw();
10734 			break;
10735 			case Key.Right:
10736 				textLayout.moveRight();
10737 				redraw();
10738 			break;
10739 			case Key.Up:
10740 				textLayout.moveUp();
10741 				redraw();
10742 			break;
10743 			case Key.Down:
10744 				textLayout.moveDown();
10745 				redraw();
10746 			break;
10747 			case Key.Home:
10748 				textLayout.moveHome();
10749 				redraw();
10750 			break;
10751 			case Key.End:
10752 				textLayout.moveEnd();
10753 				redraw();
10754 			break;
10755 			case Key.PageUp:
10756 				foreach(i; 0 .. 32)
10757 				textLayout.moveUp();
10758 				redraw();
10759 			break;
10760 			case Key.PageDown:
10761 				foreach(i; 0 .. 32)
10762 				textLayout.moveDown();
10763 				redraw();
10764 			break;
10765 
10766 			default:
10767 				 {} // intentionally blank, let "char" handle it
10768 		}
10769 		/*
10770 		if(ev.key == Key.Backspace) {
10771 			textLayout.backspace();
10772 			redraw();
10773 		}
10774 		*/
10775 		ensureVisibleInScroll(textLayout.caretBoundingBox());
10776 	}
10777 
10778 
10779 }
10780 
10781 ///
10782 class LineEdit : EditableTextWidget {
10783 	// FIXME: hack
10784 	version(custom_widgets) {
10785 	override bool showingVerticalScroll() { return false; }
10786 	override bool showingHorizontalScroll() { return false; }
10787 	}
10788 
10789 	override int flexBasisWidth() { return 250; }
10790 
10791 	///
10792 	this(Widget parent) {
10793 		super(parent);
10794 		version(win32_widgets) {
10795 			createWin32Window(this, "edit"w, "", 
10796 				0, WS_EX_CLIENTEDGE);//|WS_HSCROLL|ES_AUTOHSCROLL);
10797 		} else version(custom_widgets) {
10798 			setupCustomTextEditing();
10799 			addEventListener(delegate(CharEvent ev) {
10800 				if(ev.character == '\n')
10801 					ev.preventDefault();
10802 			});
10803 		} else static assert(false);
10804 	}
10805 	override int maxHeight() { return defaultLineHeight + 4; }
10806 	override int minHeight() { return defaultLineHeight + 4; }
10807 
10808 	/+
10809 	@property void passwordMode(bool p) {
10810 		SetWindowLongPtr(hwnd, GWL_STYLE, GetWindowLongPtr(hwnd, GWL_STYLE) | ES_PASSWORD);
10811 	}
10812 	+/
10813 }
10814 
10815 /++
10816 	A [LineEdit] that displays `*` in place of the actual characters.
10817 
10818 	Alas, Windows requires the window to be created differently to use this style,
10819 	so it had to be a new class instead of a toggle on and off on an existing object.
10820 
10821 	FIXME: this is not yet implemented on Linux, it will work the same as a TextEdit there for now.
10822 
10823 	History:
10824 		Added January 24, 2021
10825 +/
10826 class PasswordEdit : EditableTextWidget {
10827 	version(custom_widgets) {
10828 	override bool showingVerticalScroll() { return false; }
10829 	override bool showingHorizontalScroll() { return false; }
10830 	}
10831 
10832 	override int flexBasisWidth() { return 250; }
10833 
10834 	///
10835 	this(Widget parent) {
10836 		super(parent);
10837 		version(win32_widgets) {
10838 			createWin32Window(this, "edit"w, "", 
10839 				ES_PASSWORD, WS_EX_CLIENTEDGE);//|WS_HSCROLL|ES_AUTOHSCROLL);
10840 		} else version(custom_widgets) {
10841 			setupCustomTextEditing();
10842 			addEventListener(delegate(CharEvent ev) {
10843 				if(ev.character == '\n')
10844 					ev.preventDefault();
10845 			});
10846 		} else static assert(false);
10847 	}
10848 	override int maxHeight() { return defaultLineHeight + 4; }
10849 	override int minHeight() { return defaultLineHeight + 4; }
10850 }
10851 
10852 
10853 ///
10854 class TextEdit : EditableTextWidget {
10855 	///
10856 	this(Widget parent) {
10857 		super(parent);
10858 		version(win32_widgets) {
10859 			createWin32Window(this, "edit"w, "", 
10860 				0|WS_VSCROLL|WS_HSCROLL|ES_MULTILINE|ES_WANTRETURN|ES_AUTOHSCROLL|ES_AUTOVSCROLL, WS_EX_CLIENTEDGE);
10861 		} else version(custom_widgets) {
10862 			setupCustomTextEditing();
10863 		} else static assert(false);
10864 	}
10865 	override int maxHeight() { return int.max; }
10866 	override int heightStretchiness() { return 7; }
10867 
10868 	override int flexBasisWidth() { return 250; }
10869 	override int flexBasisHeight() { return 250; }
10870 }
10871 
10872 
10873 /++
10874 
10875 +/
10876 version(none)
10877 class RichTextDisplay : Widget {
10878 	@property void content(string c) {}
10879 	void appendContent(string c) {}
10880 }
10881 
10882 ///
10883 class MessageBox : Window {
10884 	private string message;
10885 	MessageBoxButton buttonPressed = MessageBoxButton.None;
10886 	///
10887 	this(string message, string[] buttons = ["OK"], MessageBoxButton[] buttonIds = [MessageBoxButton.OK]) {
10888 		super(300, 100);
10889 
10890 		assert(buttons.length);
10891 		assert(buttons.length ==  buttonIds.length);
10892 
10893 		this.message = message;
10894 
10895 		int buttonsWidth = cast(int) buttons.length * 50 + (cast(int) buttons.length - 1) * 16;
10896 
10897 		int x = this.width / 2 - buttonsWidth / 2;
10898 
10899 		foreach(idx, buttonText; buttons) {
10900 			auto button = new Button(buttonText, this);
10901 			button.x = x;
10902 			button.y = height - (button.height + 10);
10903 			button.addEventListener(EventType.triggered, ((size_t idx) { return () {
10904 				this.buttonPressed = buttonIds[idx];
10905 				win.close();
10906 			}; })(idx));
10907 
10908 			button.registerMovement();
10909 			x += button.width;
10910 			x += 16;
10911 			if(idx == 0)
10912 				button.focus();
10913 		}
10914 
10915 		win.show();
10916 		redraw();
10917 	}
10918 
10919 	override void paint(WidgetPainter painter) {
10920 		super.paint(painter);
10921 
10922 		auto cs = getComputedStyle();
10923 
10924 		painter.outlineColor = cs.foregroundColor();
10925 		painter.fillColor = cs.foregroundColor();
10926 
10927 		painter.drawText(Point(0, 0), message, Point(width, height / 2), TextAlignment.Center | TextAlignment.VerticalCenter);
10928 	}
10929 
10930 	// this one is all fixed position
10931 	override void recomputeChildLayout() {}
10932 }
10933 
10934 ///
10935 enum MessageBoxStyle {
10936 	OK, ///
10937 	OKCancel, ///
10938 	RetryCancel, ///
10939 	YesNo, ///
10940 	YesNoCancel, ///
10941 	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.
10942 }
10943 
10944 ///
10945 enum MessageBoxIcon {
10946 	None, ///
10947 	Info, ///
10948 	Warning, ///
10949 	Error ///
10950 }
10951 
10952 /// Identifies the button the user pressed on a message box.
10953 enum MessageBoxButton {
10954 	None, /// The user closed the message box without clicking any of the buttons.
10955 	OK, ///
10956 	Cancel, ///
10957 	Retry, ///
10958 	Yes, ///
10959 	No, ///
10960 	Continue ///
10961 }
10962 
10963 
10964 /++
10965 	Displays a modal message box, blocking until the user dismisses it.
10966 
10967 	Returns: the button pressed.
10968 +/
10969 MessageBoxButton messageBox(string title, string message, MessageBoxStyle style = MessageBoxStyle.OK, MessageBoxIcon icon = MessageBoxIcon.None) {
10970 	version(win32_widgets) {
10971 		WCharzBuffer t = WCharzBuffer(title);
10972 		WCharzBuffer m = WCharzBuffer(message);
10973 		UINT type;
10974 		with(MessageBoxStyle)
10975 		final switch(style) {
10976 			case OK: type |= MB_OK; break;
10977 			case OKCancel: type |= MB_OKCANCEL; break;
10978 			case RetryCancel: type |= MB_RETRYCANCEL; break;
10979 			case YesNo: type |= MB_YESNO; break;
10980 			case YesNoCancel: type |= MB_YESNOCANCEL; break;
10981 			case RetryCancelContinue: type |= MB_CANCELTRYCONTINUE; break;
10982 		}
10983 		with(MessageBoxIcon)
10984 		final switch(icon) {
10985 			case None: break;
10986 			case Info: type |= MB_ICONINFORMATION; break;
10987 			case Warning: type |= MB_ICONWARNING; break;
10988 			case Error: type |= MB_ICONERROR; break;
10989 		}
10990 		switch(MessageBoxW(null, m.ptr, t.ptr, type)) {
10991 			case IDOK: return MessageBoxButton.OK;
10992 			case IDCANCEL: return MessageBoxButton.Cancel;
10993 			case IDTRYAGAIN, IDRETRY: return MessageBoxButton.Retry;
10994 			case IDYES: return MessageBoxButton.Yes;
10995 			case IDNO: return MessageBoxButton.No;
10996 			case IDCONTINUE: return MessageBoxButton.Continue;
10997 			default: return MessageBoxButton.None;
10998 		}
10999 	} else {
11000 		string[] buttons;
11001 		MessageBoxButton[] buttonIds;
11002 		with(MessageBoxStyle)
11003 		final switch(style) {
11004 			case OK:
11005 				buttons = ["OK"];
11006 				buttonIds = [MessageBoxButton.OK];
11007 			break;
11008 			case OKCancel:
11009 				buttons = ["OK", "Cancel"];
11010 				buttonIds = [MessageBoxButton.OK, MessageBoxButton.Cancel];
11011 			break;
11012 			case RetryCancel:
11013 				buttons = ["Retry", "Cancel"];
11014 				buttonIds = [MessageBoxButton.Retry, MessageBoxButton.Cancel];
11015 			break;
11016 			case YesNo:
11017 				buttons = ["Yes", "No"];
11018 				buttonIds = [MessageBoxButton.Yes, MessageBoxButton.No];
11019 			break;
11020 			case YesNoCancel:
11021 				buttons = ["Yes", "No", "Cancel"];
11022 				buttonIds = [MessageBoxButton.Yes, MessageBoxButton.No, MessageBoxButton.Cancel];
11023 			break;
11024 			case RetryCancelContinue:
11025 				buttons = ["Try Again", "Cancel", "Continue"];
11026 				buttonIds = [MessageBoxButton.Retry, MessageBoxButton.Cancel, MessageBoxButton.Continue];
11027 			break;
11028 		}
11029 		auto mb = new MessageBox(message, buttons, buttonIds);
11030 		EventLoop el = EventLoop.get;
11031 		el.run(() { return !mb.win.closed; });
11032 		return mb.buttonPressed;
11033 	}
11034 }
11035 
11036 /// ditto
11037 int messageBox(string message, MessageBoxStyle style = MessageBoxStyle.OK, MessageBoxIcon icon = MessageBoxIcon.None) {
11038 	return messageBox(null, message, style, icon);
11039 }
11040 
11041 
11042 
11043 ///
11044 alias void delegate(Widget handlerAttachedTo, Event event) EventHandler;
11045 
11046 /++
11047 	This is an opaque type you can use to disconnect an event handler when you're no longer interested.
11048 
11049 	History:
11050 		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.
11051 +/
11052 struct EventListener {
11053 	private Widget widget;
11054 	private string event;
11055 	private EventHandler handler;
11056 	private bool useCapture;
11057 
11058 	///
11059 	void disconnect() {
11060 		widget.removeEventListener(this);
11061 	}
11062 }
11063 
11064 /++
11065 	The purpose of this enum was to give a compile-time checked version of various standard event strings.
11066 
11067 	Now, I recommend you use a statically typed event object instead.
11068 
11069 	See_Also: [Event]
11070 +/
11071 enum EventType : string {
11072 	click = "click", ///
11073 
11074 	mouseenter = "mouseenter", ///
11075 	mouseleave = "mouseleave", ///
11076 	mousein = "mousein", ///
11077 	mouseout = "mouseout", ///
11078 	mouseup = "mouseup", ///
11079 	mousedown = "mousedown", ///
11080 	mousemove = "mousemove", ///
11081 
11082 	keydown = "keydown", ///
11083 	keyup = "keyup", ///
11084 	char_ = "char", ///
11085 
11086 	focus = "focus", ///
11087 	blur = "blur", ///
11088 
11089 	triggered = "triggered", ///
11090 
11091 	change = "change", ///
11092 }
11093 
11094 /++
11095 	Represents an event that is currently being processed.
11096 
11097 
11098 	Minigui's event model is based on the web browser. An event has a name, a target,
11099 	and an associated data object. It starts from the window and works its way down through
11100 	the target through all intermediate [Widget]s, triggering capture phase handlers as it goes,
11101 	then goes back up again all the way back to the window, triggering bubble phase handlers. At
11102 	the end, if [Event.preventDefault] has not been called, it calls the target widget's default
11103 	handlers for the event (please note that default handlers will be called even if [Event.stopPropagation]
11104 	was called; that just stops it from calling other handlers in the widget tree, but the default happens
11105 	whenever propagation is done, not only if it gets to the end of the chain).
11106 
11107 	This model has several nice points:
11108 
11109 	$(LIST
11110 		* It is easy to delegate dynamic handlers to a parent. You can have a parent container
11111 		  with event handlers set, then add/remove children as much as you want without needing
11112 		  to manage the event handlers on them - the parent alone can manage everything.
11113 
11114 		* It is easy to create new custom events in your application.
11115 
11116 		* It is familiar to many web developers.
11117 	)
11118 
11119 	There's a few downsides though:
11120 
11121 	$(LIST
11122 		* There's not a lot of type safety.
11123 
11124 		* You don't get a static list of what events a widget can emit.
11125 
11126 		* Tracing where an event got cancelled along the chain can get difficult; the downside of
11127 		  the central delegation benefit is it can be lead to debugging of action at a distance.
11128 	)
11129 
11130 	In May 2021, I started to adjust this model to minigui takes better advantage of D over Javascript
11131 	while keeping the benefits - and most compatibility with - the existing model. The main idea is
11132 	to simply use a D object type which provides a static interface as well as a built-in event name.
11133 	Then, a new static interface allows you to see what an event can emit and attach handlers to it
11134 	similarly to C#, which just forwards to the JS style api. They're fully compatible so you can still
11135 	delegate to a parent and use custom events as well as using the runtime dynamic access, in addition
11136 	to having a little more help from the D compiler and documentation generator.
11137 
11138 	Your code would change like this:
11139 
11140 	---
11141 	// old
11142 	widget.addEventListener("keydown", (Event ev) { ... }, /* optional arg */ useCapture );
11143 
11144 	// new
11145 	widget.addEventListener((KeyDownEvent ev) { ... }, /* optional arg */ useCapture );
11146 	---
11147 
11148 	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.
11149 
11150 	All you have to do is replace the string with a specific Event subclass. It will figure out the event string from the class.
11151 
11152 	Alternatively, you can cast the Event yourself to the appropriate subclass, but it is easier to let the library do it for you!
11153 
11154 	Thus the family of functions are:
11155 
11156 	[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.
11157 
11158 	[Widget.addDirectEventListener] is addEventListener, but only calls the handler if target == this. Useful for something you can't afford to delegate.
11159 
11160 	[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.
11161 
11162 	Let's implement a custom widget that can emit a ChangeEvent describing its `checked` property:
11163 
11164 	---
11165 	class MyCheckbox : Widget {
11166 		/// This gives a chance to document it and generates a convenience function to send it and attach handlers.
11167 		/// It is NOT actually required but should be used whenever possible.
11168 		mixin Emits!(ChangeEvent!bool);
11169 
11170 		this(Widget parent) {
11171 			super(parent);
11172 			setDefaultEventHandler((ClickEvent) { checked = !checked; });
11173 		}
11174 
11175 		private bool _checked;
11176 		@property bool checked() { return _checked; }
11177 		@property void checked(bool set) {
11178 			_checked = set;
11179 			emit!(ChangeEvent!bool)(&checked);
11180 		}
11181 	}
11182 	---
11183 
11184 	## Creating Your Own Events
11185 
11186 	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.
11187 
11188 	---
11189 	class MyEvent : Event {
11190 		this(Widget target) { super(EventString, target); }
11191 		mixin Register; // adds EventString and other reflection information
11192 	}
11193 	---
11194 
11195 	Then declare that it is sent with the [Emits] mixin, so you can use [Widget.emit] to dispatch it.
11196 
11197 	History:
11198 		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.
11199 
11200 		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.
11201 +/
11202 /+
11203 
11204 	## General Conventions
11205 
11206 	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.
11207 
11208 
11209 	## Qt-style signals and slots
11210 
11211 	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.
11212 
11213 	The intention is for events to be used when
11214 
11215 	---
11216 	class Demo : Widget {
11217 		this() {
11218 			myPropertyChanged = Signal!int(this);
11219 		}
11220 		@property myProperty(int v) {
11221 			myPropertyChanged.emit(v);
11222 		}
11223 
11224 		Signal!int myPropertyChanged; // i need to get `this` off it and inspect the name...
11225 		// but it can just genuinely not care about `this` since that's not really passed.
11226 	}
11227 
11228 	class Foo : Widget {
11229 		// the slot uda is not necessary, but it helps the script and ui builder find it.
11230 		@slot void setValue(int v) { ... }
11231 	}
11232 
11233 	demo.myPropertyChanged.connect(&foo.setValue);
11234 	---
11235 
11236 	The Signal type has a disabled default constructor, meaning your widget constructor must pass `this` to it in its constructor.
11237 
11238 	Some events may also wish to implement the Signal interface. These use particular arguments to call a method automatically.
11239 
11240 	class StringChangeEvent : ChangeEvent, Signal!string {
11241 		mixin SignalImpl
11242 	}
11243 
11244 +/
11245 class Event : ReflectableProperties {
11246 	/// Creates an event without populating any members and without sending it. See [dispatch]
11247 	this(string eventName, Widget emittedBy) {
11248 		this.eventName = eventName;
11249 		this.srcElement = emittedBy;
11250 	}
11251 
11252 
11253 	/// Implementations for the [ReflectableProperties] interface/
11254 	void getPropertiesList(scope void delegate(string name) sink) const {}
11255 	/// ditto
11256 	void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink) { }
11257 	/// ditto
11258 	SetPropertyResult setPropertyFromString(string name, scope const(char)[] str, bool strIsJson) {
11259 		return SetPropertyResult.notPermitted;
11260 	}
11261 
11262 
11263 	/+
11264 	/++
11265 		This is an internal implementation detail of [Register] and is subject to be changed or removed at any time without notice.
11266 
11267 		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.
11268 	+/
11269 	protected final void sinkJsonString(string memberName, scope const(char)[] value, scope void delegate(string name, scope const(char)[] value) finalSink) {
11270 		if(value.length == 0) {
11271 			finalSink(memberName, `""`);
11272 			return;
11273 		}
11274 
11275 		char[1024] bufferBacking;
11276 		char[] buffer = bufferBacking;
11277 		int bufferPosition;
11278 
11279 		void sink(char ch) {
11280 			if(bufferPosition >= buffer.length)
11281 				buffer.length = buffer.length + 1024;
11282 			buffer[bufferPosition++] = ch;
11283 		}
11284 
11285 		sink('"');
11286 
11287 		foreach(ch; value) {
11288 			switch(ch) {
11289 				case '\\':
11290 					sink('\\'); sink('\\');
11291 				break;
11292 				case '"':
11293 					sink('\\'); sink('"');
11294 				break;
11295 				case '\n':
11296 					sink('\\'); sink('n');
11297 				break;
11298 				case '\r':
11299 					sink('\\'); sink('r');
11300 				break;
11301 				case '\t':
11302 					sink('\\'); sink('t');
11303 				break;
11304 				default:
11305 					sink(ch);
11306 			}
11307 		}
11308 
11309 		sink('"');
11310 
11311 		finalSink(memberName, buffer[0 .. bufferPosition]);
11312 	}
11313 	+/
11314 
11315 	/+
11316 	enum EventInitiator {
11317 		system,
11318 		minigui,
11319 		user
11320 	}
11321 
11322 	immutable EventInitiator; initiatedBy;
11323 	+/
11324 
11325 	/++
11326 		Events should generally follow the propagation model, but there's some exceptions
11327 		to that rule. If so, they should override this to return false. In that case, only
11328 		bubbling event handlers on the target itself and capturing event handlers on the containing
11329 		window will be called. (That is, [dispatch] will call [sendDirectly] instead of doing the normal
11330 		capture -> target -> bubble process.)
11331 
11332 		History:
11333 			Added May 12, 2021
11334 	+/
11335 	bool propagates() const pure nothrow @nogc @safe {
11336 		return true;
11337 	}
11338 
11339 	/++
11340 		hints as to whether preventDefault will actually do anything. not entirely reliable.
11341 
11342 		History:
11343 			Added May 14, 2021
11344 	+/
11345 	bool cancelable() const pure nothrow @nogc @safe {
11346 		return true;
11347 	}
11348 
11349 	/++
11350 		You can mix this into child class to register some boilerplate. It includes the `EventString`
11351 		member, a constructor, and implementations of the dynamic get data interfaces.
11352 
11353 		If you fail to do this, your event will probably not have full compatibility but it might still work for you.
11354 
11355 
11356 		You can override the default EventString by simply providing your own in the form of
11357 		`enum string EventString = "some.name";` The default is the name of your class and its parent entity
11358 		which provides some namespace protection against conflicts in other libraries while still being fairly
11359 		easy to use.
11360 
11361 		If you provide your own constructor, it will override the default constructor provided here. A constructor
11362 		must call `super(EventString, passed_widget_target)` at some point. The `passed_widget_target` must be the
11363 		first argument to your constructor.
11364 
11365 		History:
11366 			Added May 13, 2021.
11367 	+/
11368 	protected static mixin template Register() {
11369 		public enum string EventString = __traits(identifier, __traits(parent, typeof(this))) ~ "." ~  __traits(identifier, typeof(this));
11370 		this(Widget target) { super(EventString, target); }
11371 
11372 		mixin ReflectableProperties.RegisterGetters;
11373 	}
11374 
11375 	/++
11376 		This is the widget that emitted the event.
11377 
11378 
11379 		The aliased names come from Javascript for ease of web developers to transition in, but they're all synonyms.
11380 
11381 		History:
11382 			The `source` name was added on May 14, 2021. It is a little weird that `source` and `target` are synonyms,
11383 			but that's a side effect of it doing both capture and bubble handlers and people are used to it from the web
11384 			so I don't intend to remove these aliases.
11385 	+/
11386 	Widget source;
11387 	/// ditto
11388 	alias source target;
11389 	/// ditto
11390 	alias source srcElement;
11391 
11392 	Widget relatedTarget; /// Note: likely to be deprecated at some point.
11393 
11394 	/// Prevents the default event handler (if there is one) from being called
11395 	void preventDefault() {
11396 		lastDefaultPrevented = true;
11397 		defaultPrevented = true;
11398 	}
11399 
11400 	/// Stops the event propagation immediately.
11401 	void stopPropagation() {
11402 		propagationStopped = true;
11403 	}
11404 
11405 	private bool defaultPrevented;
11406 	private bool propagationStopped;
11407 	private string eventName;
11408 
11409 	private bool isBubbling;
11410 
11411 	/// 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.
11412 	protected void adjustScrolling() { }
11413 	/// ditto
11414 	protected void adjustClientCoordinates(int deltaX, int deltaY) { }
11415 
11416 	/++
11417 		this sends it only to the target. If you want propagation, use dispatch() instead.
11418 
11419 		This should be made private!!!
11420 
11421 	+/
11422 	void sendDirectly() {
11423 		if(srcElement is null)
11424 			return;
11425 
11426 		// 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.
11427 
11428 		//debug if(eventName != "mousemove" && target !is null && target.parentWindow && target.parentWindow.devTools)
11429 			//target.parentWindow.devTools.log("Event ", eventName, " dispatched directly to ", srcElement);
11430 
11431 		adjustScrolling();
11432 
11433 		if(auto e = target.parentWindow) {
11434 			if(auto handlers = "*" in e.capturingEventHandlers)
11435 			foreach(handler; *handlers)
11436 				if(handler) handler(e, this);
11437 			if(auto handlers = eventName in e.capturingEventHandlers)
11438 			foreach(handler; *handlers)
11439 				if(handler) handler(e, this);
11440 		}
11441 
11442 		auto e = srcElement;
11443 
11444 		if(auto handlers = eventName in e.bubblingEventHandlers)
11445 		foreach(handler; *handlers)
11446 			if(handler) handler(e, this);
11447 
11448 		if(auto handlers = "*" in e.bubblingEventHandlers)
11449 		foreach(handler; *handlers)
11450 			if(handler) handler(e, this);
11451 
11452 		// there's never a default for a catch-all event
11453 		if(!defaultPrevented)
11454 			if(eventName in e.defaultEventHandlers)
11455 				e.defaultEventHandlers[eventName](e, this);
11456 	}
11457 
11458 	/// this dispatches the element using the capture -> target -> bubble process
11459 	void dispatch() {
11460 		if(srcElement is null)
11461 			return;
11462 
11463 		if(!propagates) {
11464 			sendDirectly;
11465 			return;
11466 		}
11467 
11468 		//debug if(eventName != "mousemove" && target !is null && target.parentWindow && target.parentWindow.devTools)
11469 			//target.parentWindow.devTools.log("Event ", eventName, " dispatched to ", srcElement);
11470 
11471 		adjustScrolling();
11472 		// first capture, then bubble
11473 
11474 		Widget[] chain;
11475 		Widget curr = srcElement;
11476 		while(curr) {
11477 			auto l = curr;
11478 			chain ~= l;
11479 			curr = curr.parent;
11480 		}
11481 
11482 		isBubbling = false;
11483 
11484 		foreach_reverse(e; chain) {
11485 			if(auto handlers = "*" in e.capturingEventHandlers)
11486 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
11487 
11488 			if(propagationStopped)
11489 				break;
11490 
11491 			if(auto handlers = eventName in e.capturingEventHandlers)
11492 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
11493 
11494 			// the default on capture should really be to always do nothing
11495 
11496 			//if(!defaultPrevented)
11497 			//	if(eventName in e.defaultEventHandlers)
11498 			//		e.defaultEventHandlers[eventName](e.element, this);
11499 
11500 			if(propagationStopped)
11501 				break;
11502 		}
11503 
11504 		int adjustX;
11505 		int adjustY;
11506 
11507 		isBubbling = true;
11508 		if(!propagationStopped)
11509 		foreach(e; chain) {
11510 			if(auto handlers = eventName in e.bubblingEventHandlers)
11511 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
11512 
11513 			if(propagationStopped)
11514 				break;
11515 
11516 			if(auto handlers = "*" in e.bubblingEventHandlers)
11517 				foreach(handler; *handlers) if(handler !is null) handler(e, this);
11518 
11519 			if(propagationStopped)
11520 				break;
11521 
11522 			if(e.encapsulatedChildren()) {
11523 				adjustClientCoordinates(adjustX, adjustY);
11524 				target = e;
11525 			} else {
11526 				adjustX += e.x;
11527 				adjustY += e.y;
11528 			}
11529 		}
11530 
11531 		if(!defaultPrevented)
11532 		foreach(e; chain) {
11533 			if(eventName in e.defaultEventHandlers)
11534 				e.defaultEventHandlers[eventName](e, this);
11535 		}
11536 	}
11537 
11538 
11539 	/* old compatibility things */
11540 	deprecated("Use some subclass of KeyEventBase instead of plain Event in your handler going forward. WARNING these may crash on non-key events!")
11541 	final @property {
11542 		Key key() { return (cast(KeyEventBase) this).key; }
11543 		KeyEvent originalKeyEvent() { return (cast(KeyEventBase) this).originalKeyEvent; }
11544 
11545 		bool ctrlKey() { return (cast(KeyEventBase) this).ctrlKey; }
11546 		bool altKey() { return (cast(KeyEventBase) this).altKey; }
11547 		bool shiftKey() { return (cast(KeyEventBase) this).shiftKey; }
11548 	}
11549 
11550 	deprecated("Use some subclass of MouseEventBase instead of Event in your handler going forward. WARNING these may crash on non-mouse events!")
11551 	final @property {
11552 		int clientX() { return (cast(MouseEventBase) this).clientX; }
11553 		int clientY() { return (cast(MouseEventBase) this).clientY; }
11554 
11555 		int viewportX() { return (cast(MouseEventBase) this).viewportX; }
11556 		int viewportY() { return (cast(MouseEventBase) this).viewportY; }
11557 
11558 		int button() { return (cast(MouseEventBase) this).button; }
11559 		int buttonLinear() { return (cast(MouseEventBase) this).buttonLinear; }
11560 	}
11561 
11562 	deprecated("Use either a KeyEventBase or a MouseEventBase instead of Event in your handler going forward")
11563 	final @property {
11564 		int state() {
11565 			if(auto meb = cast(MouseEventBase) this)
11566 				return meb.state;
11567 			if(auto keb = cast(KeyEventBase) this)
11568 				return keb.state;
11569 			assert(0);
11570 		}
11571 	}
11572 
11573 	deprecated("Use a CharEvent instead of Event in your handler going forward")
11574 	final @property {
11575 		dchar character() {
11576 			if(auto ce = cast(CharEvent) this)
11577 				return ce.character;
11578 			return dchar.init;
11579 		}
11580 	}
11581 
11582 	// for change events
11583 	@property {
11584 		///
11585 		int intValue() { return 0; }
11586 		///
11587 		string stringValue() { return null; }
11588 	}
11589 }
11590 
11591 /++
11592 	This lets you statically verify you send the events you claim you send and gives you a hook to document them.
11593 
11594 	Please note that a widget may send events not listed as Emits. You can always construct and dispatch
11595 	dynamic and custom events, but the static list helps ensure you get them right.
11596 
11597 	If this is declared, you can use [Widget.emit] to send the event.
11598 
11599 	All events work the same way though, following the capture->widget->bubble model described under [Event].
11600 
11601 	History:
11602 		Added May 4, 2021
11603 +/
11604 mixin template Emits(EventType) {
11605 	import arsd.minigui : EventString;
11606 	static if(is(EventType : Event) && !is(EventType == Event))
11607 		mixin("private EventType[0] emits_" ~ EventStringIdentifier!EventType ~";");
11608 	else
11609 		static assert(0, "You can only emit subclasses of Event");
11610 }
11611 
11612 /// ditto
11613 mixin template Emits(string eventString) {
11614 	mixin("private Event[0] emits_" ~ eventString ~";");
11615 }
11616 
11617 /*
11618 class SignalEvent(string name) : Event {
11619 
11620 }
11621 */
11622 
11623 /++
11624 	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".
11625 
11626 
11627 	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.
11628 
11629 	History:
11630 		Added on May 13, 2021. Prior to that, you'd most likely `addEventListener(EventType.triggered, ...)` to handle similar things.
11631 +/
11632 class CommandEvent : Event {
11633 	enum EventString = "command";
11634 	this(Widget source, string CommandString = EventString) {
11635 		super(CommandString, source);
11636 	}
11637 }
11638 
11639 /++
11640 	A [CommandEvent] is typically actually an instance of these to hold the strongly-typed arguments.
11641 +/
11642 class CommandEventWithArgs(Args...) : CommandEvent {
11643 	this(Widget source, string CommandString, Args args) { super(source, CommandString); this.args = args; }
11644 	Args args;
11645 }
11646 
11647 /++
11648 	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.
11649 
11650 	See [CommandEvent] for more information.
11651 
11652 	Returns:
11653 		The [EventListener] you can use to remove the handler.
11654 +/
11655 EventListener consumesCommand(string CommandString, WidgetType, Args...)(WidgetType w, void delegate(Args) handler) {
11656 	return w.addEventListener(CommandString, (Event ev) {
11657 		if(ev.target is w)
11658 			return; // it does not consume its own commands!
11659 		if(auto cev = cast(CommandEventWithArgs!Args) ev) {
11660 			handler(cev.args);
11661 			ev.stopPropagation();
11662 		}
11663 	});
11664 }
11665 
11666 /++
11667 	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.
11668 +/
11669 void emitCommand(string CommandString, WidgetType, Args...)(WidgetType w, Args args) {
11670 	auto event = new CommandEventWithArgs!Args(w, CommandString, args);
11671 	event.dispatch();
11672 }
11673 
11674 class ResizeEvent : Event {
11675 	enum EventString = "resize";
11676 
11677 	this(Widget target) { super(EventString, target); }
11678 
11679 	override bool propagates() const { return false; }
11680 }
11681 
11682 /++
11683 	ClosingEvent is fired when a user is attempting to close a window. You can `preventDefault` to cancel the close.
11684 
11685 	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.
11686 
11687 	History:
11688 		Added June 21, 2021 (dub v10.1)
11689 +/
11690 class ClosingEvent : Event {
11691 	enum EventString = "closing";
11692 
11693 	this(Widget target) { super(EventString, target); }
11694 
11695 	override bool propagates() const { return false; }
11696 	override bool cancelable() const { return true; }
11697 }
11698 
11699 /// ditto
11700 class ClosedEvent : Event {
11701 	enum EventString = "closed";
11702 
11703 	this(Widget target) { super(EventString, target); }
11704 
11705 	override bool propagates() const { return false; }
11706 	override bool cancelable() const { return false; }
11707 }
11708 
11709 ///
11710 class BlurEvent : Event {
11711 	enum EventString = "blur";
11712 
11713 	// FIXME: related target?
11714 	this(Widget target) { super(EventString, target); }
11715 
11716 	override bool propagates() const { return false; }
11717 }
11718 
11719 ///
11720 class FocusEvent : Event {
11721 	enum EventString = "focus";
11722 
11723 	// FIXME: related target?
11724 	this(Widget target) { super(EventString, target); }
11725 
11726 	override bool propagates() const { return false; }
11727 }
11728 
11729 /++
11730 	FocusInEvent is a FocusEvent that propagates, while FocusOutEvent is a BlurEvent that propagates.
11731 
11732 	History:
11733 		Added July 3, 2021
11734 +/
11735 class FocusInEvent : Event {
11736 	enum EventString = "focusin";
11737 
11738 	// FIXME: related target?
11739 	this(Widget target) { super(EventString, target); }
11740 
11741 	override bool cancelable() const { return false; }
11742 }
11743 
11744 /// ditto
11745 class FocusOutEvent : Event {
11746 	enum EventString = "focusout";
11747 
11748 	// FIXME: related target?
11749 	this(Widget target) { super(EventString, target); }
11750 
11751 	override bool cancelable() const { return false; }
11752 }
11753 
11754 ///
11755 class ScrollEvent : Event {
11756 	enum EventString = "scroll";
11757 	this(Widget target) { super(EventString, target); }
11758 
11759 	override bool cancelable() const { return false; }
11760 }
11761 
11762 /++
11763 	Indicates that a character has been typed by the user. Normally dispatched to the currently focused widget.
11764 
11765 	History:
11766 		Added May 2, 2021. Previously, this was simply a "char" event and `character` as a member of the [Event] base class.
11767 +/
11768 class CharEvent : Event {
11769 	enum EventString = "char";
11770 	this(Widget target, dchar ch) {
11771 		character = ch;
11772 		super(EventString, target);
11773 	}
11774 
11775 	immutable dchar character;
11776 }
11777 
11778 /++
11779 	You should generally use a `ChangeEvent!Type` instead of this directly. See [ChangeEvent] for more information.
11780 +/
11781 abstract class ChangeEventBase : Event {
11782 	enum EventString = "change";
11783 	this(Widget target) {
11784 		super(EventString, target);
11785 	}
11786 
11787 	/+
11788 		// idk where or how exactly i want to do this.
11789 		// i might come back to it later.
11790 
11791 	// If a widget itself broadcasts one of theses itself, it stops propagation going down
11792 	// this way the source doesn't get too confused (think of a nested scroll widget)
11793 	//
11794 	// the idea is like the scroll bar emits a command event saying like "scroll left one line"
11795 	// then you consume that command and change you scroll x position to whatever. then you do
11796 	// some kind of change event that is broadcast back to the children and any horizontal scroll
11797 	// listeners are now able to update, without having an explicit connection between them.
11798 	void broadcastToChildren(string fieldName) {
11799 
11800 	}
11801 	+/
11802 }
11803 
11804 /++
11805 	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.
11806 
11807 
11808 	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).
11809 
11810 	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);`
11811 
11812 	Since it is emitted after the value has already changed, [preventDefault] is unlikely to do anything.
11813 
11814 	History:
11815 		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.
11816 +/
11817 class ChangeEvent(T) : ChangeEventBase {
11818 	this(Widget target, T delegate() getNewValue) {
11819 		assert(getNewValue !is null);
11820 		this.getNewValue = getNewValue;
11821 		super(target);
11822 	}
11823 
11824 	private T delegate() getNewValue;
11825 
11826 	/++
11827 		Gets the new value that just changed.
11828 	+/
11829 	@property T value() {
11830 		return getNewValue();
11831 	}
11832 
11833 	/// compatibility method for old generic Events
11834 	static if(is(immutable T == immutable int))
11835 		override int intValue() { return value; }
11836 	/// ditto
11837 	static if(is(immutable T == immutable string))
11838 		override string stringValue() { return value; }
11839 }
11840 
11841 /++
11842 	Contains shared properties for [KeyDownEvent]s and [KeyUpEvent]s.
11843 
11844 
11845 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
11846 
11847 	History:
11848 		Added May 2, 2021. Previously, its properties were members of the [Event] base class.
11849 +/
11850 abstract class KeyEventBase : Event {
11851 	this(string name, Widget target) {
11852 		super(name, target);
11853 	}
11854 
11855 	// for key events
11856 	Key key; ///
11857 
11858 	KeyEvent originalKeyEvent;
11859 
11860 	/++
11861 		Indicates the current state of the given keyboard modifier keys.
11862 
11863 		History:
11864 			Added to events on April 15, 2020.
11865 	+/
11866 	bool ctrlKey;
11867 
11868 	/// ditto
11869 	bool altKey;
11870 
11871 	/// ditto
11872 	bool shiftKey;
11873 
11874 	/++
11875 		The raw bitflags that are parsed out into [ctrlKey], [altKey], and [shiftKey].
11876 
11877 		See [arsd.simpledisplay.ModifierState] for other possible flags.
11878 	+/
11879 	int state;
11880 
11881 	mixin Register;
11882 }
11883 
11884 /++
11885 	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].
11886 
11887 
11888 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
11889 
11890 	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.
11891 
11892 	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.
11893 
11894 	See_Also: [KeyUpEvent], [CharEvent]
11895 
11896 	History:
11897 		Added May 2, 2021. Previously, it was only seen as the base [Event] class on "keydown" event listeners.
11898 +/
11899 class KeyDownEvent : KeyEventBase {
11900 	enum EventString = "keydown";
11901 	this(Widget target) { super(EventString, target); }
11902 }
11903 
11904 /++
11905 	Indicates that the user has released a key on the keyboard. For available properties, see [KeyEventBase].
11906 
11907 
11908 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
11909 
11910 	See_Also: [KeyDownEvent], [CharEvent]
11911 
11912 	History:
11913 		Added May 2, 2021. Previously, it was only seen as the base [Event] class on "keyup" event listeners.
11914 +/
11915 class KeyUpEvent : KeyEventBase {
11916 	enum EventString = "keyup";
11917 	this(Widget target) { super(EventString, target); }
11918 }
11919 
11920 /++
11921 	Contains shared properties for various mouse events;
11922 
11923 
11924 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
11925 
11926 	History:
11927 		Added May 2, 2021. Previously, its properties were members of the [Event] base class.
11928 +/
11929 abstract class MouseEventBase : Event {
11930 	this(string name, Widget target) {
11931 		super(name, target);
11932 	}
11933 
11934 	// for mouse events
11935 	int clientX; /// The mouse event location relative to the target widget
11936 	int clientY; /// ditto
11937 
11938 	int viewportX; /// The mouse event location relative to the window origin
11939 	int viewportY; /// ditto
11940 
11941 	int button; /// See: [MouseEvent.button]
11942 	int buttonLinear; /// See: [MouseEvent.buttonLinear]
11943 
11944 	/++
11945 		Indicates the current state of the given keyboard modifier keys.
11946 
11947 		History:
11948 			Added to mouse events on September 28, 2010.
11949 	+/
11950 	bool ctrlKey;
11951 
11952 	/// ditto
11953 	bool altKey;
11954 
11955 	/// ditto
11956 	bool shiftKey;
11957 
11958 
11959 
11960 	int state; ///
11961 
11962 	/++
11963 		for consistent names with key event. 
11964 
11965 		History:
11966 			Added September 28, 2021 (dub v10.3)
11967 	+/
11968 	alias modifierState = state;
11969 
11970 	/++
11971 		Mouse wheel movement sends down/up/click events just like other buttons clicking. This method is to help you filter that out.
11972 
11973 		History:
11974 			Added May 15, 2021
11975 	+/
11976 	bool isMouseWheel() {
11977 		return button == MouseButton.wheelUp || button == MouseButton.wheelDown;
11978 	}
11979 
11980 	// private
11981 	override void adjustClientCoordinates(int deltaX, int deltaY) {
11982 		clientX += deltaX;
11983 		clientY += deltaY;
11984 	}
11985 
11986 	override void adjustScrolling() {
11987 	version(custom_widgets) { // TEMP
11988 		viewportX = clientX;
11989 		viewportY = clientY;
11990 		if(auto se = cast(ScrollableWidget) srcElement) {
11991 			clientX += se.scrollOrigin.x;
11992 			clientY += se.scrollOrigin.y;
11993 		} else if(auto se = cast(ScrollableContainerWidget) srcElement) {
11994 			//clientX += se.scrollX_;
11995 			//clientY += se.scrollY_;
11996 		}
11997 	}
11998 	}
11999 
12000 	mixin Register;
12001 }
12002 
12003 /++
12004 	Indicates that the user has worked with the mouse over your widget. For available properties, see [MouseEventBase].
12005 
12006 
12007 	$(WARNING
12008 		Important: MouseDownEvent, MouseUpEvent, ClickEvent, and DoubleClickEvent are all sent for all mouse buttons and
12009 		for wheel movement! You should check the [MouseEventBase.button|button] property in most your handlers to get correct
12010 		behavior.
12011 	)
12012 
12013 	[MouseDownEvent] is sent when the user presses a mouse button. It is also sent on mouse wheel movement.
12014 
12015 	[MouseUpEvent] is sent when the user releases a mouse button.
12016 
12017 	[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.)
12018 
12019 	[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.
12020 
12021 	[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.
12022 
12023 	[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.
12024 
12025 	[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.
12026 
12027 	[MouseEnterEvent] is sent when the mouse enters the bounding box of a widget.
12028 
12029 	[MouseLeaveEvent] is sent when the mouse leaves the bounding box of a widget.
12030 
12031 	You can construct these yourself, but generally the system will send them to you and there's little need to emit your own.
12032 
12033 	Rationale:
12034 
12035 		If you only want to do drag, mousedown/up works just fine being consistently sent.
12036 
12037 		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).
12038 
12039 		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.
12040 
12041 	History:
12042 		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.
12043 +/
12044 class MouseUpEvent : MouseEventBase {
12045 	enum EventString = "mouseup"; ///
12046 	this(Widget target) { super(EventString, target); }
12047 }
12048 /// ditto
12049 class MouseDownEvent : MouseEventBase {
12050 	enum EventString = "mousedown"; ///
12051 	this(Widget target) { super(EventString, target); }
12052 }
12053 /// ditto
12054 class MouseMoveEvent : MouseEventBase {
12055 	enum EventString = "mousemove"; ///
12056 	this(Widget target) { super(EventString, target); }
12057 }
12058 /// ditto
12059 class ClickEvent : MouseEventBase {
12060 	enum EventString = "click"; ///
12061 	this(Widget target) { super(EventString, target); }
12062 }
12063 /// ditto
12064 class DoubleClickEvent : MouseEventBase {
12065 	enum EventString = "dblclick"; ///
12066 	this(Widget target) { super(EventString, target); }
12067 }
12068 /// ditto
12069 class MouseOverEvent : Event {
12070 	enum EventString = "mouseover"; ///
12071 	this(Widget target) { super(EventString, target); }
12072 }
12073 /// ditto
12074 class MouseOutEvent : Event {
12075 	enum EventString = "mouseout"; ///
12076 	this(Widget target) { super(EventString, target); }
12077 }
12078 /// ditto
12079 class MouseEnterEvent : Event {
12080 	enum EventString = "mouseenter"; ///
12081 	this(Widget target) { super(EventString, target); }
12082 
12083 	override bool propagates() const { return false; }
12084 }
12085 /// ditto
12086 class MouseLeaveEvent : Event {
12087 	enum EventString = "mouseleave"; ///
12088 	this(Widget target) { super(EventString, target); }
12089 
12090 	override bool propagates() const { return false; }
12091 }
12092 
12093 private bool isAParentOf(Widget a, Widget b) {
12094 	if(a is null || b is null)
12095 		return false;
12096 
12097 	while(b !is null) {
12098 		if(a is b)
12099 			return true;
12100 		b = b.parent;
12101 	}
12102 
12103 	return false;
12104 }
12105 
12106 private struct WidgetAtPointResponse {
12107 	Widget widget;
12108 
12109 	// x, y relative to the widget in the response.
12110 	int x;
12111 	int y;
12112 }
12113 
12114 private WidgetAtPointResponse widgetAtPoint(Widget starting, int x, int y) {
12115 	assert(starting !is null);
12116 
12117 	starting.addScrollPosition(x, y);
12118 
12119 	auto child = starting.getChildAtPosition(x, y);
12120 	while(child) {
12121 		if(child.hidden)
12122 			continue;
12123 		starting = child;
12124 		x -= child.x;
12125 		y -= child.y;
12126 		auto r = starting.widgetAtPoint(x, y);//starting.getChildAtPosition(x, y);
12127 		child = r.widget;
12128 		if(child is starting)
12129 			break;
12130 	}
12131 	return WidgetAtPointResponse(starting, x, y);
12132 }
12133 
12134 version(win32_widgets) {
12135 private:
12136 	import core.sys.windows.commctrl;
12137 
12138 	pragma(lib, "comctl32");
12139 	shared static this() {
12140 		// http://msdn.microsoft.com/en-us/library/windows/desktop/bb775507(v=vs.85).aspx
12141 		INITCOMMONCONTROLSEX ic;
12142 		ic.dwSize = cast(DWORD) ic.sizeof;
12143 		ic.dwICC = ICC_UPDOWN_CLASS | ICC_WIN95_CLASSES | ICC_BAR_CLASSES | ICC_PROGRESS_CLASS | ICC_COOL_CLASSES | ICC_STANDARD_CLASSES | ICC_USEREX_CLASSES;
12144 		if(!InitCommonControlsEx(&ic)) {
12145 			//import std.stdio; writeln("ICC failed");
12146 		}
12147 	}
12148 
12149 
12150 	// everything from here is just win32 headers copy pasta
12151 private:
12152 extern(Windows):
12153 
12154 	alias HANDLE HMENU;
12155 	HMENU CreateMenu();
12156 	bool SetMenu(HWND, HMENU);
12157 	HMENU CreatePopupMenu();
12158 	enum MF_POPUP = 0x10;
12159 	enum MF_STRING = 0;
12160 
12161 
12162 	BOOL InitCommonControlsEx(const INITCOMMONCONTROLSEX*);
12163 	struct INITCOMMONCONTROLSEX {
12164 		DWORD dwSize;
12165 		DWORD dwICC;
12166 	}
12167 	enum HINST_COMMCTRL = cast(HINSTANCE) (-1);
12168 enum {
12169         IDB_STD_SMALL_COLOR,
12170         IDB_STD_LARGE_COLOR,
12171         IDB_VIEW_SMALL_COLOR = 4,
12172         IDB_VIEW_LARGE_COLOR = 5
12173 }
12174 enum {
12175         STD_CUT,
12176         STD_COPY,
12177         STD_PASTE,
12178         STD_UNDO,
12179         STD_REDOW,
12180         STD_DELETE,
12181         STD_FILENEW,
12182         STD_FILEOPEN,
12183         STD_FILESAVE,
12184         STD_PRINTPRE,
12185         STD_PROPERTIES,
12186         STD_HELP,
12187         STD_FIND,
12188         STD_REPLACE,
12189         STD_PRINT // = 14
12190 }
12191 
12192 alias HANDLE HIMAGELIST;
12193 	HIMAGELIST ImageList_Create(int, int, UINT, int, int);
12194 	int ImageList_Add(HIMAGELIST, HBITMAP, HBITMAP);
12195         BOOL ImageList_Destroy(HIMAGELIST);
12196 
12197 uint MAKELONG(ushort a, ushort b) {
12198         return cast(uint) ((b << 16) | a);
12199 }
12200 
12201 
12202 struct TBBUTTON {
12203 	int   iBitmap;
12204 	int   idCommand;
12205 	BYTE  fsState;
12206 	BYTE  fsStyle;
12207 	version(Win64)
12208 	BYTE[6] bReserved;
12209 	else
12210 	BYTE[2]  bReserved;
12211 	DWORD dwData;
12212 	INT_PTR   iString;
12213 }
12214 
12215 	enum {
12216 		TB_ADDBUTTONSA   = WM_USER + 20,
12217 		TB_INSERTBUTTONA = WM_USER + 21,
12218 		TB_GETIDEALSIZE = WM_USER + 99,
12219 	}
12220 
12221 struct SIZE {
12222 	LONG cx;
12223 	LONG cy;
12224 }
12225 
12226 
12227 enum {
12228 	TBSTATE_CHECKED       = 1,
12229 	TBSTATE_PRESSED       = 2,
12230 	TBSTATE_ENABLED       = 4,
12231 	TBSTATE_HIDDEN        = 8,
12232 	TBSTATE_INDETERMINATE = 16,
12233 	TBSTATE_WRAP          = 32
12234 }
12235 
12236 
12237 
12238 enum {
12239 	ILC_COLOR    = 0,
12240 	ILC_COLOR4   = 4,
12241 	ILC_COLOR8   = 8,
12242 	ILC_COLOR16  = 16,
12243 	ILC_COLOR24  = 24,
12244 	ILC_COLOR32  = 32,
12245 	ILC_COLORDDB = 254,
12246 	ILC_MASK     = 1,
12247 	ILC_PALETTE  = 2048
12248 }
12249 
12250 
12251 alias TBBUTTON*       PTBBUTTON, LPTBBUTTON;
12252 
12253 
12254 enum {
12255 	TB_ENABLEBUTTON          = WM_USER + 1,
12256 	TB_CHECKBUTTON,
12257 	TB_PRESSBUTTON,
12258 	TB_HIDEBUTTON,
12259 	TB_INDETERMINATE, //     = WM_USER + 5,
12260 	TB_ISBUTTONENABLED       = WM_USER + 9,
12261 	TB_ISBUTTONCHECKED,
12262 	TB_ISBUTTONPRESSED,
12263 	TB_ISBUTTONHIDDEN,
12264 	TB_ISBUTTONINDETERMINATE, // = WM_USER + 13,
12265 	TB_SETSTATE              = WM_USER + 17,
12266 	TB_GETSTATE              = WM_USER + 18,
12267 	TB_ADDBITMAP             = WM_USER + 19,
12268 	TB_DELETEBUTTON          = WM_USER + 22,
12269 	TB_GETBUTTON,
12270 	TB_BUTTONCOUNT,
12271 	TB_COMMANDTOINDEX,
12272 	TB_SAVERESTOREA,
12273 	TB_CUSTOMIZE,
12274 	TB_ADDSTRINGA,
12275 	TB_GETITEMRECT,
12276 	TB_BUTTONSTRUCTSIZE,
12277 	TB_SETBUTTONSIZE,
12278 	TB_SETBITMAPSIZE,
12279 	TB_AUTOSIZE, //          = WM_USER + 33,
12280 	TB_GETTOOLTIPS           = WM_USER + 35,
12281 	TB_SETTOOLTIPS           = WM_USER + 36,
12282 	TB_SETPARENT             = WM_USER + 37,
12283 	TB_SETROWS               = WM_USER + 39,
12284 	TB_GETROWS,
12285 	TB_GETBITMAPFLAGS,
12286 	TB_SETCMDID,
12287 	TB_CHANGEBITMAP,
12288 	TB_GETBITMAP,
12289 	TB_GETBUTTONTEXTA,
12290 	TB_REPLACEBITMAP, //     = WM_USER + 46,
12291 	TB_GETBUTTONSIZE         = WM_USER + 58,
12292 	TB_SETBUTTONWIDTH        = WM_USER + 59,
12293 	TB_GETBUTTONTEXTW        = WM_USER + 75,
12294 	TB_SAVERESTOREW          = WM_USER + 76,
12295 	TB_ADDSTRINGW            = WM_USER + 77,
12296 }
12297 
12298 extern(Windows)
12299 BOOL EnumChildWindows(HWND, WNDENUMPROC, LPARAM);
12300 
12301 alias extern(Windows) BOOL function (HWND, LPARAM) WNDENUMPROC;
12302 
12303 
12304 	enum {
12305 		TB_SETINDENT = WM_USER + 47,
12306 		TB_SETIMAGELIST,
12307 		TB_GETIMAGELIST,
12308 		TB_LOADIMAGES,
12309 		TB_GETRECT,
12310 		TB_SETHOTIMAGELIST,
12311 		TB_GETHOTIMAGELIST,
12312 		TB_SETDISABLEDIMAGELIST,
12313 		TB_GETDISABLEDIMAGELIST,
12314 		TB_SETSTYLE,
12315 		TB_GETSTYLE,
12316 		//TB_GETBUTTONSIZE,
12317 		//TB_SETBUTTONWIDTH,
12318 		TB_SETMAXTEXTROWS,
12319 		TB_GETTEXTROWS // = WM_USER + 61
12320 	}
12321 
12322 enum {
12323 	CCM_FIRST            = 0x2000,
12324 	CCM_LAST             = CCM_FIRST + 0x200,
12325 	CCM_SETBKCOLOR       = 8193,
12326 	CCM_SETCOLORSCHEME   = 8194,
12327 	CCM_GETCOLORSCHEME   = 8195,
12328 	CCM_GETDROPTARGET    = 8196,
12329 	CCM_SETUNICODEFORMAT = 8197,
12330 	CCM_GETUNICODEFORMAT = 8198,
12331 	CCM_SETVERSION       = 0x2007,
12332 	CCM_GETVERSION       = 0x2008,
12333 	CCM_SETNOTIFYWINDOW  = 0x2009
12334 }
12335 
12336 
12337 enum {
12338 	PBM_SETRANGE     = WM_USER + 1,
12339 	PBM_SETPOS,
12340 	PBM_DELTAPOS,
12341 	PBM_SETSTEP,
12342 	PBM_STEPIT,   // = WM_USER + 5
12343 	PBM_SETRANGE32   = 1030,
12344 	PBM_GETRANGE,
12345 	PBM_GETPOS,
12346 	PBM_SETBARCOLOR, // = 1033
12347 	PBM_SETBKCOLOR   = CCM_SETBKCOLOR
12348 }
12349 
12350 enum {
12351 	PBS_SMOOTH   = 1,
12352 	PBS_VERTICAL = 4
12353 }
12354 
12355 enum {
12356         ICC_LISTVIEW_CLASSES = 1,
12357         ICC_TREEVIEW_CLASSES = 2,
12358         ICC_BAR_CLASSES      = 4,
12359         ICC_TAB_CLASSES      = 8,
12360         ICC_UPDOWN_CLASS     = 16,
12361         ICC_PROGRESS_CLASS   = 32,
12362         ICC_HOTKEY_CLASS     = 64,
12363         ICC_ANIMATE_CLASS    = 128,
12364         ICC_WIN95_CLASSES    = 255,
12365         ICC_DATE_CLASSES     = 256,
12366         ICC_USEREX_CLASSES   = 512,
12367         ICC_COOL_CLASSES     = 1024,
12368 	ICC_STANDARD_CLASSES = 0x00004000,
12369 }
12370 
12371 	enum WM_USER = 1024;
12372 }
12373 
12374 version(win32_widgets)
12375 	pragma(lib, "comdlg32");
12376 
12377 
12378 ///
12379 enum GenericIcons : ushort {
12380 	None, ///
12381 	// these happen to match the win32 std icons numerically if you just subtract one from the value
12382 	Cut, ///
12383 	Copy, ///
12384 	Paste, ///
12385 	Undo, ///
12386 	Redo, ///
12387 	Delete, ///
12388 	New, ///
12389 	Open, ///
12390 	Save, ///
12391 	PrintPreview, ///
12392 	Properties, ///
12393 	Help, ///
12394 	Find, ///
12395 	Replace, ///
12396 	Print, ///
12397 }
12398 
12399 ///
12400 void getOpenFileName(
12401 	void delegate(string) onOK,
12402 	string prefilledName = null,
12403 	string[] filters = null,
12404 	void delegate() onCancel = null,
12405 )
12406 {
12407 	return getFileName(true, onOK, prefilledName, filters, onCancel);
12408 }
12409 
12410 /++
12411 	History:
12412 		onCancel was added November 6, 2021.
12413 +/
12414 void getSaveFileName(
12415 	void delegate(string) onOK,
12416 	string prefilledName = null,
12417 	string[] filters = null,
12418 	void delegate() onCancel = null,
12419 )
12420 {
12421 	return getFileName(false, onOK, prefilledName, filters, onCancel);
12422 }
12423 
12424 void getFileName(
12425 	bool openOrSave,
12426 	void delegate(string) onOK,
12427 	string prefilledName = null,
12428 	string[] filters = null, // format here is like ["Text files\0*.txt;*.text", "Image files\n*.png;*.jpg"]
12429 	void delegate() onCancel = null,
12430 )
12431 {
12432 
12433 	version(win32_widgets) {
12434 		import core.sys.windows.commdlg;
12435 	/*
12436 	Ofn.lStructSize = sizeof(OPENFILENAME); 
12437 	Ofn.hwndOwner = hWnd; 
12438 	Ofn.lpstrFilter = szFilter; 
12439 	Ofn.lpstrFile= szFile; 
12440 	Ofn.nMaxFile = sizeof(szFile)/ sizeof(*szFile); 
12441 	Ofn.lpstrFileTitle = szFileTitle; 
12442 	Ofn.nMaxFileTitle = sizeof(szFileTitle); 
12443 	Ofn.lpstrInitialDir = (LPSTR)NULL; 
12444 	Ofn.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT; 
12445 	Ofn.lpstrTitle = szTitle; 
12446 	 */
12447 
12448 
12449 		wchar[1024] file = 0;
12450 		wchar[1024] filterBuffer = 0;
12451 		makeWindowsString(prefilledName, file[]);
12452 		OPENFILENAME ofn;
12453 		ofn.lStructSize = ofn.sizeof;
12454 		if(filters.length) {
12455 			string filter;
12456 			foreach(i, f; filters) {
12457 				filter ~= f;
12458 				filter ~= "\0";
12459 			}
12460 			filter ~= "\0";
12461 			ofn.lpstrFilter = makeWindowsString(filter, filterBuffer[], 0 /* already terminated */).ptr;
12462 		}
12463 		ofn.lpstrFile = file.ptr;
12464 		ofn.nMaxFile = file.length;
12465 		if(openOrSave ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn)) {
12466 			onOK(makeUtf8StringFromWindowsString(ofn.lpstrFile));
12467 		} else {
12468 			if(onCancel)
12469 				onCancel();
12470 		}
12471 	} else version(custom_widgets) {
12472 		auto picker = new FilePicker(prefilledName);
12473 		picker.onOK = onOK;
12474 		picker.onCancel = onCancel;
12475 		picker.show();
12476 	}
12477 }
12478 
12479 version(custom_widgets)
12480 private
12481 class FilePicker : Dialog {
12482 	void delegate(string) onOK;
12483 	void delegate() onCancel;
12484 	LineEdit lineEdit;
12485 	this(string prefilledName, Window owner = null) {
12486 		super(300, 200, "Choose File..."); // owner);
12487 
12488 		auto listWidget = new ListWidget(this);
12489 
12490 		lineEdit = new LineEdit(this);
12491 		lineEdit.focus();
12492 		lineEdit.addEventListener(delegate(CharEvent event) {
12493 			if(event.character == '\t' || event.character == '\n')
12494 				event.preventDefault();
12495 		});
12496 
12497 		listWidget.addEventListener(EventType.change, () {
12498 			foreach(o; listWidget.options)
12499 				if(o.selected)
12500 					lineEdit.content = o.label;
12501 		});
12502 
12503 		//version(none)
12504 		lineEdit.addEventListener((KeyDownEvent event) {
12505 			if(event.key == Key.Tab) {
12506 				listWidget.clear();
12507 
12508 				string commonPrefix;
12509 				auto cnt = lineEdit.content;
12510 				if(cnt.length >= 2 && cnt[0 ..2] == "./")
12511 					cnt = cnt[2 .. $];
12512 
12513 				version(Windows) {
12514 					WIN32_FIND_DATA data;
12515 					WCharzBuffer search = WCharzBuffer("./" ~ cnt ~ "*");
12516 					auto handle = FindFirstFileW(search.ptr, &data);
12517 					scope(exit) if(handle !is INVALID_HANDLE_VALUE) FindClose(handle);
12518 					if(handle is INVALID_HANDLE_VALUE) {
12519 						if(GetLastError() == ERROR_FILE_NOT_FOUND)
12520 							goto file_not_found;
12521 						throw new WindowsApiException("FindFirstFileW");
12522 					}
12523 				} else version(Posix) {
12524 					import core.sys.posix.dirent;
12525 					auto dir = opendir(".");
12526 					scope(exit)
12527 						if(dir) closedir(dir);
12528 					if(dir is null)
12529 						throw new ErrnoApiException("opendir");
12530 
12531 					auto dirent = readdir(dir);
12532 					if(dirent is null)
12533 						goto file_not_found;
12534 					// filter those that don't start with it, since posix doesn't
12535 					// do the * thing itself
12536 					while(dirent.d_name[0 .. cnt.length] != cnt[]) {
12537 						dirent = readdir(dir);
12538 						if(dirent is null)
12539 							goto file_not_found;
12540 					}
12541 				} else static assert(0);
12542 
12543 				while(true) {
12544 				//foreach(string name; dirEntries(".", cnt ~ "*", SpanMode.shallow)) {
12545 					version(Windows) {
12546 						string name = makeUtf8StringFromWindowsString(data.cFileName[0 .. findIndexOfZero(data.cFileName[])]);
12547 					} else version(Posix) {
12548 						string name = dirent.d_name[0 .. findIndexOfZero(dirent.d_name[])].idup;
12549 					} else static assert(0);
12550 
12551 
12552 					listWidget.addOption(name);
12553 					if(commonPrefix is null)
12554 						commonPrefix = name;
12555 					else {
12556 						foreach(idx, char i; name) {
12557 							if(idx >= commonPrefix.length || i != commonPrefix[idx]) {
12558 								commonPrefix = commonPrefix[0 .. idx];
12559 								break;
12560 							}
12561 						}
12562 					}
12563 
12564 					version(Windows) {
12565 						auto ret = FindNextFileW(handle, &data);
12566 						if(ret == 0) {
12567 							if(GetLastError() == ERROR_NO_MORE_FILES)
12568 								break;
12569 							throw new WindowsApiException("FindNextFileW");
12570 						}
12571 					} else version(Posix) {
12572 						dirent = readdir(dir);
12573 						if(dirent is null)
12574 							break;
12575 
12576 						while(dirent.d_name[0 .. cnt.length] != cnt[]) {
12577 							dirent = readdir(dir);
12578 							if(dirent is null)
12579 								break;
12580 						}
12581 
12582 						if(dirent is null)
12583 							break;
12584 					} else static assert(0);
12585 				}
12586 				if(commonPrefix.length)
12587 					lineEdit.content = commonPrefix;
12588 
12589 				file_not_found:
12590 				event.preventDefault();
12591 			}
12592 		});
12593 
12594 		lineEdit.content = prefilledName;
12595 
12596 		auto hl = new HorizontalLayout(this);
12597 		auto cancelButton = new Button("Cancel", hl);
12598 		auto okButton = new Button("OK", hl);
12599 
12600 		recomputeChildLayout(); // FIXME hack
12601 
12602 		cancelButton.addEventListener(EventType.triggered, &Cancel);
12603 		okButton.addEventListener(EventType.triggered, &OK);
12604 
12605 		this.addEventListener((KeyDownEvent event) {
12606 			if(event.key == Key.Enter || event.key == Key.PadEnter) {
12607 				event.preventDefault();
12608 				OK();
12609 			}
12610 			if(event.key == Key.Escape)
12611 				Cancel();
12612 		});
12613 
12614 	}
12615 
12616 	override void OK() {
12617 		if(onOK)
12618 			onOK(lineEdit.content);
12619 		close();
12620 	}
12621 
12622 	override void Cancel() {
12623 		if(onCancel)
12624 			onCancel();
12625 		close();
12626 	}
12627 }
12628 
12629 /*
12630 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775947%28v=vs.85%29.aspx#check_boxes
12631 http://msdn.microsoft.com/en-us/library/windows/desktop/ms633574%28v=vs.85%29.aspx
12632 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775943%28v=vs.85%29.aspx
12633 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775951%28v=vs.85%29.aspx
12634 http://msdn.microsoft.com/en-us/library/windows/desktop/ms632680%28v=vs.85%29.aspx
12635 http://msdn.microsoft.com/en-us/library/windows/desktop/ms644996%28v=vs.85%29.aspx#message_box
12636 http://www.sbin.org/doc/Xlib/chapt_03.html
12637 
12638 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760433%28v=vs.85%29.aspx
12639 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760446%28v=vs.85%29.aspx
12640 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760443%28v=vs.85%29.aspx
12641 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760476%28v=vs.85%29.aspx
12642 */
12643 
12644 
12645 // These are all for setMenuAndToolbarFromAnnotatedCode
12646 /// This item in the menu will be preceded by a separator line
12647 /// Group: generating_from_code
12648 struct separator {}
12649 deprecated("It was misspelled, use separator instead") alias seperator = separator;
12650 /// Program-wide keyboard shortcut to trigger the action
12651 /// Group: generating_from_code
12652 struct accelerator { string keyString; }
12653 /// tells which menu the action will be on
12654 /// Group: generating_from_code
12655 struct menu { string name; }
12656 /// Describes which toolbar section the action appears on
12657 /// Group: generating_from_code
12658 struct toolbar { string groupName; }
12659 ///
12660 /// Group: generating_from_code
12661 struct icon { ushort id; }
12662 ///
12663 /// Group: generating_from_code
12664 struct label { string label; }
12665 ///
12666 /// Group: generating_from_code
12667 struct hotkey { dchar ch; }
12668 ///
12669 /// Group: generating_from_code
12670 struct tip { string tip; }
12671 
12672 
12673 /++
12674 	Observes and allows inspection of an object via automatic gui
12675 +/
12676 /// Group: generating_from_code
12677 ObjectInspectionWindow objectInspectionWindow(T)(T t) if(is(T == class)) {
12678 	return new ObjectInspectionWindowImpl!(T)(t);
12679 }
12680 
12681 class ObjectInspectionWindow : Window {
12682 	this(int a, int b, string c) {
12683 		super(a, b, c);
12684 	}
12685 
12686 	abstract void readUpdatesFromObject();
12687 }
12688 
12689 class ObjectInspectionWindowImpl(T) : ObjectInspectionWindow {
12690 	T t;
12691 	this(T t) {
12692 		this.t = t;
12693 
12694 		super(300, 400, "ObjectInspectionWindow - " ~ T.stringof);
12695 
12696 		foreach(memberName; __traits(derivedMembers, T)) {{
12697 			alias member = I!(__traits(getMember, t, memberName))[0];
12698 			alias type = typeof(member);
12699 			static if(is(type == int)) {
12700 				auto le = new LabeledLineEdit(memberName ~ ": ", this);
12701 				//le.addEventListener("char", (Event ev) {
12702 					//if((ev.character < '0' || ev.character > '9') && ev.character != '-')
12703 						//ev.preventDefault();
12704 				//});
12705 				le.addEventListener(EventType.change, (Event ev) {
12706 					__traits(getMember, t, memberName) = cast(type) stringToLong(ev.stringValue);
12707 				});
12708 
12709 				updateMemberDelegates[memberName] = () {
12710 					le.content = toInternal!string(__traits(getMember, t, memberName));
12711 				};
12712 			}
12713 		}}
12714 	}
12715 
12716 	void delegate()[string] updateMemberDelegates;
12717 
12718 	override void readUpdatesFromObject() {
12719 		foreach(k, v; updateMemberDelegates)
12720 			v();
12721 	}
12722 }
12723 
12724 /++
12725 	Creates a dialog based on a data structure.
12726 
12727 	---
12728 	dialog((YourStructure value) {
12729 		// the user filled in the struct and clicked OK,
12730 		// you can check the members now
12731 	});
12732 	---
12733 +/
12734 /// Group: generating_from_code
12735 void dialog(T)(void delegate(T) onOK, void delegate() onCancel = null, string title = T.stringof) {
12736 	auto dg = new AutomaticDialog!T(onOK, onCancel, title);
12737 	dg.show();
12738 }
12739 
12740 private static template I(T...) { alias I = T; }
12741 
12742 
12743 private string beautify(string name, char space = ' ', bool allLowerCase = false) {
12744 	if(name == "id")
12745 		return allLowerCase ? name : "ID";
12746 
12747 	char[160] buffer;
12748 	int bufferIndex = 0;
12749 	bool shouldCap = true;
12750 	bool shouldSpace;
12751 	bool lastWasCap;
12752 	foreach(idx, char ch; name) {
12753 		if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
12754 
12755 		if((ch >= 'A' && ch <= 'Z') || ch == '_') {
12756 			if(lastWasCap) {
12757 				// two caps in a row, don't change. Prolly acronym.
12758 			} else {
12759 				if(idx)
12760 					shouldSpace = true; // new word, add space
12761 			}
12762 
12763 			lastWasCap = true;
12764 		} else {
12765 			lastWasCap = false;
12766 		}
12767 
12768 		if(shouldSpace) {
12769 			buffer[bufferIndex++] = space;
12770 			if(bufferIndex == buffer.length) return name; // out of space, just give up, not that important
12771 			shouldSpace = false;
12772 		}
12773 		if(shouldCap) {
12774 			if(ch >= 'a' && ch <= 'z')
12775 				ch -= 32;
12776 			shouldCap = false;
12777 		}
12778 		if(allLowerCase && ch >= 'A' && ch <= 'Z')
12779 			ch += 32;
12780 		buffer[bufferIndex++] = ch;
12781 	}
12782 	return buffer[0 .. bufferIndex].idup;
12783 }
12784 
12785 /++
12786 	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.
12787 +/
12788 class AutomaticDialog(T) : Dialog {
12789 	T t;
12790 
12791 	void delegate(T) onOK;
12792 	void delegate() onCancel;
12793 
12794 	override int paddingTop() { return defaultLineHeight; }
12795 	override int paddingBottom() { return defaultLineHeight; }
12796 	override int paddingRight() { return defaultLineHeight; }
12797 	override int paddingLeft() { return defaultLineHeight; }
12798 
12799 	this(void delegate(T) onOK, void delegate() onCancel, string title) {
12800 		assert(onOK !is null);
12801 		static if(is(T == class))
12802 			t = new T();
12803 		this.onOK = onOK;
12804 		this.onCancel = onCancel;
12805 		super(400, cast(int)(__traits(allMembers, T).length * 2) * (defaultLineHeight + 4 + 2) + Window.lineHeight + 56, title);
12806 
12807 		static if(is(T == class))
12808 			this.addDataControllerWidget(t);
12809 		else
12810 			this.addDataControllerWidget(&t);
12811 
12812 		auto hl = new HorizontalLayout(this);
12813 		auto stretch = new HorizontalSpacer(hl); // to right align
12814 		auto ok = new CommandButton("OK", hl);
12815 		auto cancel = new CommandButton("Cancel", hl);
12816 		ok.addEventListener(EventType.triggered, &OK);
12817 		cancel.addEventListener(EventType.triggered, &Cancel);
12818 
12819 		this.addEventListener((KeyDownEvent ev) {
12820 			if(ev.key == Key.Enter || ev.key == Key.PadEnter) {
12821 				ok.focus();
12822 				OK();
12823 				ev.preventDefault();
12824 			}
12825 			if(ev.key == Key.Escape) {
12826 				Cancel();
12827 				ev.preventDefault();
12828 			}
12829 		});
12830 
12831 		//this.children[0].focus();
12832 	}
12833 
12834 	override void OK() {
12835 		onOK(t);
12836 		close();
12837 	}
12838 
12839 	override void Cancel() {
12840 		if(onCancel)
12841 			onCancel();
12842 		close();
12843 	}
12844 }
12845 
12846 private template baseClassCount(Class) {
12847 	private int helper() {
12848 		int count = 0;
12849 		static if(is(Class bases == super)) {
12850 			foreach(base; bases)
12851 				static if(is(base == class))
12852 					count += 1 + baseClassCount!base;
12853 		}
12854 		return count;
12855 	}
12856 
12857 	enum int baseClassCount = helper();
12858 }
12859 
12860 private long stringToLong(string s) {
12861 	long ret;
12862 	if(s.length == 0)
12863 		return ret;
12864 	bool negative = s[0] == '-';
12865 	if(negative)
12866 		s = s[1 .. $];
12867 	foreach(ch; s) {
12868 		if(ch >= '0' && ch <= '9') {
12869 			ret *= 10;
12870 			ret += ch - '0';
12871 		}
12872 	}
12873 	if(negative)
12874 		ret = -ret;
12875 	return ret;
12876 }
12877 
12878 
12879 interface ReflectableProperties {
12880 	/++
12881 		Iterates the event's properties as strings. Note that keys may be repeated and a get property request may
12882 		call your sink with `null`. It it does, it means the key either doesn't request or cannot be represented by
12883 		json in the current implementation.
12884 
12885 		This is auto-implemented for you if you mixin [RegisterGetters] in your child classes and only have
12886 		properties of type `bool`, `int`, `double`, or `string`. For other ones, you will need to do it yourself
12887 		as of the June 2, 2021 release.
12888 
12889 		History:
12890 			Added June 2, 2021.
12891 
12892 		See_Also: [getPropertyAsString], [setPropertyFromString]
12893 	+/
12894 	void getPropertiesList(scope void delegate(string name) sink) const;// @nogc pure nothrow;
12895 	/++
12896 		Requests a property to be delivered to you as a string, through your `sink` delegate.
12897 
12898 		If the `value` is null, it means the property could not be retreived. If `valueIsJson`, it should
12899 		be interpreted as json, otherwise, it is just a plain string.
12900 
12901 		The sink should always be called exactly once for each call (it is basically a return value, but it might
12902 		use a local buffer it maintains instead of allocating a return value).
12903 
12904 		History:
12905 			Added June 2, 2021.
12906 
12907 		See_Also: [getPropertiesList], [setPropertyFromString]
12908 	+/
12909 	void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink);
12910 	/++
12911 		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.
12912 
12913 		History:
12914 			Added June 2, 2021.
12915 
12916 		See_Also: [getPropertiesList], [getPropertyAsString], [SetPropertyResult]
12917 	+/
12918 	SetPropertyResult setPropertyFromString(string name, scope const(char)[] str, bool strIsJson);
12919 
12920 	/// [setPropertyFromString] possible return values
12921 	enum SetPropertyResult {
12922 		success = 0, /// the property has been successfully set to the request value
12923 		notPermitted = -1, /// the property exists but it cannot be changed at this time
12924 		notImplemented = -2, /// the set function is not implemented for the given property (which may or may not exist)
12925 		noSuchProperty = -3, /// there is no property by that name
12926 		wrongFormat = -4, /// the string was given in the wrong format, e.g. passing "two" for an int value
12927 		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)
12928 	}
12929 
12930 	/++
12931 		You can mix this in to get an implementation in child classes. This does [setPropertyFromString].
12932 
12933 		Your original base class, however, must implement its own methods. I recommend doing the initial ones by hand.
12934 
12935 		For [Widget] and [Event], the library provides [Widget.Register] and [Event.Register] that call these for you, so you should
12936 		rarely need to use these building blocks directly.
12937 	+/
12938 	mixin template RegisterSetters() {
12939 		override SetPropertyResult setPropertyFromString(string name, scope const(char)[] value, bool valueIsJson) {
12940 			switch(name) {
12941 				foreach(memberName; __traits(derivedMembers, typeof(this))) {
12942 					case memberName:
12943 						static if(is(typeof(__traits(getMember, this, memberName)) : const bool)) {
12944 							if(value != "true" && value != "false")
12945 								return SetPropertyResult.wrongFormat;
12946 							__traits(getMember, this, memberName) = value == "true" ? true : false;
12947 							return SetPropertyResult.success;
12948 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const long)) {
12949 							import core.stdc.stdlib;
12950 							char[128] zero = 0;
12951 							if(buffer.length + 1 >= zero.length)
12952 								return SetPropertyResult.wrongFormat;
12953 							zero[0 .. buffer.length] = buffer[];
12954 							__traits(getMember, this, memberName) = strtol(buffer.ptr, null, 10);
12955 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const double)) {
12956 							import core.stdc.stdlib;
12957 							char[128] zero = 0;
12958 							if(buffer.length + 1 >= zero.length)
12959 								return SetPropertyResult.wrongFormat;
12960 							zero[0 .. buffer.length] = buffer[];
12961 							__traits(getMember, this, memberName) = strtod(buffer.ptr, null, 10);
12962 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const string)) {
12963 							__traits(getMember, this, memberName) = value.idup;
12964 						} else {
12965 							return SetPropertyResult.notImplemented;
12966 						}
12967 
12968 				}
12969 				default:
12970 					return super.setPropertyFromString(name, value, valueIsJson);
12971 			}
12972 		}
12973 	}
12974 
12975 	/++
12976 		You can mix this in to get an implementation in child classes. This does [getPropertyAsString] and [getPropertiesList].
12977 
12978 		Your original base class, however, must implement its own methods. I recommend doing the initial ones by hand.
12979 
12980 		For [Widget] and [Event], the library provides [Widget.Register] and [Event.Register] that call these for you, so you should
12981 		rarely need to use these building blocks directly.
12982 	+/
12983 	mixin template RegisterGetters() {
12984 		override void getPropertiesList(scope void delegate(string name) sink) const {
12985 			super.getPropertiesList(sink);
12986 
12987 			foreach(memberName; __traits(derivedMembers, typeof(this))) {
12988 				sink(memberName);
12989 			}
12990 		}
12991 		override void getPropertyAsString(string name, scope void delegate(string name, scope const(char)[] value, bool valueIsJson) sink) {
12992 			switch(name) {
12993 				foreach(memberName; __traits(derivedMembers, typeof(this))) {
12994 					case memberName:
12995 						static if(is(typeof(__traits(getMember, this, memberName)) : const bool)) {
12996 							sink(name, __traits(getMember, this, memberName) ? "true" : "false", true);
12997 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const long)) {
12998 							import core.stdc.stdio;
12999 							char[32] buffer;
13000 							auto len = snprintf(buffer.ptr, buffer.length, "%lld", cast(long) __traits(getMember, this, memberName));
13001 							sink(name, buffer[0 .. len], true);
13002 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const double)) {
13003 							import core.stdc.stdio;
13004 							char[32] buffer;
13005 							auto len = snprintf(buffer.ptr, buffer.length, "%f", cast(double) __traits(getMember, this, memberName));
13006 							sink(name, buffer[0 .. len], true);
13007 						} else static if(is(typeof(__traits(getMember, this, memberName)) : const string)) {
13008 							sink(name, __traits(getMember, this, memberName), false);
13009 							//sinkJsonString(memberName, __traits(getMember, this, memberName), sink);
13010 						} else {
13011 							sink(name, null, true);
13012 						}
13013 
13014 					return;
13015 				}
13016 				default:
13017 					return super.getPropertyAsString(name, sink);
13018 			}
13019 		}
13020 	}
13021 }
13022 
13023 
13024 /+
13025 
13026 	I could fix up the hierarchy kinda like this
13027 
13028 	class Widget {
13029 		Widget[] children() { return null; }
13030 	}
13031 	interface WidgetContainer {
13032 		Widget asWidget();
13033 		void addChild(Widget w);
13034 
13035 		// alias asWidget this; // but meh
13036 	}
13037 
13038 	Widget can keep a (Widget parent) ctor, but it should prolly deprecate and tell people to instead change their ctors to take WidgetContainer instead.
13039 
13040 	class Layout : Widget, WidgetContainer {}
13041 
13042 	class Window : WidgetContainer {}
13043 
13044 
13045 	All constructors that previously took Widgets should now take WidgetContainers instead
13046 
13047 
13048 
13049 	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".
13050 +/
13051 
13052 /+
13053 	LAYOUTS 2.0
13054 
13055 	can just be assigned as a function. assigning a new one will cause it to be immediately called.
13056 
13057 	they simply are responsible for the recomputeChildLayout. If this pointer is null, it uses the default virtual one.
13058 
13059 	recomputeChildLayout only really needs a property accessor proxy... just the layout info too.
13060 
13061 	and even Paint can just use computedStyle...
13062 
13063 		background color
13064 		font
13065 		border color and style
13066 
13067 	And actually the style proxy can offer some helper routines to draw these like the draw 3d box
13068 		please note that many widgets and in some modes will completely ignore properties as they will.
13069 		they are just hints you set, not promises.
13070 
13071 
13072 
13073 
13074 
13075 	So generally the existing virtual functions are just the default for the class. But individual objects
13076 	or stylesheets can override this. The virtual ones count as tag-level specificity in css.
13077 +/
13078 
13079 /++
13080 	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.
13081 
13082 	History:
13083 		Added May 24, 2021.
13084 +/
13085 struct WidgetBackground {
13086 	/++
13087 		A background with the given solid color.
13088 	+/
13089 	this(Color color) {
13090 		this.color = color;
13091 	}
13092 
13093 	this(WidgetBackground bg) {
13094 		this = bg;
13095 	}
13096 
13097 	/++
13098 		Creates a widget from the string.
13099 
13100 		Currently, it only supports solid colors via [Color.fromString], but it will likely be expanded in the future to something more like css.
13101 	+/
13102 	static WidgetBackground fromString(string s) {
13103 		return WidgetBackground(Color.fromString(s));
13104 	}
13105 
13106 	private Color color;
13107 }
13108 
13109 /++
13110 	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!)
13111 
13112 	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.
13113 
13114 	You should not inherit from this directly, but instead use [VisualTheme].
13115 
13116 	History:
13117 		Added May 8, 2021
13118 +/
13119 abstract class BaseVisualTheme {
13120 	/// Don't implement this, instead use [VisualTheme] and implement `paint` methods on specific subclasses you want to override.
13121 	abstract void doPaint(Widget widget, WidgetPainter painter);
13122 
13123 	/+
13124 	/// Don't implement this, instead use [VisualTheme] and implement `StyleOverride` aliases on specific subclasses you want to override.
13125 	abstract void useStyleProperties(Widget w, scope void delegate(scope Widget.Style props) dg);
13126 	+/
13127 
13128 	/++
13129 		Returns the property as a string, or null if it was not overridden in the style definition. The idea here is something like css,
13130 		where the interpretation of the string varies for each property and may include things like measurement units.
13131 	+/
13132 	abstract string getPropertyString(Widget widget, string propertyName);
13133 
13134 	/++
13135 		Default background color of the window. Widgets also use this to simulate transparency.
13136 
13137 		Probably some shade of grey.
13138 	+/
13139 	abstract Color windowBackgroundColor();
13140 	abstract Color widgetBackgroundColor();
13141 	abstract Color foregroundColor();
13142 	abstract Color lightAccentColor();
13143 	abstract Color darkAccentColor();
13144 
13145 	/++
13146 		Color used to indicate active selections in lists and text boxes, etc.
13147 	+/
13148 	abstract Color selectionColor();
13149 
13150 	abstract OperatingSystemFont defaultFont();
13151 
13152 	private OperatingSystemFont defaultFontCache_;
13153 	private bool defaultFontCachePopulated;
13154 	private OperatingSystemFont defaultFontCached() {
13155 		if(!defaultFontCachePopulated) {
13156 			// FIXME: set this to false if X disconnect or if visual theme changes
13157 			defaultFontCache_ = defaultFont();
13158 			defaultFontCachePopulated = true;
13159 		}
13160 		return defaultFontCache_;
13161 	}
13162 }
13163 
13164 /+
13165 	A widget should have:
13166 		classList
13167 		dataset
13168 		attributes
13169 		computedStyles
13170 		state (persistent)
13171 		dynamic state (focused, hover, etc)
13172 +/
13173 
13174 // visualTheme.computedStyle(this).paddingLeft
13175 
13176 
13177 /++
13178 	This is your entry point to create your own visual theme for custom widgets.
13179 +/
13180 abstract class VisualTheme(CRTP) : BaseVisualTheme {
13181 	override string getPropertyString(Widget widget, string propertyName) {
13182 		return null;
13183 	}
13184 
13185 	/+
13186 		mixin StyleOverride!Widget
13187 	final override void useStyleProperties(Widget w, scope void delegate(scope Widget.Style props) dg) {
13188 		w.useStyleProperties(dg);
13189 	}
13190 	+/
13191 
13192 	final override void doPaint(Widget widget, WidgetPainter painter) {
13193 		auto derived = cast(CRTP) cast(void*) this;
13194 
13195 		scope void delegate(Widget, WidgetPainter) bestMatch;
13196 		int bestMatchScore;
13197 
13198 		static if(__traits(hasMember, CRTP, "paint"))
13199 		foreach(overload; __traits(getOverloads, CRTP, "paint")) {
13200 			static if(is(typeof(overload) Params == __parameters)) {
13201 				static assert(Params.length == 2);
13202 				static assert(is(Params[0] : Widget));
13203 				static assert(is(Params[1] == WidgetPainter));
13204 				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);
13205 
13206 				alias type = Params[0];
13207 				if(cast(type) widget) {
13208 					auto score = baseClassCount!type;
13209 
13210 					if(score > bestMatchScore) {
13211 						bestMatch = cast(typeof(bestMatch)) &__traits(child, derived, overload);
13212 						bestMatchScore = score;
13213 					}
13214 				}
13215 			} else static assert(0, "paint should be a method.");
13216 		}
13217 
13218 		if(bestMatch)
13219 			bestMatch(widget, painter);
13220 		else
13221 			widget.paint(painter);
13222 	}
13223 
13224 	// 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
13225 	override Color windowBackgroundColor() { return Color(212, 212, 212); }
13226 	override Color widgetBackgroundColor() { return Color.white; }
13227 	override Color foregroundColor() { return Color.black; }
13228 	override Color darkAccentColor() { return Color(172, 172, 172); }
13229 	override Color lightAccentColor() { return Color(223, 223, 223); }
13230 	override Color selectionColor() { return Color(0, 0, 128); }
13231 	override OperatingSystemFont defaultFont() { return null; } // will just use the default out of simpledisplay's xfontstr
13232 
13233 	private static struct Cached {
13234 		// i prolly want to do this
13235 	}
13236 }
13237 
13238 final class DefaultVisualTheme : VisualTheme!DefaultVisualTheme {
13239 	/+
13240 	OperatingSystemFont defaultFont() { return new OperatingSystemFont("Times New Roman", 8, FontWeight.medium); }
13241 	Color windowBackgroundColor() { return Color(242, 242, 242); }
13242 	Color darkAccentColor() { return windowBackgroundColor; }
13243 	Color lightAccentColor() { return windowBackgroundColor; }
13244 	+/
13245 }
13246 
13247 /++
13248 	Event fired when an [Observeable] variable changes. You will want to add an event listener referencing
13249 	the field like `widget.addEventListener((scope StateChanged!(Whatever.field) ev) { });`
13250 
13251 	History:
13252 		Moved from minigui_addons.webview to main minigui on November 27, 2021 (dub v10.4)
13253 +/
13254 class StateChanged(alias field) : Event {
13255 	enum EventString = __traits(identifier, __traits(parent, field)) ~ "." ~ __traits(identifier, field) ~ ":change";
13256 	override bool cancelable() const { return false; }
13257 	this(Widget target, typeof(field) newValue) {
13258 		this.newValue = newValue;
13259 		super(EventString, target);
13260 	}
13261 
13262 	typeof(field) newValue;
13263 }
13264 
13265 /++
13266 	Convenience function to add a `triggered` event listener.
13267 
13268 	Its implementation is simply `w.addEventListener("triggered", dg);`
13269 
13270 	History:
13271 		Added November 27, 2021 (dub v10.4)
13272 +/
13273 void addWhenTriggered(Widget w, void delegate() dg) {
13274 	w.addEventListener("triggered", dg);
13275 }
13276 
13277 /++
13278 	Observable varables can be added to widgets and when they are changed, it fires
13279 	off a [StateChanged] event so you can react to it.
13280 
13281 	It is implemented as a getter and setter property, along with another helper you
13282 	can use to subscribe whith is `name_changed`. You can also subscribe to the [StateChanged]
13283 	event through the usual means. Just give the name of the variable. See [StateChanged] for an
13284 	example.
13285 
13286 	History:
13287 		Moved from minigui_addons.webview to main minigui on November 27, 2021 (dub v10.4)
13288 +/
13289 mixin template Observable(T, string name) {
13290 	private T backing;
13291 
13292 	mixin(q{
13293 		void } ~ name ~ q{_changed (void delegate(T) dg) {
13294 			this.addEventListener((StateChanged!this_thing ev) {
13295 				dg(ev.newValue);
13296 			});
13297 		}
13298 
13299 		@property T } ~ name ~ q{ () {
13300 			return backing;
13301 		}
13302 
13303 		@property void } ~ name ~ q{ (T t) {
13304 			backing = t;
13305 			auto event = new StateChanged!this_thing(this, t);
13306 			event.dispatch();
13307 		}
13308 	});
13309 
13310 	mixin("private alias this_thing = " ~ name ~ ";");
13311 }
13312 
13313 
13314 // still do layout delegation
13315 // and... split off Window from Widget.
13316 
13317 version(minigui_demos)
13318 // https://github.com/adamdruppe/arsd/issues/310
13319 unittest {
13320 	import arsd.minigui;
13321 
13322 	class ColorWidget : Widget {
13323 		this(Color color, Widget parent) {
13324 			this.color = color;
13325 			super(parent);
13326 		}
13327 		Color color;
13328 		class Style : Widget.Style {
13329 			override WidgetBackground background() { return WidgetBackground(color); }
13330 		}
13331 		mixin OverrideStyle!Style;
13332 	}
13333 
13334 	void main() {
13335 		auto window = new Window;
13336 
13337 		auto header = new class HorizontalLayout {
13338 			this() { super(window); }
13339 			override int maxHeight() { return 50; }
13340 		};
13341 
13342 		auto bar = new HorizontalLayout(window);
13343 
13344 		auto left = new class VerticalLayout {
13345 			this() { super(bar); }
13346 			override int maxWidth() { return 100; }
13347 		};
13348 
13349 		auto container = new Widget(bar);
13350 
13351 
13352 		auto headerColorBox = new ColorWidget(Color(0, 255, 255), header);
13353 		auto leftColorBox = new ColorWidget(Color.green, left);
13354 		auto rightColorBox = new ColorWidget(Color.purple, container);
13355 
13356 		window.loop();
13357 	}
13358 }
13359 
13360 
13361 // FIXME: i called hotkey accelerator in some places. hotkey = key when menu is active like E&xit. accelerator = global shortcut.
13362 // FIXME: make multiple accelerators disambiguate based ona rgs
13363 // FIXME: MainWindow ctor should have same arg order as Window
13364 // FIXME: mainwindow ctor w/ client area size instead of total size.
13365 // 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.
13366 // FIXME: tri-state checkbox
13367 // FIXME: subordinate controls grouping...