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