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