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