1 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb775498%28v=vs.85%29.aspx
2 
3 // FIXME: a scroll area event signaling when a thing comes into view might be good
4 // FIXME: arrow key navigation and accelerators in dialog boxes will be a must
5 
6 // FIXME: unify Windows style line endings
7 
8 /*
9 	TODO:
10 
11 	class Form with submit behavior
12 	class CommandButton with a consistent size
13 
14 	disabled widgets and menu items
15 
16 	TrackBar controls
17 
18 	event cleanup
19 	tooltips.
20 	api improvements
21 
22 	margins are kinda broken, they don't collapse like they should. at least.
23 
24 	a table form btw would be a horizontal layout of vertical layouts holding each column
25 	that would give the same width things
26 */
27 
28 /*
29 
30 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
31 */
32 
33 /++
34 	minigui is a smallish GUI widget library, aiming to be on par with at least
35 	HTML4 forms and a few other expected gui components. It uses native controls
36 	on Windows and does its own thing on Linux (Mac is not currently supported but
37 	may be later, and should use native controls) to keep size down. The Linux
38 	appearance is similar to Windows 95 and avoids using images to maintain network
39 	efficiency on remote X connections.
40 	
41 	minigui's only required dependencies are [arsd.simpledisplay] and [arsd.color].
42 
43 	Its #1 goal is to be useful without being large and complicated like GTK and Qt.
44 	It isn't hugely concerned with appearance - on Windows, it just uses the native
45 	controls and native theme, and on Linux, it keeps it simple and I may change that
46 	at any time.
47 
48 	I love Qt, if you want something full featured, use it! But if you want something
49 	you can just drop into a small project and expect the basics to work without outside
50 	dependencies, hopefully minigui will work for you.
51 
52 	The event model is similar to what you use in the browser with Javascript and the
53 	layout engine tries to automatically fit things in, similar to a css flexbox.
54 
55 
56 	FOR BEST RESULTS: be sure to link with the appropriate subsystem command
57 	`-L/SUBSYSTEM:WINDOWS:5.0`, for example, because otherwise you'll get a
58 	console and other visual bugs.
59 
60 	HTML_To_Classes:
61 		`<input type="text">` = [LineEdit]
62 		`<textarea>` = [TextEdit]
63 		`<select>` = [DropDownSelection]
64 		`<input type="checkbox">` = [Checkbox]
65 		`<input type="radio">` = [Radiobox]
66 		`<button>` = [Button]
67 
68 
69 	Stretchiness:
70 		The default is 4. You can use larger numbers for things that should
71 		consume a lot of space, and lower numbers for ones that are better at
72 		smaller sizes.
73 
74 	Overlapped_input:
75 		COMING SOON:
76 		minigui will include a little bit of I/O functionality that just works
77 		with the event loop. If you want to get fancy, I suggest spinning up
78 		another thread and posting events back and forth.
79 
80 	$(H2 Add ons)
81 
82 	$(H2 XML definitions)
83 		If you use [arsd.minigui_xml], you can create widget trees from XML at runtime.
84 
85 	$(H3 Scriptability)
86 		minigui is compatible with [arsd.script]. If you see `@scriptable` on a method
87 		in this documentation, it means you can call it from the script language.
88 
89 		Tip: to allow easy creation of widget trees from script, import [arsd.minigui_xml]
90 		and make [arsd.minigui_xml.makeWidgetFromString] available to your script:
91 
92 		---
93 		import arsd.minigui_xml;
94 		import arsd.script;
95 
96 		var globals = var.emptyObject;
97 		globals.makeWidgetFromString = &makeWidgetFromString;
98 
99 		// this now works
100 		interpret(`var window = makeWidgetFromString("<MainWindow />");`, globals);
101 		---
102 
103 		More to come.
104 +/
105 module arsd.minigui;
106 
107 public import arsd.simpledisplay;
108 private alias Rectangle = arsd.color.Rectangle; // I specifically want this in here, not the win32 GDI Rectangle()
109 
110 version(Windows)
111 	import core.sys.windows.windows;
112 
113 // this is a hack to call the original window procedure on native win32 widgets if our event listener thing prevents default.
114 private bool lastDefaultPrevented;
115 
116 /// Methods marked with this are available from scripts
117 alias scriptable = arsd_jsvar_compatible;
118 
119 version(Windows) {
120 	// use native widgets when available unless specifically asked otherwise
121 	version(custom_widgets) {
122 		enum bool UsingCustomWidgets = true;
123 		enum bool UsingWin32Widgets = false;
124 	} else {
125 		version = win32_widgets;
126 		enum bool UsingCustomWidgets = false;
127 		enum bool UsingWin32Widgets = true;
128 	}
129 	// and native theming when needed
130 	//version = win32_theming;
131 } else {
132 	enum bool UsingCustomWidgets = true;
133 	enum bool UsingWin32Widgets = false;
134 	version=custom_widgets;
135 }
136 
137 
138 
139 /*
140 
141 	The main goals of minigui.d are to:
142 		1) Provide basic widgets that just work in a lightweight lib.
143 		   I basically want things comparable to a plain HTML form,
144 		   plus the easy and obvious things you expect from Windows
145 		   apps like a menu.
146 		2) Use native things when possible for best functionality with
147 		   least library weight.
148 		3) Give building blocks to provide easy extension for your
149 		   custom widgets, or hooking into additional native widgets
150 		   I didn't wrap.
151 		4) Provide interfaces for easy interaction between third
152 		   party minigui extensions. (event model, perhaps
153 		   signals/slots, drop-in ease of use bits.)
154 		5) Zero non-system dependencies, including Phobos as much as
155 		   I reasonably can. It must only import arsd.color and
156 		   my simpledisplay.d. If you need more, it will have to be
157 		   an extension module.
158 		6) An easy layout system that generally works.
159 
160 	A stretch goal is to make it easy to make gui forms with code,
161 	some kind of resource file (xml?) and even a wysiwyg designer.
162 
163 	Another stretch goal is to make it easy to hook data into the gui,
164 	including from reflection. So like auto-generate a form from a
165 	function signature or struct definition, or show a list from an
166 	array that automatically updates as the array is changed. Then,
167 	your program focuses on the data more than the gui interaction.
168 
169 
170 
171 	STILL NEEDED:
172 		* combo box. (this is diff than select because you can free-form edit too. more like a lineedit with autoselect)
173 		* slider
174 		* listbox
175 		* spinner
176 		* label?
177 		* rich text
178 */
179 
180 alias HWND=void*;
181 
182 ///
183 abstract class ComboboxBase : Widget {
184 	// if the user can enter arbitrary data, we want to use  2 == CBS_DROPDOWN
185 	// or to always show the list, we want CBS_SIMPLE == 1
186 	version(win32_widgets)
187 		this(uint style, Widget parent = null) {
188 			super(parent);
189 			createWin32Window(this, "ComboBox"w, null, style);
190 		}
191 	else version(custom_widgets)
192 		this(Widget parent = null) {
193 			super(parent);
194 
195 			addEventListener("keydown", (Event event) {
196 				if(event.key == Key.Up) {
197 					if(selection > -1) { // -1 means select blank
198 						selection--;
199 						fireChangeEvent();
200 					}
201 					event.preventDefault();
202 				}
203 				if(event.key == Key.Down) {
204 					if(selection + 1 < options.length) {
205 						selection++;
206 						fireChangeEvent();
207 					}
208 					event.preventDefault();
209 				}
210 
211 			});
212 
213 		}
214 	else static assert(false);
215 
216 	private string[] options;
217 	private int selection = -1;
218 
219 	void addOption(string s) {
220 		options ~= s;
221 		version(win32_widgets)
222 		SendMessageA(hwnd, 323 /*CB_ADDSTRING*/, 0, cast(LPARAM) toStringzInternal(s));
223 	}
224 
225 	void setSelection(int idx) {
226 		selection = idx;
227 		version(win32_widgets)
228 		SendMessageA(hwnd, 334 /*CB_SETCURSEL*/, idx, 0);
229 
230 		auto t = new Event(EventType.change, this);
231 		t.intValue = selection;
232 		t.stringValue = selection == -1 ? null : options[selection];
233 		t.dispatch();
234 	}
235 
236 	version(win32_widgets)
237 	override void handleWmCommand(ushort cmd, ushort id) {
238 		selection = cast(int) SendMessageA(hwnd, 327 /* CB_GETCURSEL */, 0, 0);
239 		fireChangeEvent();
240 	}
241 
242 	private void fireChangeEvent() {
243 		if(selection >= options.length)
244 			selection = -1;
245 		auto event = new Event(EventType.change, this);
246 		event.intValue = selection;
247 		event.stringValue = selection == -1 ? null : options[selection];
248 		event.dispatch();
249 	}
250 
251 	override int minHeight() { return Window.lineHeight + 4; }
252 	override int maxHeight() { return Window.lineHeight + 4; }
253 
254 	version(custom_widgets) {
255 		SimpleWindow dropDown;
256 		void popup() {
257 			auto w = width;
258 			auto h = cast(int) this.options.length * Window.lineHeight + 8;
259 
260 			auto coord = this.globalCoordinates();
261 			auto dropDown = new SimpleWindow(
262 				w, h,
263 				null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.dropdownMenu, WindowFlags.dontAutoShow/*, window*/);
264 
265 			dropDown.move(coord.x, coord.y + this.height);
266 
267 			{
268 				auto painter = dropDown.draw();
269 				draw3dFrame(0, 0, w, h, painter, FrameStyle.risen);
270 				auto p = Point(4, 4);
271 				painter.outlineColor = Color.black;
272 				foreach(option; options) {
273 					painter.drawText(p, option);
274 					p.y += Window.lineHeight;
275 				}
276 			}
277 
278 			dropDown.setEventHandlers(
279 				(MouseEvent event) {
280 					if(event.type == MouseEventType.buttonReleased) {
281 						auto element = (event.y - 4) / Window.lineHeight;
282 						if(element >= 0 && element <= options.length) {
283 							selection = element;
284 
285 							fireChangeEvent();
286 						}
287 						dropDown.close();
288 					}
289 				}
290 			);
291 
292 			dropDown.show();
293 			dropDown.grabInput();
294 		}
295 
296 	}
297 }
298 
299 /++
300 	A drop-down list where the user must select one of the
301 	given options. Like `<select>` in HTML.
302 +/
303 class DropDownSelection : ComboboxBase {
304 	this(Widget parent = null) {
305 		version(win32_widgets)
306 			super(3 /* CBS_DROPDOWNLIST */, parent);
307 		else version(custom_widgets) {
308 			super(parent);
309 
310 			addEventListener("focus", () { this.redraw; });
311 			addEventListener("blur", () { this.redraw; });
312 			addEventListener(EventType.change, () { this.redraw; });
313 			addEventListener("mousedown", () { this.focus(); this.popup(); });
314 			addEventListener("keydown", (Event event) {
315 				if(event.key == Key.Space)
316 					popup();
317 			});
318 		} else static assert(false);
319 	}
320 
321 	version(custom_widgets)
322 	override void paint(ScreenPainter painter) {
323 		draw3dFrame(this, painter, FrameStyle.risen);
324 		painter.outlineColor = Color.black;
325 		painter.drawText(Point(4, 4), selection == -1 ? "" : options[selection]);
326 
327 		painter.outlineColor = Color.black;
328 		painter.fillColor = Color.black;
329 		Point[3] triangle;
330 		enum padding = 6;
331 		enum paddingV = 8;
332 		enum triangleWidth = 10;
333 		triangle[0] = Point(width - padding - triangleWidth, paddingV);
334 		triangle[1] = Point(width - padding - triangleWidth / 2, height - paddingV);
335 		triangle[2] = Point(width - padding - 0, paddingV);
336 		painter.drawPolygon(triangle[]);
337 
338 		if(isFocused()) {
339 			painter.fillColor = Color.transparent;
340 			painter.pen = Pen(Color.black, 1, Pen.Style.Dotted);
341 			painter.drawRectangle(Point(2, 2), width - 4, height - 4);
342 			painter.pen = Pen(Color.black, 1, Pen.Style.Solid);
343 
344 		}
345 
346 	}
347 
348 }
349 
350 /++
351 	A text box with a drop down arrow listing selections.
352 	The user can choose from the list, or type their own.
353 +/
354 class FreeEntrySelection : ComboboxBase {
355 	this(Widget parent = null) {
356 		version(win32_widgets)
357 			super(2 /* CBS_DROPDOWN */, parent);
358 		else version(custom_widgets) {
359 			super(parent);
360 			auto hl = new HorizontalLayout(this);
361 			lineEdit = new LineEdit(hl);
362 
363 			tabStop = false;
364 
365 			lineEdit.addEventListener("focus", &lineEdit.selectAll);
366 
367 			auto btn = new class ArrowButton {
368 				this() {
369 					super(ArrowDirection.down, hl);
370 				}
371 				override int maxHeight() {
372 					return int.max;
373 				}
374 			};
375 			//btn.addDirectEventListener("focus", &lineEdit.focus);
376 			btn.addEventListener("triggered", &this.popup);
377 			addEventListener(EventType.change, (Event event) {
378 				lineEdit.content = event.stringValue;
379 				lineEdit.focus();
380 				redraw();
381 			});
382 		}
383 		else static assert(false);
384 	}
385 
386 	version(custom_widgets) {
387 		LineEdit lineEdit;
388 	}
389 }
390 
391 /++
392 	A combination of free entry with a list below it.
393 +/
394 class ComboBox : ComboboxBase {
395 	this(Widget parent = null) {
396 		version(win32_widgets)
397 			super(1 /* CBS_SIMPLE */, parent);
398 		else version(custom_widgets) {
399 			super(parent);
400 			lineEdit = new LineEdit(this);
401 			listWidget = new ListWidget(this);
402 			listWidget.multiSelect = false;
403 			listWidget.addEventListener(EventType.change, delegate(Widget, Event) {
404 				string c = null;
405 				foreach(option; listWidget.options)
406 					if(option.selected) {
407 						c = option.label;
408 						break;
409 					}
410 				lineEdit.content = c;
411 			});
412 
413 			listWidget.tabStop = false;
414 			this.tabStop = false;
415 			listWidget.addEventListener("focus", &lineEdit.focus);
416 			this.addEventListener("focus", &lineEdit.focus);
417 
418 			addDirectEventListener(EventType.change, {
419 				listWidget.setSelection(selection);
420 				if(selection != -1)
421 					lineEdit.content = options[selection];
422 				lineEdit.focus();
423 				redraw();
424 			});
425 
426 			lineEdit.addEventListener("focus", &lineEdit.selectAll);
427 
428 			listWidget.addDirectEventListener(EventType.change, {
429 				int set = -1;
430 				foreach(idx, opt; listWidget.options)
431 					if(opt.selected) {
432 						set = cast(int) idx;
433 						break;
434 					}
435 				if(set != selection)
436 					this.setSelection(set);
437 			});
438 		} else static assert(false);
439 	}
440 
441 	override int minHeight() { return Window.lineHeight * 3; }
442 	override int maxHeight() { return int.max; }
443 	override int heightStretchiness() { return 5; }
444 
445 	version(custom_widgets) {
446 		LineEdit lineEdit;
447 		ListWidget listWidget;
448 
449 		override void addOption(string s) {
450 			listWidget.options ~= ListWidget.Option(s);
451 			ComboboxBase.addOption(s);
452 		}
453 	}
454 }
455 
456 /+
457 class Spinner : Widget {
458 	version(win32_widgets)
459 	this(Widget parent = null) {
460 		super(parent);
461 		parentWindow = parent.parentWindow;
462 		auto hlayout = new HorizontalLayout(this);
463 		lineEdit = new LineEdit(hlayout);
464 		upDownControl = new UpDownControl(hlayout);
465 	}
466 
467 	LineEdit lineEdit;
468 	UpDownControl upDownControl;
469 }
470 
471 class UpDownControl : Widget {
472 	version(win32_widgets)
473 	this(Widget parent = null) {
474 		super(parent);
475 		parentWindow = parent.parentWindow;
476 		createWin32Window(this, "msctls_updown32"w, null, 4/*UDS_ALIGNRIGHT*/| 2 /* UDS_SETBUDDYINT */ | 16 /* UDS_AUTOBUDDY */ | 32 /* UDS_ARROWKEYS */);
477 	}
478 
479 	override int minHeight() { return Window.lineHeight; }
480 	override int maxHeight() { return Window.lineHeight * 3/2; }
481 
482 	override int minWidth() { return Window.lineHeight * 3/2; }
483 	override int maxWidth() { return Window.lineHeight * 3/2; }
484 }
485 +/
486 
487 /+
488 class DataView : Widget {
489 	// this is the omnibus data viewer
490 	// the internal data layout is something like:
491 	// string[string][] but also each node can have parents
492 }
493 +/
494 
495 
496 // http://msdn.microsoft.com/en-us/library/windows/desktop/bb775491(v=vs.85).aspx#PROGRESS_CLASS
497 
498 // http://svn.dsource.org/projects/bindings/trunk/win32/commctrl.d
499 
500 // FIXME: menus should prolly capture the mouse. ugh i kno.
501 /*
502 	TextEdit needs:
503 
504 	* caret manipulation
505 	* selection control
506 	* convenience functions for appendText, insertText, insertTextAtCaret, etc.
507 
508 	For example:
509 
510 	connect(paste, &textEdit.insertTextAtCaret);
511 
512 	would be nice.
513 
514 
515 
516 	I kinda want an omnibus dataview that combines list, tree,
517 	and table - it can be switched dynamically between them.
518 
519 	Flattening policy: only show top level, show recursive, show grouped
520 	List styles: plain list (e.g. <ul>), tiles (some details next to it), icons (like Windows explorer)
521 
522 	Single select, multi select, organization, drag+drop
523 */
524 
525 //static if(UsingSimpledisplayX11)
526 version(win32_widgets) {}
527 else version(custom_widgets)
528 	enum windowBackgroundColor = Color(192, 192, 192);
529 else static assert(false);
530 
531 private const(char)* toStringzInternal(string s) { return (s ~ '\0').ptr; }
532 private const(wchar)* toWstringzInternal(in char[] s) {
533 	wchar[] str;
534 	str.reserve(s.length + 1);
535 	foreach(dchar ch; s)
536 		str ~= ch;
537 	str ~= '\0';
538 	return str.ptr;
539 }
540 
541 enum FrameStyle {
542 	risen,
543 	sunk
544 }
545 
546 version(custom_widgets)
547 void draw3dFrame(Widget widget, ScreenPainter painter, FrameStyle style, Color background = windowBackgroundColor) {
548 	draw3dFrame(0, 0, widget.width, widget.height, painter, style, background);
549 }
550 
551 version(custom_widgets)
552 void draw3dFrame(int x, int y, int width, int height, ScreenPainter painter, FrameStyle style, Color background = windowBackgroundColor) {
553 	// outer layer
554 	painter.outlineColor = style == FrameStyle.sunk ? Color.white : Color.black;
555 	painter.fillColor = background;
556 	painter.drawRectangle(Point(x + 0, y + 0), width, height);
557 
558 	painter.outlineColor = (style == FrameStyle.sunk) ? Color(128, 128, 128) : Color(223, 223, 223);
559 	painter.drawLine(Point(x + 0, y + 0), Point(x + width, y + 0));
560 	painter.drawLine(Point(x + 0, y + 0), Point(x + 0, y + height - 1));
561 
562 	// inner layer
563 	//right, bottom
564 	painter.outlineColor = (style == FrameStyle.sunk) ? Color(223, 223, 223) : Color(128, 128, 128);
565 	painter.drawLine(Point(x + width - 2, y + 2), Point(x + width - 2, y + height - 2));
566 	painter.drawLine(Point(x + 2, y + height - 2), Point(x + width - 2, y + height - 2));
567 	// left, top
568 	painter.outlineColor = (style == FrameStyle.sunk) ? Color.black : Color.white;
569 	painter.drawLine(Point(x + 1, y + 1), Point(x + width - 2, y + 1));
570 	painter.drawLine(Point(x + 1, y + 1), Point(x + 1, y + height - 2));
571 }
572 
573 ///
574 class Action {
575 	version(win32_widgets) {
576 		private int id;
577 		private static int lastId = 9000;
578 		private static Action[int] mapping;
579 	}
580 
581 	KeyEvent accelerator;
582 
583 	///
584 	this(string label, ushort icon = 0, void delegate() triggered = null) {
585 		this.label = label;
586 		this.iconId = icon;
587 		if(triggered !is null)
588 			this.triggered ~= triggered;
589 		version(win32_widgets) {
590 			id = ++lastId;
591 			mapping[id] = this;
592 		}
593 	}
594 
595 	private string label;
596 	private ushort iconId;
597 	// icon
598 
599 	// when it is triggered, the triggered event is fired on the window
600 	void delegate()[] triggered;
601 }
602 
603 /*
604 	plan:
605 		keyboard accelerators
606 
607 		* menus (and popups and tooltips)
608 		* status bar
609 		* toolbars and buttons
610 
611 		sortable table view
612 
613 		maybe notification area icons
614 		basic clipboard
615 
616 		* radio box
617 		splitter
618 		toggle buttons (optionally mutually exclusive, like in Paint)
619 		label, rich text display, multi line plain text (selectable)
620 		* fieldset
621 		* nestable grid layout
622 		single line text input
623 		* multi line text input
624 		slider
625 		spinner
626 		list box
627 		drop down
628 		combo box
629 		auto complete box
630 		* progress bar
631 
632 		terminal window/widget (on unix it might even be a pty but really idk)
633 
634 		ok button
635 		cancel button
636 
637 		keyboard hotkeys
638 
639 		scroll widget
640 
641 		event redirections and network transparency
642 		script integration
643 */
644 
645 
646 /*
647 	MENUS
648 
649 	auto bar = new MenuBar(window);
650 	window.menuBar = bar;
651 
652 	auto fileMenu = bar.addItem(new Menu("&File"));
653 	fileMenu.addItem(new MenuItem("&Exit"));
654 
655 
656 	EVENTS
657 
658 	For controls, you should usually use "triggered" rather than "click", etc., because
659 	triggered handles both keyboard (focus and press as well as hotkeys) and mouse activation.
660 	This is the case on menus and pushbuttons.
661 
662 	"click", on the other hand, currently only fires when it is literally clicked by the mouse.
663 */
664 
665 
666 /*
667 enum LinePreference {
668 	AlwaysOnOwnLine, // always on its own line
669 	PreferOwnLine, // it will always start a new line, and if max width <= line width, it will expand all the way
670 	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
671 }
672 */
673 
674 mixin template Padding(string code) {
675 	override int paddingLeft() { return mixin(code);}
676 	override int paddingRight() { return mixin(code);}
677 	override int paddingTop() { return mixin(code);}
678 	override int paddingBottom() { return mixin(code);}
679 }
680 
681 mixin template Margin(string code) {
682 	override int marginLeft() { return mixin(code);}
683 	override int marginRight() { return mixin(code);}
684 	override int marginTop() { return mixin(code);}
685 	override int marginBottom() { return mixin(code);}
686 }
687 
688 
689 mixin template LayoutInfo() {
690 	int minWidth() { return 0; }
691 	int minHeight() {
692 		// default widgets have a vertical layout, therefore the minimum height is the sum of the contents
693 		int sum = 0;
694 		foreach(child; children) {
695 			sum += child.minHeight();
696 			sum += child.marginTop();
697 			sum += child.marginBottom();
698 		}
699 
700 		return sum;
701 	}
702 	int maxWidth() { return int.max; }
703 	int maxHeight() { return int.max; }
704 	int widthStretchiness() { return 4; }
705 	int heightStretchiness() { return 4; }
706 
707 	int marginLeft() { return 0; }
708 	int marginRight() { return 0; }
709 	int marginTop() { return 0; }
710 	int marginBottom() { return 0; }
711 	int paddingLeft() { return 0; }
712 	int paddingRight() { return 0; }
713 	int paddingTop() { return 0; }
714 	int paddingBottom() { return 0; }
715 	//LinePreference linePreference() { return LinePreference.PreferOwnLine; }
716 
717 	void recomputeChildLayout() {
718 		.recomputeChildLayout!"height"(this);
719 	}
720 }
721 
722 private
723 void recomputeChildLayout(string relevantMeasure)(Widget parent) {
724 	enum calcingV = relevantMeasure == "height";
725 
726 	parent.registerMovement();
727 
728 	if(parent.children.length == 0)
729 		return;
730 
731 	enum firstThingy = relevantMeasure == "height" ? "Top" : "Left";
732 	enum secondThingy = relevantMeasure == "height" ? "Bottom" : "Right";
733 
734 	enum otherFirstThingy = relevantMeasure == "height" ? "Left" : "Top";
735 	enum otherSecondThingy = relevantMeasure == "height" ? "Right" : "Bottom";
736 
737 	// my own width and height should already be set by the caller of this function...
738 	int spaceRemaining = mixin("parent." ~ relevantMeasure) -
739 		mixin("parent.padding"~firstThingy~"()") -
740 		mixin("parent.padding"~secondThingy~"()");
741 
742 	int stretchinessSum;
743 	int stretchyChildSum;
744 	int lastMargin = 0;
745 
746 	// set initial size
747 	foreach(child; parent.children) {
748 		if(cast(StaticPosition) child)
749 			continue;
750 		if(child.hidden)
751 			continue;
752 
753 		static if(calcingV) {
754 			child.width = parent.width -
755 				mixin("child.margin"~otherFirstThingy~"()") -
756 				mixin("child.margin"~otherSecondThingy~"()") -
757 				mixin("parent.padding"~otherFirstThingy~"()") -
758 				mixin("parent.padding"~otherSecondThingy~"()");
759 
760 			if(child.width < 0)
761 				child.width = 0;
762 			if(child.width > child.maxWidth())
763 				child.width = child.maxWidth();
764 			child.height = child.minHeight();
765 		} else {
766 			child.height = parent.height -
767 				mixin("child.margin"~firstThingy~"()") -
768 				mixin("child.margin"~secondThingy~"()") -
769 				mixin("parent.padding"~firstThingy~"()") -
770 				mixin("parent.padding"~secondThingy~"()");
771 			if(child.height < 0)
772 				child.height = 0;
773 			if(child.height > child.maxHeight())
774 				child.height = child.maxHeight();
775 			child.width = child.minWidth();
776 		}
777 
778 		spaceRemaining -= mixin("child." ~ relevantMeasure);
779 
780 		int thisMargin = mymax(lastMargin, mixin("child.margin"~firstThingy~"()"));
781 		auto margin = mixin("child.margin" ~ secondThingy ~ "()");
782 		lastMargin = margin;
783 		spaceRemaining -= thisMargin + margin;
784 		auto s = mixin("child." ~ relevantMeasure ~ "Stretchiness()");
785 		stretchinessSum += s;
786 		if(s > 0)
787 			stretchyChildSum++;
788 	}
789 
790 	// stretch to fill space
791 	while(spaceRemaining > 0 && stretchinessSum && stretchyChildSum) {
792 		//import std.stdio; writeln("str ", stretchinessSum);
793 		auto spacePerChild = spaceRemaining / stretchinessSum;
794 		bool spreadEvenly;
795 		bool giveToBiggest;
796 		if(spacePerChild <= 0) {
797 			spacePerChild = spaceRemaining / stretchyChildSum;
798 			spreadEvenly = true;
799 		}
800 		if(spacePerChild <= 0) {
801 			giveToBiggest = true;
802 		}
803 		int previousSpaceRemaining = spaceRemaining;
804 		stretchinessSum = 0;
805 		Widget mostStretchy;
806 		int mostStretchyS;
807 		foreach(child; parent.children) {
808 			if(cast(StaticPosition) child)
809 				continue;
810 			if(child.hidden)
811 				continue;
812 			static if(calcingV)
813 				auto maximum = child.maxHeight();
814 			else
815 				auto maximum = child.maxWidth();
816 
817 			if(mixin("child." ~ relevantMeasure) >= maximum) {
818 				auto adj = mixin("child." ~ relevantMeasure) - maximum;
819 				mixin("child." ~ relevantMeasure) -= adj;
820 				spaceRemaining += adj;
821 				continue;
822 			}
823 			auto s = mixin("child." ~ relevantMeasure ~ "Stretchiness()");
824 			if(s <= 0)
825 				continue;
826 			auto spaceAdjustment = spacePerChild * (spreadEvenly ? 1 : s);
827 			mixin("child." ~ relevantMeasure) += spaceAdjustment;
828 			spaceRemaining -= spaceAdjustment;
829 			if(mixin("child." ~ relevantMeasure) > maximum) {
830 				auto diff = mixin("child." ~ relevantMeasure) - maximum;
831 				mixin("child." ~ relevantMeasure) -= diff;
832 				spaceRemaining += diff;
833 			} else if(mixin("child." ~ relevantMeasure) < maximum) {
834 				stretchinessSum += mixin("child." ~ relevantMeasure ~ "Stretchiness()");
835 				if(mostStretchy is null || s >= mostStretchyS) {
836 					mostStretchy = child;
837 					mostStretchyS = s;
838 				}
839 			}
840 		}
841 
842 		if(giveToBiggest && mostStretchy !is null) {
843 			auto child = mostStretchy;
844 			int spaceAdjustment = spaceRemaining;
845 
846 			static if(calcingV)
847 				auto maximum = child.maxHeight();
848 			else
849 				auto maximum = child.maxWidth();
850 
851 			mixin("child." ~ relevantMeasure) += spaceAdjustment;
852 			spaceRemaining -= spaceAdjustment;
853 			if(mixin("child." ~ relevantMeasure) > maximum) {
854 				auto diff = mixin("child." ~ relevantMeasure) - maximum;
855 				mixin("child." ~ relevantMeasure) -= diff;
856 				spaceRemaining += diff;
857 			}
858 		}
859 
860 		if(spaceRemaining == previousSpaceRemaining)
861 			break; // apparently nothing more we can do
862 	}
863 
864 	// position
865 	lastMargin = 0;
866 	int currentPos = mixin("parent.padding"~firstThingy~"()");
867 	foreach(child; parent.children) {
868 		if(cast(StaticPosition) child) {
869 			child.recomputeChildLayout();
870 			continue;
871 		}
872 		if(child.hidden)
873 			continue;
874 		auto margin = mixin("child.margin" ~ secondThingy ~ "()");
875 		int thisMargin = mymax(lastMargin, mixin("child.margin"~firstThingy~"()"));
876 		currentPos += thisMargin;
877 		static if(calcingV) {
878 			child.x = parent.paddingLeft() + child.marginLeft();
879 			child.y = currentPos;
880 		} else {
881 			child.x = currentPos;
882 			child.y = parent.paddingTop() + child.marginTop();
883 
884 		}
885 		currentPos += mixin("child." ~ relevantMeasure);
886 		currentPos += margin;
887 		lastMargin = margin;
888 
889 		child.recomputeChildLayout();
890 	}
891 }
892 
893 int mymax(int a, int b) { return a > b ? a : b; }
894 
895 // OK so we need to make getting at the native window stuff possible in simpledisplay.d
896 // and here, it must be integrable with the layout, the event system, and not be painted over.
897 version(win32_widgets) {
898 	extern(Windows)
899 	private
900 	int HookedWndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam) nothrow {
901 		//import std.stdio; try { writeln(iMessage); } catch(Exception e) {};
902 		if(auto te = hWnd in Widget.nativeMapping) {
903 			try {
904 
905 				te.hookedWndProc(iMessage, wParam, lParam);
906 
907 				if(iMessage == WM_SETFOCUS) {
908 					auto lol = *te;
909 					while(lol !is null && lol.implicitlyCreated)
910 						lol = lol.parent;
911 					lol.focus();
912 					//(*te).parentWindow.focusedWidget = lol;
913 				}
914 
915 
916 
917 				if(iMessage == WM_CTLCOLORBTN || iMessage == WM_CTLCOLORSTATIC) {
918 					SetBkMode(cast(HDC) wParam, TRANSPARENT);
919 					return cast(typeof(return)) GetSysColorBrush(COLOR_3DFACE); // this is the window background color...
920 						//GetStockObject(NULL_BRUSH);
921 				}
922 
923 
924 				auto pos = getChildPositionRelativeToParentOrigin(*te);
925 				lastDefaultPrevented = false;
926 				// try {import std.stdio; writeln(typeid(*te)); } catch(Exception e) {}
927 				if(SimpleWindow.triggerEvents(hWnd, iMessage, wParam, lParam, pos[0], pos[1], (*te).parentWindow.win) || !lastDefaultPrevented)
928 					return cast(int) CallWindowProcW((*te).originalWindowProcedure, hWnd, iMessage, wParam, lParam);
929 				else {
930 					// it was something we recognized, should only call the window procedure if the default was not prevented
931 				}
932 			} catch(Exception e) {
933 				assert(0, e.toString());
934 			}
935 			return 0;
936 		}
937 		assert(0, "shouldn't be receiving messages for this window....");
938 		//import std.conv;
939 		//assert(0, to!string(hWnd) ~ " :: " ~ to!string(TextEdit.nativeMapping)); // not supposed to happen
940 	}
941 
942 	// className MUST be a string literal
943 	void createWin32Window(Widget p, const(wchar)[] className, string windowText, DWORD style, DWORD extStyle = 0) {
944 		assert(p.parentWindow !is null);
945 		assert(p.parentWindow.win.impl.hwnd !is null);
946 
947 		HWND phwnd;
948 		if(p.parent !is null && p.parent.hwnd !is null)
949 			phwnd = p.parent.hwnd;
950 		else
951 			phwnd = p.parentWindow.win.impl.hwnd;
952 
953 		assert(phwnd !is null);
954 
955 		WCharzBuffer wt = WCharzBuffer(windowText);
956 
957 		style |= WS_VISIBLE | WS_CHILD;
958 		p.hwnd = CreateWindowExW(extStyle, className.ptr, wt.ptr, style,
959 				CW_USEDEFAULT, CW_USEDEFAULT, 100, 100,
960 				phwnd, null, cast(HINSTANCE) GetModuleHandle(null), null);
961 
962 		assert(p.hwnd !is null);
963 
964 
965 		static HFONT font;
966 		if(font is null) {
967 			NONCLIENTMETRICS params;
968 			params.cbSize = params.sizeof;
969 			if(SystemParametersInfo(SPI_GETNONCLIENTMETRICS, params.sizeof, &params, 0)) {
970 				font = CreateFontIndirect(&params.lfMessageFont);
971 			}
972 		}
973 
974 		if(font)
975 			SendMessage(p.hwnd, WM_SETFONT, cast(uint) font, true);
976 
977 		Widget.nativeMapping[p.hwnd] = p;
978 
979 		p.originalWindowProcedure = cast(WNDPROC) SetWindowLong(p.hwnd, GWL_WNDPROC, cast(LONG) &HookedWndProc);
980 
981 		EnumChildWindows(p.hwnd, &childHandler, cast(LPARAM) cast(void*) p);
982 
983 		p.registerMovement();
984 	}
985 }
986 
987 version(win32_widgets)
988 private
989 extern(Windows) BOOL childHandler(HWND hwnd, LPARAM lparam) {
990 	if(hwnd is null || hwnd in Widget.nativeMapping)
991 		return true;
992 	auto parent = cast(Widget) cast(void*) lparam;
993 	Widget p = new Widget();
994 	p.parent = parent;
995 	p.parentWindow = parent.parentWindow;
996 	p.hwnd = hwnd;
997 	p.implicitlyCreated = true;
998 	Widget.nativeMapping[p.hwnd] = p;
999 	p.originalWindowProcedure = cast(WNDPROC) SetWindowLong(p.hwnd, GWL_WNDPROC, cast(LONG) &HookedWndProc);
1000 	return true;
1001 }
1002 
1003 /**
1004 	The way this module works is it builds on top of a SimpleWindow
1005 	from simpledisplay to provide simple controls and such.
1006 
1007 	Non-native controls suck, but nevertheless, I'm going to do it that
1008 	way to avoid dependencies on stuff like gtk on X... and since I'll
1009 	be writing the widgets there, I might as well just use them on Windows
1010 	too if you like, using `-version=custom_widgets`.
1011 
1012 	So, by extension, this sucks. But gtkd is just too big for me.
1013 
1014 
1015 	The goal is to look kinda like Windows 95, perhaps with customizability.
1016 	Nothing too fancy, just the basics that work.
1017 */
1018 class Widget {
1019 	mixin LayoutInfo!();
1020 
1021 	///
1022 	@scriptable
1023 	void removeWidget() {
1024 		auto p = this.parent;
1025 		if(p) {
1026 			int item;
1027 			for(item = 0; item < p.children.length; item++)
1028 				if(p.children[item] is this)
1029 					break;
1030 			for(; item < p.children.length - 1; item++)
1031 				p.children[item] = p.children[item + 1];
1032 			p.children = p.children[0 .. $-1];
1033 		}
1034 	}
1035 
1036 	@scriptable
1037 	Widget getChildByName(string name) {
1038 		return getByName(name);
1039 	}
1040 	///
1041 	final WidgetClass getByName(WidgetClass = Widget)(string name) {
1042 		if(this.name == name)
1043 			if(auto c = cast(WidgetClass) this)
1044 				return c;
1045 		foreach(child; children) {
1046 			auto w = child.getByName(name);
1047 			if(auto c = cast(WidgetClass) w)
1048 				return c;
1049 		}
1050 		return null;
1051 	}
1052 
1053 	@scriptable
1054 	string name; ///
1055 
1056 	private EventHandler[][string] bubblingEventHandlers;
1057 	private EventHandler[][string] capturingEventHandlers;
1058 
1059 	/++
1060 		Default event handlers. These are called on the appropriate
1061 		event unless [Event.preventDefault] is called on the event at
1062 		some point through the bubbling process.
1063 
1064 
1065 		If you are implementing your own widget and want to add custom
1066 		events, you should follow the same pattern here: create a virtual
1067 		function named `defaultEventHandler_eventname` with the implementation,
1068 		then, override [setupDefaultEventHandlers] and add a wrapped caller to
1069 		`defaultEventHandlers["eventname"]`. It should be wrapped like so:
1070 		`defaultEventHandlers["eventname"] = (Widget t, Event event) { t.defaultEventHandler_name(event); };`.
1071 		This ensures virtual dispatch based on the correct subclass.
1072 
1073 		Also, don't forget to call `super.setupDefaultEventHandlers();` too in your
1074 		overridden version.
1075 
1076 		You only need to do that on parent classes adding NEW event types. If you
1077 		just want to change the default behavior of an existing event type in a subclass,
1078 		you override the function (and optionally call `super.method_name`) like normal.
1079 
1080 	+/
1081 	protected EventHandler[string] defaultEventHandlers;
1082 
1083 	/// ditto
1084 	void setupDefaultEventHandlers() {
1085 		defaultEventHandlers["click"] = (Widget t, Event event) { t.defaultEventHandler_click(event); };
1086 		defaultEventHandlers["keydown"] = (Widget t, Event event) { t.defaultEventHandler_keydown(event); };
1087 		defaultEventHandlers["keyup"] = (Widget t, Event event) { t.defaultEventHandler_keyup(event); };
1088 		defaultEventHandlers["mouseover"] = (Widget t, Event event) { t.defaultEventHandler_mouseover(event); };
1089 		defaultEventHandlers["mouseout"] = (Widget t, Event event) { t.defaultEventHandler_mouseout(event); };
1090 		defaultEventHandlers["mousedown"] = (Widget t, Event event) { t.defaultEventHandler_mousedown(event); };
1091 		defaultEventHandlers["mouseup"] = (Widget t, Event event) { t.defaultEventHandler_mouseup(event); };
1092 		defaultEventHandlers["mouseenter"] = (Widget t, Event event) { t.defaultEventHandler_mouseenter(event); };
1093 		defaultEventHandlers["mouseleave"] = (Widget t, Event event) { t.defaultEventHandler_mouseleave(event); };
1094 		defaultEventHandlers["mousemove"] = (Widget t, Event event) { t.defaultEventHandler_mousemove(event); };
1095 		defaultEventHandlers["char"] = (Widget t, Event event) { t.defaultEventHandler_char(event); };
1096 		defaultEventHandlers["triggered"] = (Widget t, Event event) { t.defaultEventHandler_triggered(event); };
1097 		defaultEventHandlers["change"] = (Widget t, Event event) { t.defaultEventHandler_change(event); };
1098 		defaultEventHandlers["focus"] = (Widget t, Event event) { t.defaultEventHandler_focus(event); };
1099 		defaultEventHandlers["blur"] = (Widget t, Event event) { t.defaultEventHandler_blur(event); };
1100 	}
1101 
1102 	/// ditto
1103 	void defaultEventHandler_click(Event event) {}
1104 	/// ditto
1105 	void defaultEventHandler_keydown(Event event) {}
1106 	/// ditto
1107 	void defaultEventHandler_keyup(Event event) {}
1108 	/// ditto
1109 	void defaultEventHandler_mousedown(Event event) {}
1110 	/// ditto
1111 	void defaultEventHandler_mouseover(Event event) {}
1112 	/// ditto
1113 	void defaultEventHandler_mouseout(Event event) {}
1114 	/// ditto
1115 	void defaultEventHandler_mouseup(Event event) {}
1116 	/// ditto
1117 	void defaultEventHandler_mousemove(Event event) {}
1118 	/// ditto
1119 	void defaultEventHandler_mouseenter(Event event) {}
1120 	/// ditto
1121 	void defaultEventHandler_mouseleave(Event event) {}
1122 	/// ditto
1123 	void defaultEventHandler_char(Event event) {}
1124 	/// ditto
1125 	void defaultEventHandler_triggered(Event event) {}
1126 	/// ditto
1127 	void defaultEventHandler_change(Event event) {}
1128 	/// ditto
1129 	void defaultEventHandler_focus(Event event) {}
1130 	/// ditto
1131 	void defaultEventHandler_blur(Event event) {}
1132 
1133 	/++
1134 		Events use a Javascript-esque scheme.
1135 
1136 		[addEventListener] returns an opaque handle that you can later pass to [removeEventListener].
1137 	+/
1138 	EventListener addDirectEventListener(string event, void delegate() handler, bool useCapture = false) {
1139 		return addEventListener(event, (Widget, Event e) {
1140 			if(e.srcElement is this)
1141 				handler();
1142 		}, useCapture);
1143 	}
1144 
1145 	///
1146 	EventListener addDirectEventListener(string event, void delegate(Event) handler, bool useCapture = false) {
1147 		return addEventListener(event, (Widget, Event e) {
1148 			if(e.srcElement is this)
1149 				handler(e);
1150 		}, useCapture);
1151 	}
1152 
1153 
1154 	///
1155 	@scriptable
1156 	EventListener addEventListener(string event, void delegate() handler, bool useCapture = false) {
1157 		return addEventListener(event, (Widget, Event) { handler(); }, useCapture);
1158 	}
1159 
1160 	///
1161 	EventListener addEventListener(string event, void delegate(Event) handler, bool useCapture = false) {
1162 		return addEventListener(event, (Widget, Event e) { handler(e); }, useCapture);
1163 	}
1164 
1165 	///
1166 	EventListener addEventListener(string event, EventHandler handler, bool useCapture = false) {
1167 		if(event.length > 2 && event[0..2] == "on")
1168 			event = event[2 .. $];
1169 
1170 		if(useCapture)
1171 			capturingEventHandlers[event] ~= handler;
1172 		else
1173 			bubblingEventHandlers[event] ~= handler;
1174 
1175 		return EventListener(this, event, handler, useCapture);
1176 	}
1177 
1178 	///
1179 	void removeEventListener(string event, EventHandler handler, bool useCapture = false) {
1180 		if(event.length > 2 && event[0..2] == "on")
1181 			event = event[2 .. $];
1182 
1183 		if(useCapture) {
1184 			if(event in capturingEventHandlers)
1185 			foreach(ref evt; capturingEventHandlers[event])
1186 				if(evt is handler) evt = null;
1187 		} else {
1188 			if(event in bubblingEventHandlers)
1189 			foreach(ref evt; bubblingEventHandlers[event])
1190 				if(evt is handler) evt = null;
1191 		}
1192 	}
1193 
1194 	///
1195 	void removeEventListener(EventListener listener) {
1196 		removeEventListener(listener.event, listener.handler, listener.useCapture);
1197 	}
1198 
1199 	MouseCursor cursor() {
1200 		return GenericCursor.Default;
1201 	}
1202 
1203 	static if(UsingSimpledisplayX11) {
1204 		void discardXConnectionState() {
1205 			foreach(child; children)
1206 				child.discardXConnectionState();
1207 		}
1208 
1209 		void recreateXConnectionState() {
1210 			foreach(child; children)
1211 				child.recreateXConnectionState();
1212 			redraw();
1213 		}
1214 	}
1215 
1216 	///
1217 	Point globalCoordinates() {
1218 		int x = this.x;
1219 		int y = this.y;
1220 		auto p = this.parent;
1221 		while(p) {
1222 			x += p.x;
1223 			y += p.y;
1224 			p = p.parent;
1225 		}
1226 
1227 		static if(UsingSimpledisplayX11) {
1228 			auto dpy = XDisplayConnection.get;
1229 			arsd.simpledisplay.Window dummyw;
1230 			XTranslateCoordinates(dpy, this.parentWindow.win.impl.window, RootWindow(dpy, DefaultScreen(dpy)), x, y, &x, &y, &dummyw);
1231 		} else {
1232 			POINT pt;
1233 			pt.x = x;
1234 			pt.y = y;
1235 			MapWindowPoints(this.parentWindow.win.impl.hwnd, null, &pt, 1);
1236 			x = pt.x;
1237 			y = pt.y;
1238 		}
1239 
1240 		return Point(x, y);
1241 	}
1242 
1243 	version(win32_widgets)
1244 	void handleWmCommand(ushort cmd, ushort id) {}
1245 
1246 	version(win32_widgets)
1247 	int handleWmNotify(NMHDR* hdr, int code) { return 0; }
1248 
1249 	@scriptable
1250 	string statusTip;
1251 	// string toolTip;
1252 	// string helpText;
1253 
1254 	bool tabStop = true;
1255 	int tabOrder;
1256 
1257 	version(win32_widgets) {
1258 		static Widget[HWND] nativeMapping;
1259 		HWND hwnd;
1260 		WNDPROC originalWindowProcedure;
1261 
1262 		int hookedWndProc(UINT iMessage, WPARAM wParam, LPARAM lParam) {
1263 			return 0;
1264 		}
1265 	}
1266 	bool implicitlyCreated;
1267 
1268 	int x; // relative to the parent's origin
1269 	int y; // relative to the parent's origin
1270 	int width;
1271 	int height;
1272 	Widget[] children;
1273 	Widget parent;
1274 
1275 	protected
1276 	void registerMovement() {
1277 		version(win32_widgets) {
1278 			if(hwnd) {
1279 				auto pos = getChildPositionRelativeToParentHwnd(this);
1280 				MoveWindow(hwnd, pos[0], pos[1], width, height, true);
1281 			}
1282 		}
1283 	}
1284 
1285 	Window parentWindow;
1286 
1287 	///
1288 	this(Widget parent = null) {
1289 		if(parent !is null)
1290 			parent.addChild(this);
1291 		setupDefaultEventHandlers();
1292 	}
1293 
1294 	///
1295 	@scriptable
1296 	bool isFocused() {
1297 		return parentWindow && parentWindow.focusedWidget is this;
1298 	}
1299 
1300 	private bool showing_ = true;
1301 	bool showing() { return showing_; }
1302 	bool hidden() { return !showing_; }
1303 	void showing(bool s, bool recalculate = true) {
1304 		auto so = showing_;
1305 		showing_ = s;
1306 		if(s != so) {
1307 
1308 			version(win32_widgets)
1309 			if(hwnd)
1310 				ShowWindow(hwnd, s ? SW_SHOW : SW_HIDE);
1311 
1312 			if(parent && recalculate) {
1313 				parent.recomputeChildLayout();
1314 				parent.redraw();
1315 			}
1316 
1317 			foreach(child; children)
1318 				child.showing(s, false);
1319 		}
1320 	}
1321 	///
1322 	@scriptable
1323 	void show() {
1324 		showing = true;
1325 	}
1326 	///
1327 	@scriptable
1328 	void hide() {
1329 		showing = false;
1330 	}
1331 
1332 	///
1333 	@scriptable
1334 	void focus() {
1335 		assert(parentWindow !is null);
1336 		if(isFocused())
1337 			return;
1338 
1339 		if(parentWindow.focusedWidget) {
1340 			// FIXME: more details here? like from and to
1341 			auto evt = new Event("blur", parentWindow.focusedWidget);
1342 			parentWindow.focusedWidget = null;
1343 			evt.sendDirectly();
1344 		}
1345 
1346 
1347 		version(win32_widgets) {
1348 			if(this.hwnd !is null)
1349 				SetFocus(this.hwnd);
1350 		}
1351 
1352 		parentWindow.focusedWidget = this;
1353 		auto evt = new Event("focus", this);
1354 		evt.dispatch();
1355 	}
1356 
1357 
1358 	void attachedToWindow(Window w) {}
1359 	void addedTo(Widget w) {}
1360 
1361 	private void newWindow(Window parent) {
1362 		parentWindow = parent;
1363 		foreach(child; children)
1364 			child.newWindow(parent);
1365 	}
1366 
1367 	protected void addChild(Widget w, int position = int.max) {
1368 		w.parent = this;
1369 		if(position == int.max || position == children.length)
1370 			children ~= w;
1371 		else {
1372 			assert(position < children.length);
1373 			children.length = children.length + 1;
1374 			for(int i = cast(int) children.length - 1; i > position; i--)
1375 				children[i] = children[i - 1];
1376 			children[position] = w;
1377 		}
1378 
1379 		w.newWindow(this.parentWindow);
1380 
1381 		w.addedTo(this);
1382 
1383 		if(this.hidden)
1384 			w.showing = false;
1385 
1386 		if(parentWindow !is null) {
1387 			w.attachedToWindow(parentWindow);
1388 			parentWindow.recomputeChildLayout();
1389 			parentWindow.redraw();
1390 		}
1391 	}
1392 
1393 	Widget getChildAtPosition(int x, int y) {
1394 		// it goes backward so the last one to show gets picked first
1395 		// might use z-index later
1396 		foreach_reverse(child; children) {
1397 			if(child.hidden)
1398 				continue;
1399 			if(child.x <= x && child.y <= y
1400 				&& ((x - child.x) < child.width)
1401 				&& ((y - child.y) < child.height))
1402 			{
1403 				return child;
1404 			}
1405 		}
1406 
1407 		return null;
1408 	}
1409 
1410 	///
1411 	void paint(ScreenPainter painter) {}
1412 
1413 	/// I don't actually like the name of this
1414 	/// this draws a background on it
1415 	void erase(ScreenPainter painter) {
1416 		version(win32_widgets)
1417 			if(hwnd) return; // Windows will do it. I think.
1418 
1419 		auto c = backgroundColor;
1420 		painter.fillColor = c;
1421 		painter.outlineColor = c;
1422 
1423 		version(win32_widgets) {
1424 			HANDLE b, p;
1425 			if(c.a == 0) {
1426 				b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
1427 				p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
1428 			}
1429 		}
1430 		painter.drawRectangle(Point(0, 0), width, height);
1431 		version(win32_widgets) {
1432 			if(c.a == 0) {
1433 				SelectObject(painter.impl.hdc, p);
1434 				SelectObject(painter.impl.hdc, b);
1435 			}
1436 		}
1437 	}
1438 
1439 	///
1440 	Color backgroundColor() {
1441 		// the default is a "transparent" background, which means
1442 		// it goes as far up as it can to get the color
1443 		if(parent)
1444 			return parent.backgroundColor();
1445 		return Color.transparent;
1446 	}
1447 
1448 	///
1449 	ScreenPainter draw() {
1450 		int x = this.x, y = this.y;
1451 		auto parent = this.parent;
1452 		while(parent) {
1453 			x += parent.x;
1454 			y += parent.y;
1455 			parent = parent.parent;
1456 		}
1457 
1458 		auto painter = parentWindow.win.draw();
1459 		painter.originX = x;
1460 		painter.originY = y;
1461 		painter.setClipRectangle(Point(0, 0), width, height);
1462 		return painter;
1463 	}
1464 
1465 	protected void privatePaint(ScreenPainter painter, int lox, int loy, bool force = false) {
1466 		if(hidden)
1467 			return;
1468 
1469 		painter.originX = lox + x;
1470 		painter.originY = loy + y;
1471 
1472 		bool actuallyPainted = false;
1473 
1474 		if(redrawRequested || force) {
1475 			painter.setClipRectangle(Point(0, 0), width, height);
1476 
1477 			erase(painter);
1478 			paint(painter);
1479 
1480 			redrawRequested = false;
1481 			actuallyPainted = true;
1482 		}
1483 
1484 		foreach(child; children)
1485 			child.privatePaint(painter, painter.originX, painter.originY, actuallyPainted);
1486 	}
1487 
1488 	static class RedrawEvent {}
1489 	__gshared re = new RedrawEvent();
1490 
1491 	private bool redrawRequested;
1492 	///
1493 	final void redraw(string file = __FILE__, size_t line = __LINE__) {
1494 		redrawRequested = true;
1495 
1496 		if(this.parentWindow) {
1497 			auto sw = this.parentWindow.win;
1498 			assert(sw !is null);
1499 			if(!sw.eventQueued!RedrawEvent) {
1500 				sw.postEvent(re);
1501 				//import std.stdio; writeln("redraw requested from ", file,":",line," ", this.parentWindow.win.impl.window);
1502 			}
1503 		}
1504 	}
1505 
1506 	void actualRedraw() {
1507 		if(!showing) return;
1508 
1509 		assert(parentWindow !is null);
1510 
1511 		auto w = drawableWindow;
1512 		if(w is null)
1513 			w = parentWindow.win;
1514 
1515 		if(w.closed())
1516 			return;
1517 
1518 		auto ugh = this.parent;
1519 		int lox, loy;
1520 		while(ugh) {
1521 			lox += ugh.x;
1522 			loy += ugh.y;
1523 			ugh = ugh.parent;
1524 		}
1525 		auto painter = w.draw();
1526 		privatePaint(painter, lox, loy);
1527 	}
1528 
1529 	SimpleWindow drawableWindow;
1530 }
1531 
1532 /++
1533 	Nests an opengl capable window inside this window as a widget.
1534 
1535 	You may also just want to create an additional [SimpleWindow] with
1536 	[OpenGlOptions.yes] yourself.
1537 
1538 	An OpenGL widget cannot have child widgets. It will throw if you try.
1539 +/
1540 static if(OpenGlEnabled)
1541 class OpenGlWidget : Widget {
1542 	SimpleWindow win;
1543 
1544 	///
1545 	this(Widget parent) {
1546 		this.parentWindow = parent.parentWindow;
1547 		win = new SimpleWindow(640, 480, null, OpenGlOptions.yes, Resizability.automaticallyScaleIfPossible, WindowTypes.nestedChild, WindowFlags.normal, this.parentWindow.win);
1548 		super(parent);
1549 
1550 		version(win32_widgets) {
1551 			Widget.nativeMapping[win.hwnd] = this;
1552 			this.originalWindowProcedure = cast(WNDPROC) SetWindowLong(win.hwnd, GWL_WNDPROC, cast(LONG) &HookedWndProc);
1553 		} else {
1554 			win.setEventHandlers(
1555 				(MouseEvent e) {
1556 					Widget p = this;
1557 					while(p ! is parentWindow) {
1558 						e.x += p.x;
1559 						e.y += p.y;
1560 						p = p.parent;
1561 					}
1562 					parentWindow.dispatchMouseEvent(e);
1563 				},
1564 				(KeyEvent e) {
1565 					//import std.stdio;
1566 					//writefln("%x   %s", cast(uint) e.key, e.key);
1567 					parentWindow.dispatchKeyEvent(e);
1568 				},
1569 				(dchar e) {
1570 					parentWindow.dispatchCharEvent(e);
1571 				},
1572 			);
1573 		}
1574 	}
1575 
1576 	override void paint(ScreenPainter painter) {
1577 		win.redrawOpenGlSceneNow();
1578 	}
1579 
1580 	void redrawOpenGlScene(void delegate() dg) {
1581 		win.redrawOpenGlScene = dg;
1582 	}
1583 
1584 	override void showing(bool s, bool recalc) {
1585 		auto cur = hidden;
1586 		win.hidden = !s;
1587 		if(cur != s && s)
1588 			redraw();
1589 	}
1590 
1591 	/// OpenGL widgets cannot have child widgets. Do not call this.
1592 	/* @disable */ final override void addChild(Widget, int) {
1593 		throw new Error("cannot add children to OpenGL widgets");
1594 	}
1595 
1596 	/// When an opengl widget is laid out, it will adjust the glViewport for you automatically.
1597 	/// Keep in mind that events like mouse coordinates are still relative to your size.
1598 	override void registerMovement() {
1599 		//import std.stdio; writefln("%d %d %d %d", x,y,width,height);
1600 		version(win32_widgets)
1601 			auto pos = getChildPositionRelativeToParentHwnd(this);
1602 		else
1603 			auto pos = getChildPositionRelativeToParentOrigin(this);
1604 		win.moveResize(pos[0], pos[1], width, height);
1605 	}
1606 
1607 	//void delegate() drawFrame;
1608 }
1609 
1610 /++
1611 
1612 +/
1613 version(custom_widgets)
1614 class ListWidget : ScrollableWidget {
1615 
1616 	static struct Option {
1617 		string label;
1618 		bool selected;
1619 	}
1620 
1621 	void setSelection(int y) {
1622 		if(!multiSelect)
1623 			foreach(ref opt; options)
1624 				opt.selected = false;
1625 		if(y >= 0 && y < options.length)
1626 			options[y].selected = !options[y].selected;
1627 
1628 		auto evt = new Event(EventType.change, this);
1629 		evt.dispatch();
1630 
1631 		redraw();
1632 
1633 	}
1634 
1635 	override void defaultEventHandler_click(Event event) {
1636 		this.focus();
1637 		auto y = (event.clientY - 4) / Window.lineHeight;
1638 		if(y >= 0 && y < options.length) {
1639 			setSelection(y);
1640 		}
1641 		super.defaultEventHandler_click(event);
1642 	}
1643 
1644 	this(Widget parent = null) {
1645 		super(parent);
1646 	}
1647 
1648 	override void paint(ScreenPainter painter) {
1649 		draw3dFrame(this, painter, FrameStyle.sunk, Color.white);
1650 
1651 		auto pos = Point(4, 4);
1652 		foreach(idx, option; options) {
1653 			painter.fillColor = Color.white;
1654 			painter.outlineColor = Color.white;
1655 			painter.drawRectangle(pos, width - 8, Window.lineHeight);
1656 			painter.outlineColor = Color.black;
1657 			painter.drawText(pos, option.label);
1658 			if(option.selected) {
1659 				painter.rasterOp = RasterOp.xor;
1660 				painter.outlineColor = Color.white;
1661 				painter.fillColor = Color(255, 255, 0);
1662 				painter.drawRectangle(pos, width - 8, Window.lineHeight);
1663 				painter.rasterOp = RasterOp.normal;
1664 			}
1665 			pos.y += Window.lineHeight;
1666 		}
1667 	}
1668 
1669 
1670 	void addOption(string text) {
1671 		options ~= Option(text);
1672 		setContentSize(width, cast(int) (options.length * Window.lineHeight));
1673 		redraw();
1674 	}
1675 
1676 	void clear() {
1677 		options = null;
1678 		redraw();
1679 	}
1680 
1681 	Option[] options;
1682 	bool multiSelect;
1683 
1684 	override int heightStretchiness() { return 6; }
1685 }
1686 
1687 
1688 
1689 /// For [ScrollableWidget], determines when to show the scroll bar to the user.
1690 enum ScrollBarShowPolicy {
1691 	automatic, /// automatically show the scroll bar if it is necessary
1692 	never, /// never show the scroll bar (scrolling must be done programmatically)
1693 	always /// always show the scroll bar, even if it is disabled
1694 }
1695 
1696 /++
1697 FIXME ScrollBarShowPolicy
1698 +/
1699 class ScrollableWidget : Widget {
1700 	// FIXME: make line size configurable
1701 	// FIXME: add keyboard controls
1702 	version(win32_widgets) {
1703 		override int hookedWndProc(UINT msg, WPARAM wParam, LPARAM lParam) {
1704 			if(msg == WM_VSCROLL || msg == WM_HSCROLL) {
1705 				auto pos = HIWORD(wParam);
1706 				auto m = LOWORD(wParam);
1707 
1708 				// FIXME: I can reintroduce the
1709 				// scroll bars now by using this
1710 				// in the top-level window handler
1711 				// to forward comamnds
1712 				auto scrollbarHwnd = lParam;
1713 				switch(m) {
1714 					case SB_BOTTOM:
1715 						if(msg == WM_HSCROLL)
1716 							horizontalScrollTo(contentWidth_);
1717 						else
1718 							verticalScrollTo(contentHeight_);
1719 					break;
1720 					case SB_TOP:
1721 						if(msg == WM_HSCROLL)
1722 							horizontalScrollTo(0);
1723 						else
1724 							verticalScrollTo(0);
1725 					break;
1726 					case SB_ENDSCROLL:
1727 						// idk
1728 					break;
1729 					case SB_LINEDOWN:
1730 						if(msg == WM_HSCROLL)
1731 							horizontalScroll(16);
1732 						else
1733 							verticalScroll(16);
1734 					break;
1735 					case SB_LINEUP:
1736 						if(msg == WM_HSCROLL)
1737 							horizontalScroll(-16);
1738 						else
1739 							verticalScroll(-16);
1740 					break;
1741 					case SB_PAGEDOWN:
1742 						if(msg == WM_HSCROLL)
1743 							horizontalScroll(100);
1744 						else
1745 							verticalScroll(100);
1746 					break;
1747 					case SB_PAGEUP:
1748 						if(msg == WM_HSCROLL)
1749 							horizontalScroll(-100);
1750 						else
1751 							verticalScroll(-100);
1752 					break;
1753 					case SB_THUMBPOSITION:
1754 					case SB_THUMBTRACK:
1755 						if(msg == WM_HSCROLL)
1756 							horizontalScrollTo(pos);
1757 						else
1758 							verticalScrollTo(pos);
1759 
1760 						if(m == SB_THUMBTRACK) {
1761 							// the event loop doesn't seem to carry on with a requested redraw..
1762 							// so we request it to get our dirty bit set...
1763 							redraw();
1764 							// then we need to immediately actually redraw it too for instant feedback to user
1765 							actualRedraw();
1766 						}
1767 					break;
1768 					default:
1769 				}
1770 			}
1771 			return 0;
1772 		}
1773 	}
1774 	///
1775 	this(Widget parent) {
1776 		this.parentWindow = parent.parentWindow;
1777 
1778 		version(win32_widgets) {
1779 			static bool classRegistered = false;
1780 			if(!classRegistered) {
1781 				HINSTANCE hInstance = cast(HINSTANCE) GetModuleHandle(null);
1782 				WNDCLASSEX wc;
1783 				wc.cbSize = wc.sizeof;
1784 				wc.hInstance = hInstance;
1785 				wc.lpfnWndProc = &DefWindowProc;
1786 				wc.lpszClassName = "arsd_minigui_ScrollableWidget"w.ptr;
1787 				if(!RegisterClassExW(&wc))
1788 					throw new Exception("RegisterClass ");// ~ to!string(GetLastError()));
1789 				classRegistered = true;
1790 			}
1791 
1792 			createWin32Window(this, "arsd_minigui_ScrollableWidget"w, "", 
1793 				0|WS_CHILD|WS_VISIBLE|WS_HSCROLL|WS_VSCROLL, 0);
1794 		} else version(custom_widgets) {
1795 			horizontalScrollbarHolder = new FixedPosition(this);
1796 			verticalScrollbarHolder = new FixedPosition(this);
1797 			horizontalScrollBar = new HorizontalScrollbar(horizontalScrollbarHolder);
1798 			verticalScrollBar = new VerticalScrollbar(verticalScrollbarHolder);
1799 
1800 			horizontalScrollbarHolder.showing_ = false;
1801 			verticalScrollbarHolder.showing_ = false;
1802 
1803 			horizontalScrollBar.addEventListener(EventType.change, () {
1804 				horizontalScrollTo(horizontalScrollBar.position);
1805 			});
1806 			verticalScrollBar.addEventListener(EventType.change, () {
1807 				verticalScrollTo(verticalScrollBar.position);
1808 			});
1809 		} else static assert(0);
1810 
1811 		super(parent);
1812 	}
1813 
1814 	override void defaultEventHandler_click(Event event) {
1815 		if(event.button == MouseButton.wheelUp)
1816 			verticalScroll(-16);
1817 		if(event.button == MouseButton.wheelDown)
1818 			verticalScroll(16);
1819 		super.defaultEventHandler_click(event);
1820 	}
1821 
1822 	override void defaultEventHandler_keydown(Event event) {
1823 		switch(event.key) {
1824 			case Key.Left:
1825 				horizontalScroll(-16);
1826 			break;
1827 			case Key.Right:
1828 				horizontalScroll(16);
1829 			break;
1830 			case Key.Up:
1831 				verticalScroll(-16);
1832 			break;
1833 			case Key.Down:
1834 				verticalScroll(16);
1835 			break;
1836 			case Key.Home:
1837 				verticalScrollTo(0);
1838 			break;
1839 			case Key.End:
1840 				verticalScrollTo(contentHeight);
1841 			break;
1842 			case Key.PageUp:
1843 				verticalScroll(-160);
1844 			break;
1845 			case Key.PageDown:
1846 				verticalScroll(160);
1847 			break;
1848 			default:
1849 		}
1850 		super.defaultEventHandler_keydown(event);
1851 	}
1852 
1853 	version(custom_widgets) {
1854 		FixedPosition horizontalScrollbarHolder;
1855 		FixedPosition verticalScrollbarHolder;
1856 
1857 		VerticalScrollbar verticalScrollBar;
1858 		HorizontalScrollbar horizontalScrollBar;
1859 	}
1860 
1861 	version(custom_widgets)
1862 	override void recomputeChildLayout() {
1863 		bool both = showingVerticalScroll && showingHorizontalScroll;
1864 		if(horizontalScrollbarHolder && verticalScrollbarHolder) {
1865 			horizontalScrollbarHolder.width = this.width - (both ? verticalScrollBar.minWidth() : 0);
1866 			horizontalScrollbarHolder.height = horizontalScrollBar.minHeight();
1867 			horizontalScrollbarHolder.x = 0;
1868 			horizontalScrollbarHolder.y = this.height - horizontalScrollBar.minHeight();
1869 
1870 			verticalScrollbarHolder.width = verticalScrollBar.minWidth();
1871 			verticalScrollbarHolder.height = this.height - (both ? horizontalScrollBar.minHeight() : 0);
1872 			verticalScrollbarHolder.x = this.width - verticalScrollBar.minWidth();
1873 			verticalScrollbarHolder.y = 0;
1874 
1875 			if(contentWidth_ <= this.width)
1876 				scrollOrigin_.x = 0;
1877 			if(contentHeight_ <= this.height)
1878 				scrollOrigin_.y = 0;
1879 		}
1880 
1881 
1882 		super.recomputeChildLayout();
1883 
1884 		if(contentWidth_ <= this.width)
1885 			scrollOrigin_.x = 0;
1886 		if(contentHeight_ <= this.height)
1887 			scrollOrigin_.y = 0;
1888 
1889 		if(showingHorizontalScroll())
1890 			horizontalScrollbarHolder.showing = true;
1891 		else
1892 			horizontalScrollbarHolder.showing = false;
1893 		if(showingVerticalScroll())
1894 			verticalScrollbarHolder.showing = true;
1895 		else
1896 			verticalScrollbarHolder.showing = false;
1897 
1898 
1899 		verticalScrollBar.setViewableArea(this.viewportHeight());
1900 		verticalScrollBar.setMax(contentHeight);
1901 		verticalScrollBar.setPosition(this.scrollOrigin.y);
1902 
1903 		horizontalScrollBar.setViewableArea(this.viewportWidth());
1904 		horizontalScrollBar.setMax(contentWidth);
1905 		horizontalScrollBar.setPosition(this.scrollOrigin.x);
1906 
1907 
1908 	} else version(win32_widgets)
1909 	override void recomputeChildLayout() {
1910 		super.recomputeChildLayout();
1911 		SCROLLINFO info;
1912 		info.cbSize = info.sizeof;
1913 		info.nPage = viewportHeight;
1914 		info.fMask = SIF_PAGE | SIF_RANGE;
1915 		info.nMin = 0;
1916 		info.nMax = contentHeight_;
1917 		SetScrollInfo(hwnd, SB_VERT, &info, true);
1918 
1919 		info.cbSize = info.sizeof;
1920 		info.nPage = viewportWidth;
1921 		info.fMask = SIF_PAGE | SIF_RANGE;
1922 		info.nMin = 0;
1923 		info.nMax = contentWidth_;
1924 		SetScrollInfo(hwnd, SB_HORZ, &info, true);
1925 	} else static assert(0);
1926 
1927 
1928 
1929 	/*
1930 		Scrolling
1931 		------------
1932 
1933 		You are assigned a width and a height by the layout engine, which
1934 		is your viewport box. However, you may draw more than that by setting
1935 		a contentWidth and contentHeight.
1936 
1937 		If these can be contained by the viewport, no scrollbar is displayed.
1938 		If they cannot fit though, it will automatically show scroll as necessary.
1939 
1940 		If contentWidth == 0, no horizontal scrolling is performed. If contentHeight
1941 		is zero, no vertical scrolling is performed.
1942 
1943 		If scrolling is necessary, the lib will automatically work with the bars.
1944 		When you redraw, the origin and clipping info in the painter is set so if
1945 		you just draw everything, it will work, but you can be more efficient by checking
1946 		the viewportWidth, viewportHeight, and scrollOrigin members.
1947 	*/
1948 
1949 	///
1950 	final @property int viewportWidth() {
1951 		return width - (showingVerticalScroll ? 16 : 0);
1952 	}
1953 	///
1954 	final @property int viewportHeight() {
1955 		return height - (showingHorizontalScroll ? 16 : 0);
1956 	}
1957 
1958 	// FIXME property
1959 	Point scrollOrigin_;
1960 
1961 	///
1962 	final const(Point) scrollOrigin() {
1963 		return scrollOrigin_;
1964 	}
1965 
1966 	// the user sets these two
1967 	private int contentWidth_ = 0;
1968 	private int contentHeight_ = 0;
1969 
1970 	///
1971 	int contentWidth() { return contentWidth_; }
1972 	///
1973 	int contentHeight() { return contentHeight_; }
1974 
1975 	///
1976 	void setContentSize(int width, int height) {
1977 		contentWidth_ = width;
1978 		contentHeight_ = height;
1979 
1980 		version(custom_widgets) {
1981 			if(showingVerticalScroll || showingHorizontalScroll) {
1982 				recomputeChildLayout();
1983 			}
1984 
1985 			if(showingVerticalScroll())
1986 				verticalScrollBar.redraw();
1987 			if(showingHorizontalScroll())
1988 				horizontalScrollBar.redraw();
1989 		} else version(win32_widgets) {
1990 			recomputeChildLayout();
1991 		} else static assert(0);
1992 
1993 	}
1994 
1995 	///
1996 	void verticalScroll(int delta) {
1997 		verticalScrollTo(scrollOrigin.y + delta);
1998 	}
1999 	///
2000 	void verticalScrollTo(int pos) {
2001 		scrollOrigin_.y = pos;
2002 		if(scrollOrigin_.y + viewportHeight > contentHeight)
2003 			scrollOrigin_.y = contentHeight - viewportHeight;
2004 
2005 		if(scrollOrigin_.y < 0)
2006 			scrollOrigin_.y = 0;
2007 
2008 		version(win32_widgets) {
2009 			SCROLLINFO info;
2010 			info.cbSize = info.sizeof;
2011 			info.fMask = SIF_POS;
2012 			info.nPos = scrollOrigin_.y;
2013 			SetScrollInfo(hwnd, SB_VERT, &info, true);
2014 		} else version(custom_widgets) {
2015 			verticalScrollBar.setPosition(scrollOrigin_.y);
2016 		} else static assert(0);
2017 
2018 		redraw();
2019 	}
2020 
2021 	///
2022 	void horizontalScroll(int delta) {
2023 		horizontalScrollTo(scrollOrigin.x + delta);
2024 	}
2025 	///
2026 	void horizontalScrollTo(int pos) {
2027 		scrollOrigin_.x = pos;
2028 		if(scrollOrigin_.x + viewportWidth > contentWidth)
2029 			scrollOrigin_.x = contentWidth - viewportWidth;
2030 
2031 		if(scrollOrigin_.x < 0)
2032 			scrollOrigin_.x = 0;
2033 
2034 		version(win32_widgets) {
2035 			SCROLLINFO info;
2036 			info.cbSize = info.sizeof;
2037 			info.fMask = SIF_POS;
2038 			info.nPos = scrollOrigin_.x;
2039 			SetScrollInfo(hwnd, SB_HORZ, &info, true);
2040 		} else version(custom_widgets) {
2041 			horizontalScrollBar.setPosition(scrollOrigin_.x);
2042 		} else static assert(0);
2043 
2044 		redraw();
2045 	}
2046 	///
2047 	void scrollTo(Point p) {
2048 		verticalScrollTo(p.y);
2049 		horizontalScrollTo(p.x);
2050 	}
2051 
2052 	///
2053 	void ensureVisibleInScroll(Point p) {
2054 		auto rect = viewportRectangle();
2055 		if(rect.contains(p))
2056 			return;
2057 		if(p.x < rect.left)
2058 			horizontalScroll(p.x - rect.left);
2059 		else if(p.x > rect.right)
2060 			horizontalScroll(p.x - rect.right);
2061 
2062 		if(p.y < rect.top)
2063 			verticalScroll(p.y - rect.top);
2064 		else if(p.y > rect.bottom)
2065 			verticalScroll(p.y - rect.bottom);
2066 	}
2067 
2068 	///
2069 	void ensureVisibleInScroll(Rectangle rect) {
2070 		ensureVisibleInScroll(rect.upperLeft);
2071 		ensureVisibleInScroll(rect.lowerRight);
2072 	}
2073 
2074 	///
2075 	Rectangle viewportRectangle() {
2076 		return Rectangle(scrollOrigin, Size(viewportWidth, viewportHeight));
2077 	}
2078 
2079 	///
2080 	bool showingHorizontalScroll() {
2081 		return contentWidth > width;
2082 	}
2083 	///
2084 	bool showingVerticalScroll() {
2085 		return contentHeight > height;
2086 	}
2087 
2088 	/// This is called before the ordinary paint delegate,
2089 	/// giving you a chance to draw the window frame, etc,
2090 	/// before the scroll clip takes effect
2091 	void paintFrameAndBackground(ScreenPainter painter) {
2092 		version(win32_widgets) {
2093 			auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
2094 			auto p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
2095 			// since the pen is null, to fill the whole space, we need the +1 on both.
2096 			gdi.Rectangle(painter.impl.hdc, 0, 0, this.width + 1, this.height + 1);
2097 			SelectObject(painter.impl.hdc, p);
2098 			SelectObject(painter.impl.hdc, b);
2099 		}
2100 
2101 	}
2102 
2103 	// make space for the scroll bar, and that's it.
2104 	final override int paddingRight() { return 16; }
2105 	final override int paddingBottom() { return 16; }
2106 
2107 	/*
2108 		END SCROLLING
2109 	*/
2110 
2111 	override ScreenPainter draw() {
2112 		int x = this.x, y = this.y;
2113 		auto parent = this.parent;
2114 		while(parent) {
2115 			x += parent.x;
2116 			y += parent.y;
2117 			parent = parent.parent;
2118 		}
2119 
2120 		auto painter = parentWindow.win.draw();
2121 		painter.originX = x;
2122 		painter.originY = y;
2123 
2124 		painter.originX = painter.originX - scrollOrigin.x;
2125 		painter.originY = painter.originY - scrollOrigin.y;
2126 		painter.setClipRectangle(scrollOrigin, viewportWidth(), viewportHeight());
2127 
2128 		return painter;
2129 	}
2130 
2131 	override protected void privatePaint(ScreenPainter painter, int lox, int loy, bool force = false) {
2132 		if(hidden)
2133 			return;
2134 		painter.originX = lox + x;
2135 		painter.originY = loy + y;
2136 
2137 		bool actuallyPainted = false;
2138 
2139 		if(force || redrawRequested) {
2140 			painter.setClipRectangle(Point(0, 0), width, height);
2141 			paintFrameAndBackground(painter);
2142 		}
2143 
2144 		painter.originX = painter.originX - scrollOrigin.x;
2145 		painter.originY = painter.originY - scrollOrigin.y;
2146 		if(force || redrawRequested) {
2147 			painter.setClipRectangle(scrollOrigin, viewportWidth(), viewportHeight());
2148 
2149 			//erase(painter); // we paintFrameAndBackground above so no need
2150 			paint(painter);
2151 
2152 			actuallyPainted = true;
2153 			redrawRequested = false;
2154 		}
2155 		foreach(child; children) {
2156 			if(cast(FixedPosition) child)
2157 				child.privatePaint(painter, painter.originX + scrollOrigin.x, painter.originY + scrollOrigin.y, actuallyPainted);
2158 			else
2159 				child.privatePaint(painter, painter.originX, painter.originY, actuallyPainted);
2160 		}
2161 	}
2162 }
2163 
2164 ///
2165 abstract class ScrollbarBase : Widget {
2166 	///
2167 	this(Widget parent) {
2168 		super(parent);
2169 		tabStop = false;
2170 	}
2171 
2172 	private int viewableArea_;
2173 	private int max_;
2174 	private int step_ = 16;
2175 	private int position_;
2176 
2177 	///
2178 	void setViewableArea(int a) {
2179 		viewableArea_ = a;
2180 	}
2181 	///
2182 	void setMax(int a) {
2183 		max_ = a;
2184 	}
2185 	///
2186 	int max() {
2187 		return max_;
2188 	}
2189 	///
2190 	void setPosition(int a) {
2191 		position_ = max ? a : 0;
2192 	}
2193 	///
2194 	int position() {
2195 		return position_;
2196 	}
2197 	///
2198 	void setStep(int a) {
2199 		step_ = a;
2200 	}
2201 	///
2202 	int step() {
2203 		return step_;
2204 	}
2205 
2206 	protected void informProgramThatUserChangedPosition(int n) {
2207 		position_ = n;
2208 		auto evt = new Event(EventType.change, this);
2209 		evt.intValue = n;
2210 		evt.dispatch();
2211 	}
2212 
2213 	version(custom_widgets) {
2214 		abstract protected int getBarDim();
2215 		int thumbSize() {
2216 			int res;
2217 			if(max_) {
2218 				res = getBarDim() * viewableArea_ / max_;
2219 			}
2220 			if(res < 6)
2221 				res = 6;
2222 
2223 			return res;
2224 		}
2225 
2226 		int thumbPosition() {
2227 			if(max_) {
2228 				auto res = position_ * viewableArea_ / max_;
2229 				if(res + thumbSize() > getBarDim())
2230 					res = getBarDim() - thumbSize();
2231 				if(res < 0)
2232 					res = 0;
2233 				return res;
2234 			}
2235 			return 0;
2236 		}
2237 	}
2238 }
2239 
2240 //public import mgt;
2241 
2242 /++
2243 	A mouse tracking widget is one that follows the mouse when dragged inside it.
2244 
2245 	Concrete subclasses may include a scrollbar thumb and a volume control.
2246 +/
2247 //version(custom_widgets)
2248 class MouseTrackingWidget : Widget {
2249 
2250 	///
2251 	int positionX() { return positionX_; }
2252 	///
2253 	int positionY() { return positionY_; }
2254 
2255 	///
2256 	void positionX(int p) { positionX_ = p; }
2257 	///
2258 	void positionY(int p) { positionY_ = p; }
2259 
2260 	private int positionX_;
2261 	private int positionY_;
2262 
2263 	///
2264 	enum Orientation {
2265 		horizontal, ///
2266 		vertical, ///
2267 		twoDimensional, ///
2268 	}
2269 
2270 	private int thumbWidth_;
2271 	private int thumbHeight_;
2272 
2273 	///
2274 	int thumbWidth() { return thumbWidth_; }
2275 	///
2276 	int thumbHeight() { return thumbHeight_; }
2277 	///
2278 	int thumbWidth(int a) { return thumbWidth_ = a; }
2279 	///
2280 	int thumbHeight(int a) { return thumbHeight_ = a; }
2281 
2282 	private bool dragging;
2283 	private bool hovering;
2284 	private int startMouseX, startMouseY;
2285 
2286 	///
2287 	this(Orientation orientation, Widget parent = null) {
2288 		super(parent);
2289 
2290 		//assert(parentWindow !is null);
2291 
2292 		addEventListener(EventType.mousedown, (Event event) {
2293 			if(event.clientX >= positionX && event.clientX < positionX + thumbWidth && event.clientY >= positionY && event.clientY < positionY + thumbHeight) {
2294 				dragging = true;
2295 				startMouseX = event.clientX - positionX;
2296 				startMouseY = event.clientY - positionY;
2297 				parentWindow.captureMouse(this);
2298 			} else {
2299 				if(orientation == Orientation.horizontal || orientation == Orientation.twoDimensional)
2300 					positionX = event.clientX - thumbWidth / 2;
2301 				if(orientation == Orientation.vertical || orientation == Orientation.twoDimensional)
2302 					positionY = event.clientY - thumbHeight / 2;
2303 
2304 				if(positionX + thumbWidth > this.width)
2305 					positionX = this.width - thumbWidth;
2306 				if(positionY + thumbHeight > this.height)
2307 					positionY = this.height - thumbHeight;
2308 
2309 				if(positionX < 0)
2310 					positionX = 0;
2311 				if(positionY < 0)
2312 					positionY = 0;
2313 
2314 
2315 				auto evt = new Event(EventType.change, this);
2316 				evt.sendDirectly();
2317 
2318 				redraw();
2319 
2320 			}
2321 		});
2322 
2323 		addEventListener(EventType.mouseup, (Event event) {
2324 			dragging = false;
2325 			parentWindow.releaseMouseCapture();
2326 		});
2327 
2328 		addEventListener(EventType.mouseout, (Event event) {
2329 			if(!hovering)
2330 				return;
2331 			hovering = false;
2332 			redraw();
2333 		});
2334 
2335 		addEventListener(EventType.mousemove, (Event event) {
2336 			auto oh = hovering;
2337 			if(event.clientX >= positionX && event.clientX < positionX + thumbWidth && event.clientY >= positionY && event.clientY < positionY + thumbHeight) {
2338 				hovering = true;
2339 			} else {
2340 				hovering = false;
2341 			}
2342 			if(!dragging) {
2343 				if(hovering != oh)
2344 					redraw();
2345 				return;
2346 			}
2347 
2348 			if(orientation == Orientation.horizontal || orientation == Orientation.twoDimensional)
2349 				positionX = event.clientX - startMouseX; // FIXME: click could be in the middle of it
2350 			if(orientation == Orientation.vertical || orientation == Orientation.twoDimensional)
2351 				positionY = event.clientY - startMouseY;
2352 
2353 			if(positionX + thumbWidth > this.width)
2354 				positionX = this.width - thumbWidth;
2355 			if(positionY + thumbHeight > this.height)
2356 				positionY = this.height - thumbHeight;
2357 
2358 			if(positionX < 0)
2359 				positionX = 0;
2360 			if(positionY < 0)
2361 				positionY = 0;
2362 
2363 			auto evt = new Event(EventType.change, this);
2364 			evt.sendDirectly();
2365 
2366 			redraw();
2367 		});
2368 	}
2369 
2370 	version(custom_widgets)
2371 	override void paint(ScreenPainter painter) {
2372 		auto c = lighten(windowBackgroundColor, 0.2);
2373 		painter.outlineColor = c;
2374 		painter.fillColor = c;
2375 		painter.drawRectangle(Point(0, 0), this.width, this.height);
2376 
2377 		auto color = hovering ? Color(215, 215, 215) : windowBackgroundColor;
2378 		draw3dFrame(positionX, positionY, thumbWidth, thumbHeight, painter, FrameStyle.risen, color);
2379 	}
2380 }
2381 
2382 version(custom_widgets)
2383 private
2384 class HorizontalScrollbar : ScrollbarBase {
2385 
2386 	version(custom_widgets) {
2387 		private MouseTrackingWidget thumb;
2388 
2389 		override int getBarDim() {
2390 			return thumb.width;
2391 		}
2392 	}
2393 
2394 	override void setViewableArea(int a) {
2395 		super.setViewableArea(a);
2396 
2397 		version(win32_widgets) {
2398 			SCROLLINFO info;
2399 			info.cbSize = info.sizeof;
2400 			info.nPage = a;
2401 			info.fMask = SIF_PAGE;
2402 			SetScrollInfo(hwnd, SB_CTL, &info, true);
2403 		} else version(custom_widgets) {
2404 			// intentionally blank
2405 		} else static assert(0);
2406 
2407 	}
2408 
2409 	override void setMax(int a) {
2410 		super.setMax(a);
2411 		version(win32_widgets) {
2412 			SCROLLINFO info;
2413 			info.cbSize = info.sizeof;
2414 			info.nMin = 0;
2415 			info.nMax = max;
2416 			info.fMask = SIF_RANGE;
2417 			SetScrollInfo(hwnd, SB_CTL, &info, true);
2418 		}
2419 	}
2420 
2421 	override void setPosition(int a) {
2422 		super.setPosition(a);
2423 		version(win32_widgets) {
2424 			SCROLLINFO info;
2425 			info.cbSize = info.sizeof;
2426 			info.fMask = SIF_POS;
2427 			info.nPos = position;
2428 			SetScrollInfo(hwnd, SB_CTL, &info, true);
2429 		} else version(custom_widgets) {
2430 			thumb.positionX = thumbPosition();
2431 			thumb.thumbWidth = thumbSize;
2432 			thumb.redraw();
2433 		} else static assert(0);
2434 	}
2435 
2436 	this(Widget parent) {
2437 		super(parent);
2438 
2439 		version(win32_widgets) {
2440 			createWin32Window(this, "Scrollbar"w, "", 
2441 				0|WS_CHILD|WS_VISIBLE|SBS_HORZ|SBS_BOTTOMALIGN, 0);
2442 		} else version(custom_widgets) {
2443 			auto vl = new HorizontalLayout(this);
2444 			auto leftButton = new ArrowButton(ArrowDirection.left, vl);
2445 			thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.horizontal, vl);
2446 			auto rightButton = new ArrowButton(ArrowDirection.right, vl);
2447 
2448 			leftButton.addEventListener(EventType.triggered, () {
2449 				informProgramThatUserChangedPosition(position - step());
2450 			});
2451 			rightButton.addEventListener(EventType.triggered, () {
2452 				informProgramThatUserChangedPosition(position + step());
2453 			});
2454 
2455 			thumb.thumbWidth = this.minWidth;
2456 			thumb.thumbHeight = 16;
2457 
2458 			thumb.addEventListener(EventType.change, () {
2459 				auto sx = thumb.positionX * max() / thumb.width;
2460 				informProgramThatUserChangedPosition(sx);
2461 			});
2462 		}
2463 	}
2464 
2465 	override int minHeight() { return 16; }
2466 	override int maxHeight() { return 16; }
2467 	override int minWidth() { return 48; }
2468 }
2469 
2470 version(custom_widgets)
2471 private
2472 class VerticalScrollbar : ScrollbarBase {
2473 
2474 	version(custom_widgets) {
2475 		override int getBarDim() {
2476 			return thumb.height;
2477 		}
2478 
2479 		private MouseTrackingWidget thumb;
2480 	}
2481 
2482 	override void setViewableArea(int a) {
2483 		super.setViewableArea(a);
2484 
2485 		version(win32_widgets) {
2486 			SCROLLINFO info;
2487 			info.cbSize = info.sizeof;
2488 			info.nPage = a;
2489 			info.fMask = SIF_PAGE;
2490 			SetScrollInfo(hwnd, SB_CTL, &info, true);
2491 		} else version(custom_widgets) {
2492 			// intentionally blank
2493 		} else static assert(0);
2494 
2495 	}
2496 
2497 	override void setMax(int a) {
2498 		super.setMax(a);
2499 		version(win32_widgets) {
2500 			SCROLLINFO info;
2501 			info.cbSize = info.sizeof;
2502 			info.nMin = 0;
2503 			info.nMax = max;
2504 			info.fMask = SIF_RANGE;
2505 			SetScrollInfo(hwnd, SB_CTL, &info, true);
2506 		}
2507 	}
2508 
2509 	override void setPosition(int a) {
2510 		super.setPosition(a);
2511 		version(win32_widgets) {
2512 			SCROLLINFO info;
2513 			info.cbSize = info.sizeof;
2514 			info.fMask = SIF_POS;
2515 			info.nPos = position;
2516 			SetScrollInfo(hwnd, SB_CTL, &info, true);
2517 		} else version(custom_widgets) {
2518 			thumb.positionY = thumbPosition;
2519 			thumb.thumbHeight = thumbSize;
2520 			thumb.redraw();
2521 		} else static assert(0);
2522 	}
2523 
2524 	this(Widget parent) {
2525 		super(parent);
2526 
2527 		version(win32_widgets) {
2528 			createWin32Window(this, "Scrollbar"w, "", 
2529 				0|WS_CHILD|WS_VISIBLE|SBS_VERT|SBS_RIGHTALIGN, 0);
2530 		} else version(custom_widgets) {
2531 			auto vl = new VerticalLayout(this);
2532 			auto upButton = new ArrowButton(ArrowDirection.up, vl);
2533 			thumb = new MouseTrackingWidget(MouseTrackingWidget.Orientation.vertical, vl);
2534 			auto downButton = new ArrowButton(ArrowDirection.down, vl);
2535 
2536 			upButton.addEventListener(EventType.triggered, () {
2537 				informProgramThatUserChangedPosition(position - step());
2538 			});
2539 			downButton.addEventListener(EventType.triggered, () {
2540 				informProgramThatUserChangedPosition(position + step());
2541 			});
2542 
2543 			thumb.thumbWidth = this.minWidth;
2544 			thumb.thumbHeight = 16;
2545 
2546 			thumb.addEventListener(EventType.change, () {
2547 				auto sy = thumb.positionY * max() / thumb.height;
2548 
2549 				informProgramThatUserChangedPosition(sy);
2550 			});
2551 		}
2552 	}
2553 
2554 	override int minWidth() { return 16; }
2555 	override int maxWidth() { return 16; }
2556 	override int minHeight() { return 48; }
2557 }
2558 
2559 
2560 
2561 ///
2562 abstract class Layout : Widget {
2563 	this(Widget parent = null) {
2564 		tabStop = false;
2565 		super(parent);
2566 	}
2567 }
2568 
2569 /++
2570 	Makes all children minimum width and height, placing them down
2571 	left to right, top to bottom.
2572 
2573 	Useful if you want to make a list of buttons that automatically
2574 	wrap to a new line when necessary.
2575 +/
2576 class InlineBlockLayout : Layout {
2577 	///
2578 	this(Widget parent = null) { super(parent); }
2579 
2580 	override void recomputeChildLayout() {
2581 		registerMovement();
2582 
2583 		int x = this.paddingLeft, y = this.paddingTop;
2584 
2585 		int lineHeight;
2586 		int previousMargin = 0;
2587 		int previousMarginBottom = 0;
2588 
2589 		foreach(child; children) {
2590 			if(child.hidden)
2591 				continue;
2592 			if(cast(FixedPosition) child) {
2593 				child.recomputeChildLayout();
2594 				continue;
2595 			}
2596 			child.width = child.minWidth();
2597 			if(child.width == 0)
2598 				child.width = 32;
2599 			child.height = child.minHeight();
2600 			if(child.height == 0)
2601 				child.height = 32;
2602 
2603 			if(x + child.width + paddingRight > this.width) {
2604 				x = this.paddingLeft;
2605 				y += lineHeight;
2606 				lineHeight = 0;
2607 				previousMargin = 0;
2608 				previousMarginBottom = 0;
2609 			}
2610 
2611 			auto margin = child.marginLeft;
2612 			if(previousMargin > margin)
2613 				margin = previousMargin;
2614 
2615 			x += margin;
2616 
2617 			child.x = x;
2618 			child.y = y;
2619 
2620 			int marginTopApplied;
2621 			if(child.marginTop > previousMarginBottom) {
2622 				child.y += child.marginTop;
2623 				marginTopApplied = child.marginTop;
2624 			}
2625 
2626 			x += child.width;
2627 			previousMargin = child.marginRight;
2628 
2629 			if(child.marginBottom > previousMarginBottom)
2630 				previousMarginBottom = child.marginBottom;
2631 
2632 			auto h = child.height + previousMarginBottom + marginTopApplied;
2633 			if(h > lineHeight)
2634 				lineHeight = h;
2635 
2636 			child.recomputeChildLayout();
2637 		}
2638 
2639 	}
2640 
2641 	override int minWidth() {
2642 		int min;
2643 		foreach(child; children) {
2644 			auto cm = child.minWidth;
2645 			if(cm > min)
2646 				min = cm;
2647 		}
2648 		return min + paddingLeft + paddingRight;
2649 	}
2650 
2651 	override int minHeight() {
2652 		int min;
2653 		foreach(child; children) {
2654 			auto cm = child.minHeight;
2655 			if(cm > min)
2656 				min = cm;
2657 		}
2658 		return min + paddingTop + paddingBottom;
2659 	}
2660 }
2661 
2662 /++
2663 	A tab widget is a set of clickable tab buttons followed by a content area.
2664 
2665 
2666 	Tabs can change existing content or can be new pages.
2667 
2668 	When the user picks a different tab, a `change` message is generated.
2669 +/
2670 class TabWidget : Widget {
2671 	this(Widget parent) {
2672 		super(parent);
2673 
2674 		version(win32_widgets) {
2675 			createWin32Window(this, WC_TABCONTROL, "", 0);
2676 		} else version(custom_widgets) {
2677 			tabBarHeight = Window.lineHeight;
2678 
2679 			addDirectEventListener(EventType.click, (Event event) {
2680 				if(event.clientY < tabBarHeight) {
2681 					auto t = (event.clientX / tabWidth);
2682 					if(t >= 0 && t < children.length)
2683 						setCurrentTab(t);
2684 				}
2685 			});
2686 		} else static assert(0);
2687 	}
2688 
2689 	override int marginTop() { return 4; }
2690 	override int marginBottom() { return 4; }
2691 
2692 	override int minHeight() {
2693 		int max = 0;
2694 		foreach(child; children)
2695 			max = mymax(child.minHeight, max);
2696 
2697 
2698 		version(win32_widgets) {
2699 			RECT rect;
2700 			rect.right = this.width;
2701 			rect.bottom = max;
2702 			TabCtrl_AdjustRect(hwnd, true, &rect);
2703 
2704 			max = rect.bottom;
2705 		} else {
2706 			max += Window.lineHeight + 4;
2707 		}
2708 
2709 
2710 		return max;
2711 	}
2712 
2713 	version(win32_widgets)
2714 	override int handleWmNotify(NMHDR* hdr, int code) {
2715 		switch(code) {
2716 			case TCN_SELCHANGE:
2717 				auto sel = TabCtrl_GetCurSel(hwnd);
2718 				showOnly(sel);
2719 			break;
2720 			default:
2721 		}
2722 		return 0;
2723 	}
2724 
2725 	override void addChild(Widget child, int pos = int.max) {
2726 		if(auto twp = cast(TabWidgetPage) child) {
2727 			super.addChild(child, pos);
2728 			if(pos == int.max)
2729 				pos = cast(int) this.children.length - 1;
2730 
2731 			version(win32_widgets) {
2732 				TCITEM item;
2733 				item.mask = TCIF_TEXT;
2734 				WCharzBuffer buf = WCharzBuffer(twp.title);
2735 				item.pszText = buf.ptr;
2736 				SendMessage(hwnd, TCM_INSERTITEM, pos, cast(LPARAM) &item);
2737 			} else version(custom_widgets) {
2738 			}
2739 
2740 			if(pos != getCurrentTab) {
2741 				child.showing = false;
2742 			}
2743 		} else {
2744 			assert(0, "Don't add children directly to a tab widget, instead add them to a page (see addPage)");
2745 		}
2746 	}
2747 
2748 	override void recomputeChildLayout() {
2749 		this.registerMovement();
2750 		version(win32_widgets) {
2751 
2752 			// Windows doesn't actually parent widgets to the
2753 			// tab control, so we will temporarily pretend this isn't
2754 			// a native widget as we do the changes. A bit of a filthy
2755 			// hack, but a functional one.
2756 			auto hwnd = this.hwnd;
2757 			this.hwnd = null;
2758 			scope(exit) this.hwnd = hwnd;
2759 
2760 			RECT rect;
2761 			GetWindowRect(hwnd, &rect);
2762 
2763 			auto left = rect.left;
2764 			auto top = rect.top;
2765 
2766 			TabCtrl_AdjustRect(hwnd, false, &rect);
2767 			foreach(child; children) {
2768 				child.x = rect.left - left;
2769 				child.y = rect.top - top;
2770 				child.width = rect.right - rect.left;
2771 				child.height = rect.bottom - rect.top;
2772 				child.recomputeChildLayout();
2773 			}
2774 		} else version(custom_widgets) {
2775 			foreach(child; children) {
2776 				child.x = 2;
2777 				child.y = tabBarHeight + 2; // for the border
2778 				child.width = width - 4; // for the border
2779 				child.height = height - tabBarHeight - 2 - 2; // for the border
2780 				child.recomputeChildLayout();
2781 			}
2782 		} else static assert(0);
2783 	}
2784 
2785 	version(custom_widgets) {
2786 		private int currentTab_;
2787 		int tabBarHeight;
2788 		int tabWidth = 80;
2789 	}
2790 
2791 	version(custom_widgets)
2792 	override void paint(ScreenPainter painter) {
2793 
2794 		draw3dFrame(0, tabBarHeight - 2, width, height - tabBarHeight + 2, painter, FrameStyle.risen);
2795 
2796 		int posX = 0;
2797 		foreach(idx, child; children) {
2798 			if(auto twp = cast(TabWidgetPage) child) {
2799 				auto isCurrent = idx == getCurrentTab();
2800 				draw3dFrame(posX, 0, tabWidth, tabBarHeight, painter, isCurrent ? FrameStyle.risen : FrameStyle.sunk, isCurrent ? windowBackgroundColor : darken(windowBackgroundColor, 0.1));
2801 				painter.outlineColor = Color.black;
2802 				painter.drawText(Point(posX + 4, 2), twp.title);
2803 
2804 				if(isCurrent) {
2805 					painter.outlineColor = windowBackgroundColor;
2806 					painter.fillColor = Color.transparent;
2807 					painter.drawLine(Point(posX + 2, tabBarHeight - 1), Point(posX + tabWidth, tabBarHeight - 1));
2808 					painter.drawLine(Point(posX + 2, tabBarHeight - 2), Point(posX + tabWidth, tabBarHeight - 2));
2809 
2810 					painter.outlineColor = Color.white;
2811 					painter.drawPixel(Point(posX + 1, tabBarHeight - 1));
2812 					painter.drawPixel(Point(posX + 1, tabBarHeight - 2));
2813 					painter.outlineColor = Color(233, 233, 233);
2814 	//painter.outlineColor = (style == FrameStyle.sunk) ? Color(128, 128, 128) : Color(223, 223, 223);
2815 					painter.drawPixel(Point(posX, tabBarHeight - 1));
2816 				}
2817 
2818 				posX += tabWidth - 2;
2819 			}
2820 		}
2821 	}
2822 
2823 	///
2824 	@scriptable
2825 	void setCurrentTab(int item) {
2826 		version(win32_widgets)
2827 			TabCtrl_SetCurSel(hwnd, item);
2828 		else version(custom_widgets)
2829 			currentTab_ = item;
2830 		else static assert(0);
2831 
2832 		showOnly(item);
2833 	}
2834 
2835 	///
2836 	@scriptable
2837 	int getCurrentTab() {
2838 		version(win32_widgets)
2839 			return TabCtrl_GetCurSel(hwnd);
2840 		else version(custom_widgets)
2841 			return currentTab_; // FIXME
2842 		else static assert(0);
2843 	}
2844 
2845 	///
2846 	@scriptable
2847 	void removeTab(int item) {
2848 		if(item && item == getCurrentTab())
2849 			setCurrentTab(item - 1);
2850 
2851 		version(win32_widgets) {
2852 			TabCtrl_DeleteItem(hwnd, item);
2853 		}
2854 
2855 		for(int a = item; a < children.length - 1; a++)
2856 			this.children[a] = this.children[a + 1];
2857 		this.children = this.children[0 .. $-1];
2858 	}
2859 
2860 	///
2861 	@scriptable
2862 	TabWidgetPage addPage(string title) {
2863 		return new TabWidgetPage(title, this);
2864 	}
2865 
2866 	private void showOnly(int item) {
2867 		foreach(idx, child; children)
2868 			if(idx == item) {
2869 				child.show();
2870 				recomputeChildLayout();
2871 			} else {
2872 				child.hide();
2873 			}
2874 	}
2875 }
2876 
2877 /++
2878 	A page widget is basically a tab widget with hidden tabs.
2879 
2880 	You add [TabWidgetPage]s to it.
2881 +/
2882 class PageWidget : Widget {
2883 	this(Widget parent) {
2884 		super(parent);
2885 	}
2886 
2887 	override int minHeight() {
2888 		int max = 0;
2889 		foreach(child; children)
2890 			max = mymax(child.minHeight, max);
2891 
2892 		return max;
2893 	}
2894 
2895 
2896 	override void addChild(Widget child, int pos = int.max) {
2897 		if(auto twp = cast(TabWidgetPage) child) {
2898 			super.addChild(child, pos);
2899 			if(pos == int.max)
2900 				pos = cast(int) this.children.length - 1;
2901 
2902 			if(pos != getCurrentTab) {
2903 				child.showing = false;
2904 			}
2905 		} else {
2906 			assert(0, "Don't add children directly to a page widget, instead add them to a page (see addPage)");
2907 		}
2908 	}
2909 
2910 	override void recomputeChildLayout() {
2911 		this.registerMovement();
2912 		foreach(child; children) {
2913 			child.x = 0;
2914 			child.y = 0;
2915 			child.width = width;
2916 			child.height = height;
2917 			child.recomputeChildLayout();
2918 		}
2919 	}
2920 
2921 	private int currentTab_;
2922 
2923 	///
2924 	@scriptable
2925 	void setCurrentTab(int item) {
2926 		currentTab_ = item;
2927 
2928 		showOnly(item);
2929 	}
2930 
2931 	///
2932 	@scriptable
2933 	int getCurrentTab() {
2934 		return currentTab_;
2935 	}
2936 
2937 	///
2938 	@scriptable
2939 	void removeTab(int item) {
2940 		if(item && item == getCurrentTab())
2941 			setCurrentTab(item - 1);
2942 
2943 		for(int a = item; a < children.length - 1; a++)
2944 			this.children[a] = this.children[a + 1];
2945 		this.children = this.children[0 .. $-1];
2946 	}
2947 
2948 	///
2949 	@scriptable
2950 	TabWidgetPage addPage(string title) {
2951 		return new TabWidgetPage(title, this);
2952 	}
2953 
2954 	private void showOnly(int item) {
2955 		foreach(idx, child; children)
2956 			if(idx == item) {
2957 				child.show();
2958 				child.recomputeChildLayout();
2959 			} else {
2960 				child.hide();
2961 			}
2962 	}
2963 
2964 }
2965 
2966 /++
2967 
2968 +/
2969 class TabWidgetPage : Widget {
2970 	string title;
2971 	this(string title, Widget parent) {
2972 		this.title = title;
2973 		super(parent);
2974 	}
2975 
2976 	override int minHeight() {
2977 		int sum = 0;
2978 		foreach(child; children)
2979 			sum += child.minHeight();
2980 		return sum;
2981 	}
2982 }
2983 
2984 version(none)
2985 class CollapsableSidebar : Widget {
2986 
2987 }
2988 
2989 /// Stacks the widgets vertically, taking all the available width for each child.
2990 class VerticalLayout : Layout {
2991 	// intentionally blank - widget's default is vertical layout right now
2992 	///
2993 	this(Widget parent) { super(parent); }
2994 }
2995 
2996 /// Stacks the widgets horizontally, taking all the available height for each child.
2997 class HorizontalLayout : Layout {
2998 	///
2999 	this(Widget parent = null) { super(parent); }
3000 	override void recomputeChildLayout() {
3001 		.recomputeChildLayout!"width"(this);
3002 	}
3003 
3004 	override int minHeight() {
3005 		int largest = 0;
3006 		int margins = 0;
3007 		int lastMargin = 0;
3008 		foreach(child; children) {
3009 			auto mh = child.minHeight();
3010 			if(mh > largest)
3011 				largest = mh;
3012 			margins += mymax(lastMargin, child.marginTop());
3013 			lastMargin = child.marginBottom();
3014 		}
3015 		return largest + margins;
3016 	}
3017 
3018 	override int maxHeight() {
3019 		int largest = 0;
3020 		int margins = 0;
3021 		int lastMargin = 0;
3022 		foreach(child; children) {
3023 			auto mh = child.maxHeight();
3024 			if(mh == int.max)
3025 				return int.max;
3026 			if(mh > largest)
3027 				largest = mh;
3028 			margins += mymax(lastMargin, child.marginTop());
3029 			lastMargin = child.marginBottom();
3030 		}
3031 		return largest + margins;
3032 	}
3033 
3034 	override int heightStretchiness() {
3035 		int max;
3036 		foreach(child; children) {
3037 			auto c = child.heightStretchiness;
3038 			if(c > max)
3039 				max = c;
3040 		}
3041 		return max;
3042 	}
3043 
3044 }
3045 
3046 /++
3047 	Bypasses automatic layout for its children, using manual positioning and sizing only.
3048 	While you need to manually position them, you must ensure they are inside the StaticLayout's
3049 	bounding box to avoid undefined behavior.
3050 
3051 	You should almost never use this.
3052 +/
3053 class StaticLayout : Layout {
3054 	///
3055 	this(Widget parent = null) { super(parent); }
3056 	override void recomputeChildLayout() {
3057 		registerMovement();
3058 		foreach(child; children)
3059 			child.recomputeChildLayout();
3060 	}
3061 }
3062 
3063 /++
3064 	Bypasses automatic positioning when being laid out. It is your responsibility to make
3065 	room for this widget in the parent layout.
3066 
3067 	Its children are laid out normally, unless there is exactly one, in which case it takes
3068 	on the full size of the `StaticPosition` object (if you plan to put stuff on the edge, you
3069 	can do that with `padding`).
3070 +/
3071 class StaticPosition : Layout {
3072 	///
3073 	this(Widget parent = null) { super(parent); }
3074 
3075 	override void recomputeChildLayout() {
3076 		registerMovement();
3077 		if(this.children.length == 1) {
3078 			auto child = children[0];
3079 			child.x = 0;
3080 			child.y = 0;
3081 			child.width = this.width;
3082 			child.height = this.height;
3083 			child.recomputeChildLayout();
3084 		} else
3085 		foreach(child; children)
3086 			child.recomputeChildLayout();
3087 	}
3088 
3089 }
3090 
3091 /++
3092 	FixedPosition is like [StaticPosition], but its coordinates
3093 	are always relative to the viewport, meaning they do not scroll with
3094 	the parent content.
3095 +/
3096 class FixedPosition : StaticPosition {
3097 	///
3098 	this(Widget parent) { super(parent); }
3099 }
3100 
3101 
3102 ///
3103 class Window : Widget {
3104 	int mouseCaptureCount = 0;
3105 	Widget mouseCapturedBy;
3106 	void captureMouse(Widget byWhom) {
3107 		assert(mouseCapturedBy is null || byWhom is mouseCapturedBy);
3108 		mouseCaptureCount++;
3109 		mouseCapturedBy = byWhom;
3110 		win.grabInput();
3111 	}
3112 	void releaseMouseCapture() {
3113 		mouseCaptureCount--;
3114 		mouseCapturedBy = null;
3115 		win.releaseInputGrab();
3116 	}
3117 
3118 	override Color backgroundColor() {
3119 		version(custom_widgets)
3120 			return windowBackgroundColor;
3121 		else version(win32_widgets)
3122 			return Color.transparent;
3123 		else static assert(0);
3124 	}
3125 
3126 	///
3127 	static int lineHeight;
3128 
3129 	Widget focusedWidget;
3130 
3131 	SimpleWindow win;
3132 
3133 	///
3134 	this(Widget p) {
3135 		tabStop = false;
3136 		super(p);
3137 	}
3138 
3139 	private bool skipNextChar = false;
3140 
3141 	///
3142 	this(SimpleWindow win) {
3143 
3144 		static if(UsingSimpledisplayX11) {
3145 			win.discardAdditionalConnectionState = &discardXConnectionState;
3146 			win.recreateAdditionalConnectionState = &recreateXConnectionState;
3147 		}
3148 
3149 		tabStop = false;
3150 		super(null);
3151 		this.win = win;
3152 
3153 		win.addEventListener((Widget.RedrawEvent) {
3154 			//import std.stdio; writeln("redrawing");
3155 			this.actualRedraw();
3156 		});
3157 
3158 		this.width = win.width;
3159 		this.height = win.height;
3160 		this.parentWindow = this;
3161 
3162 		win.windowResized = (int w, int h) {
3163 			this.width = w;
3164 			this.height = h;
3165 			recomputeChildLayout();
3166 			redraw();
3167 		};
3168 
3169 		win.onFocusChange = (bool getting) {
3170 			if(this.focusedWidget) {
3171 				auto evt = new Event(getting ? "focus" : "blur", this.focusedWidget);
3172 				evt.dispatch();
3173 			}
3174 			auto evt = new Event(getting ? "focus" : "blur", this);
3175 			evt.dispatch();
3176 		};
3177 
3178 		win.setEventHandlers(
3179 			(MouseEvent e) {
3180 				dispatchMouseEvent(e);
3181 			},
3182 			(KeyEvent e) {
3183 				//import std.stdio;
3184 				//writefln("%x   %s", cast(uint) e.key, e.key);
3185 				dispatchKeyEvent(e);
3186 			},
3187 			(dchar e) {
3188 				if(e == 13) e = 10; // hack?
3189 				if(e == 127) return; // linux sends this, windows doesn't. we don't want it.
3190 				dispatchCharEvent(e);
3191 			},
3192 		);
3193 
3194 		addEventListener("char", (Widget, Event ev) {
3195 			if(skipNextChar) {
3196 				ev.preventDefault();
3197 				skipNextChar = false;
3198 			}
3199 		});
3200 
3201 		version(win32_widgets)
3202 		win.handleNativeEvent = delegate int(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
3203 
3204 			if(hwnd !is this.win.impl.hwnd)
3205 				return 1; // we don't care...
3206 			switch(msg) {
3207 				case WM_NOTIFY:
3208 					auto hdr = cast(NMHDR*) lParam;
3209 					auto hwndFrom = hdr.hwndFrom;
3210 					auto code = hdr.code;
3211 
3212 					if(auto widgetp = hwndFrom in Widget.nativeMapping) {
3213 						return (*widgetp).handleWmNotify(hdr, code);
3214 					}
3215 				break;
3216 				case WM_COMMAND:
3217 					switch(HIWORD(wParam)) {
3218 						case 0:
3219 						// case BN_CLICKED: aka 0
3220 						case 1:
3221 							auto idm = LOWORD(wParam);
3222 							if(auto item = idm in Action.mapping) {
3223 								foreach(handler; (*item).triggered)
3224 									handler();
3225 							/*
3226 								auto event = new Event("triggered", *item);
3227 								event.button = idm;
3228 								event.dispatch();
3229 							*/
3230 							} else {
3231 								auto handle = cast(HWND) lParam;
3232 								if(auto widgetp = handle in Widget.nativeMapping) {
3233 									(*widgetp).handleWmCommand(HIWORD(wParam), LOWORD(wParam));
3234 								}
3235 							}
3236 						break;
3237 						default:
3238 							return 1;
3239 					}
3240 				break;
3241 				default: return 1; // not handled, pass it on
3242 			}
3243 			return 0;
3244 		};
3245 
3246 
3247 
3248 		if(lineHeight == 0) {
3249 			auto painter = win.draw();
3250 			lineHeight = painter.fontHeight() * 5 / 4;
3251 		}
3252 	}
3253 
3254 	version(win32_widgets)
3255 	override void paint(ScreenPainter painter) {
3256 		/*
3257 		RECT rect;
3258 		rect.right = this.width;
3259 		rect.bottom = this.height;
3260 		DrawThemeBackground(theme, painter.impl.hdc, 4, 1, &rect, null);
3261 		*/
3262 		// 3dface is used as window backgrounds by Windows too, so that's why I'm using it here
3263 		auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
3264 		auto p = SelectObject(painter.impl.hdc, GetStockObject(NULL_PEN));
3265 		// since the pen is null, to fill the whole space, we need the +1 on both.
3266 		gdi.Rectangle(painter.impl.hdc, 0, 0, this.width + 1, this.height + 1);
3267 		SelectObject(painter.impl.hdc, p);
3268 		SelectObject(painter.impl.hdc, b);
3269 	}
3270 	version(custom_widgets)
3271 	override void paint(ScreenPainter painter) {
3272 		painter.fillColor = windowBackgroundColor;
3273 		painter.outlineColor = windowBackgroundColor;
3274 		painter.drawRectangle(Point(0, 0), this.width, this.height);
3275 	}
3276 
3277 
3278 	override void defaultEventHandler_keydown(Event event) {
3279 		Widget _this = event.target;
3280 
3281 		if(event.key == Key.Tab) {
3282 			/* Window tab ordering is a recursive thingy with each group */
3283 
3284 			// FIXME inefficient
3285 			Widget[] helper(Widget p) {
3286 				if(p.hidden)
3287 					return null;
3288 				Widget[] childOrdering = p.children.dup;
3289 
3290 				import std.algorithm;
3291 				sort!((a, b) => a.tabOrder < b.tabOrder)(childOrdering);
3292 
3293 				Widget[] ret;
3294 				foreach(child; childOrdering) {
3295 					if(child.tabStop && !child.hidden)
3296 						ret ~= child;
3297 					ret ~= helper(child);
3298 				}
3299 
3300 				return ret;
3301 			}
3302 
3303 			Widget[] tabOrdering = helper(this);
3304 
3305 			Widget recipient;
3306 
3307 			if(tabOrdering.length) {
3308 				bool seenThis = false;
3309 				Widget previous;
3310 				foreach(idx, child; tabOrdering) {
3311 					if(child is focusedWidget) {
3312 
3313 						if(event.shiftKey) {
3314 							if(idx == 0)
3315 								recipient = tabOrdering[$-1];
3316 							else
3317 								recipient = tabOrdering[idx - 1];
3318 							break;
3319 						}
3320 
3321 						seenThis = true;
3322 						if(idx + 1 == tabOrdering.length) {
3323 							// we're at the end, either move to the next group
3324 							// or start back over
3325 							recipient = tabOrdering[0];
3326 						}
3327 						continue;
3328 					}
3329 					if(seenThis) {
3330 						recipient = child;
3331 						break;
3332 					}
3333 					previous = child;
3334 				}
3335 			}
3336 
3337 			if(recipient !is null) {
3338 				// import std.stdio; writeln(typeid(recipient));
3339 				recipient.focus();
3340 				/*
3341 				version(win32_widgets) {
3342 					if(recipient.hwnd !is null)
3343 						SetFocus(recipient.hwnd);
3344 				} else version(custom_widgets) {
3345 					focusedWidget = recipient;
3346 				} else static assert(false);
3347 				*/
3348 
3349 				skipNextChar = true;
3350 			}
3351 		}
3352 
3353 		debug if(event.key == Key.F12) {
3354 			if(devTools) {
3355 				devTools.close();
3356 				devTools = null;
3357 			} else {
3358 				devTools = new DevToolWindow(this);
3359 				devTools.show();
3360 			}
3361 		}
3362 	}
3363 
3364 	debug DevToolWindow devTools;
3365 
3366 
3367 	///
3368 	this(int width = 500, int height = 500, string title = null) {
3369 		win = new SimpleWindow(width, height, title, OpenGlOptions.no, Resizability.allowResizing, WindowTypes.normal, WindowFlags.dontAutoShow);
3370 		this(win);
3371 	}
3372 
3373 	///
3374 	this(string title) {
3375 		this(500, 500, title);
3376 	}
3377 
3378 	///
3379 	@scriptable
3380 	void close() {
3381 		win.close();
3382 	}
3383 
3384 	bool dispatchKeyEvent(KeyEvent ev) {
3385 		auto wid = focusedWidget;
3386 		if(wid is null)
3387 			wid = this;
3388 		auto event = new Event(ev.pressed ? "keydown" : "keyup", wid);
3389 		event.originalKeyEvent = ev;
3390 		event.character = ev.character;
3391 		event.key = ev.key;
3392 		event.state = ev.modifierState;
3393 		event.shiftKey = (ev.modifierState & ModifierState.shift) ? true : false;
3394 		event.dispatch();
3395 
3396 		return true;
3397 	}
3398 
3399 	bool dispatchCharEvent(dchar ch) {
3400 		if(focusedWidget) {
3401 			auto event = new Event("char", focusedWidget);
3402 			event.character = ch;
3403 			event.dispatch();
3404 		}
3405 		return true;
3406 	}
3407 
3408 	Widget mouseLastOver;
3409 	Widget mouseLastDownOn;
3410 	bool dispatchMouseEvent(MouseEvent ev) {
3411 		auto eleR = widgetAtPoint(this, ev.x, ev.y);
3412 		auto ele = eleR.widget;
3413 
3414 		if(mouseCapturedBy !is null) {
3415 			if(ele !is mouseCapturedBy && !mouseCapturedBy.isAParentOf(ele))
3416 				ele = mouseCapturedBy;
3417 		}
3418 
3419 		// a hack to get it relative to the widget.
3420 		eleR.x = ev.x;
3421 		eleR.y = ev.y;
3422 		auto pain = ele;
3423 		while(pain) {
3424 			eleR.x -= pain.x;
3425 			eleR.y -= pain.y;
3426 			pain = pain.parent;
3427 		}
3428 
3429 		if(ev.type == 1) {
3430 			mouseLastDownOn = ele;
3431 			auto event = new Event("mousedown", ele);
3432 			event.button = ev.button;
3433 			event.buttonLinear = ev.buttonLinear;
3434 			event.state = ev.modifierState;
3435 			event.clientX = eleR.x;
3436 			event.clientY = eleR.y;
3437 			event.dispatch();
3438 		} else if(ev.type == 2) {
3439 			auto event = new Event("mouseup", ele);
3440 			event.button = ev.button;
3441 			event.buttonLinear = ev.buttonLinear;
3442 			event.clientX = eleR.x;
3443 			event.clientY = eleR.y;
3444 			event.state = ev.modifierState;
3445 			event.dispatch();
3446 			if(mouseLastDownOn is ele) {
3447 				event = new Event("click", ele);
3448 				event.clientX = eleR.x;
3449 				event.clientY = eleR.y;
3450 				event.button = ev.button;
3451 				event.buttonLinear = ev.buttonLinear;
3452 				event.dispatch();
3453 			}
3454 		} else if(ev.type == 0) {
3455 			// motion
3456 			Event event = new Event("mousemove", ele);
3457 			event.state = ev.modifierState;
3458 			event.clientX = eleR.x;
3459 			event.clientY = eleR.y;
3460 			event.dispatch();
3461 
3462 			if(mouseLastOver !is ele) {
3463 				if(ele !is null) {
3464 					if(!isAParentOf(ele, mouseLastOver)) {
3465 						event = new Event("mouseenter", ele);
3466 						event.relatedTarget = mouseLastOver;
3467 						event.sendDirectly();
3468 
3469 						auto cursor = ele.cursor ? ele.cursor.cursorHandle : cast(typeof(ele.cursor.cursorHandle())) 0;
3470 						if(ele.parentWindow.win.curHidden <= 0) {
3471 						static if(UsingSimpledisplayX11) {
3472 							XDefineCursor(XDisplayConnection.get(), ele.parentWindow.win.impl.window, cursor);
3473 						} else version(Windows) {
3474 							// FIXME i don't like this
3475 							if(cursor !is null)
3476 								SetCursor(cursor);
3477 						} else static assert(0);
3478 						}
3479 					}
3480 				}
3481 
3482 				if(mouseLastOver !is null) {
3483 					if(!isAParentOf(mouseLastOver, ele)) {
3484 						event = new Event("mouseleave", mouseLastOver);
3485 						event.relatedTarget = ele;
3486 						event.sendDirectly();
3487 					}
3488 				}
3489 
3490 				if(ele !is null) {
3491 					event = new Event("mouseover", ele);
3492 					event.relatedTarget = mouseLastOver;
3493 					event.dispatch();
3494 				}
3495 
3496 				if(mouseLastOver !is null) {
3497 					event = new Event("mouseout", mouseLastOver);
3498 					event.relatedTarget = ele;
3499 					event.dispatch();
3500 				}
3501 
3502 				mouseLastOver = ele;
3503 			}
3504 		}
3505 
3506 		return true;
3507 	}
3508 
3509 	///
3510 	@scriptable
3511 	void loop() {
3512 		recomputeChildLayout();
3513 		focusedWidget = getFirstFocusable(this); // FIXME: autofocus?
3514 		win.show();
3515 		redraw();
3516 		win.eventLoop(0);
3517 	}
3518 
3519 	@scriptable
3520 	override void show() {
3521 		win.show();
3522 		super.show();
3523 	}
3524 	@scriptable
3525 	override void hide() {
3526 		win.hide();
3527 		super.hide();
3528 	}
3529 
3530 	static Widget getFirstFocusable(Widget start) {
3531 		if(start.tabStop && !start.hidden)
3532 			return start;
3533 
3534 		if(!start.hidden)
3535 		foreach(child; start.children) {
3536 			auto f = getFirstFocusable(child);
3537 			if(f !is null)
3538 				return f;
3539 		}
3540 		return null;
3541 	}
3542 }
3543 
3544 debug private class DevToolWindow : Window {
3545 	Window p;
3546 
3547 	TextEdit parentList;
3548 	TextEdit logWindow;
3549 	TextLabel clickX, clickY;
3550 
3551 	this(Window p) {
3552 		this.p = p;
3553 		super(400, 300, "Developer Toolbox");
3554 
3555 		logWindow = new TextEdit(this);
3556 		parentList = new TextEdit(this);
3557 
3558 		auto hl = new HorizontalLayout(this);
3559 		clickX = new TextLabel("", hl);
3560 		clickY = new TextLabel("", hl);
3561 
3562 		parentListeners ~= p.addEventListener(EventType.click, (Event ev) {
3563 			auto s = ev.srcElement;
3564 			string list = s.toString();
3565 			s = s.parent;
3566 			while(s) {
3567 				list ~= "\n";
3568 				list ~= s.toString();
3569 				s = s.parent;
3570 			}
3571 			parentList.content = list;
3572 
3573 			import std.conv;
3574 			clickX.label = to!string(ev.clientX);
3575 			clickY.label = to!string(ev.clientY);
3576 		});
3577 	}
3578 
3579 	EventListener[] parentListeners;
3580 
3581 	override void close() {
3582 		assert(p !is null);
3583 		foreach(p; parentListeners)
3584 			p.disconnect();
3585 		parentListeners = null;
3586 		p.devTools = null;
3587 		p = null;
3588 		super.close();
3589 	}
3590 
3591 	override void defaultEventHandler_keydown(Event ev) {
3592 		if(ev.key == Key.F12) {
3593 			this.close();
3594 			p.devTools = null;
3595 		} else {
3596 			super.defaultEventHandler_keydown(ev);
3597 		}
3598 	}
3599 
3600 	void log(T...)(T t) {
3601 		string str;
3602 		import std.conv;
3603 		foreach(i; t)
3604 			str ~= to!string(i);
3605 		str ~= "\n";
3606 		logWindow.addText(str);
3607 
3608 		logWindow.ensureVisibleInScroll(logWindow.textLayout.caretBoundingBox());
3609 	}
3610 }
3611 
3612 /++
3613 	A dialog is a transient window that intends to get information from
3614 	the user before being dismissed.
3615 +/
3616 abstract class Dialog : Window {
3617 	///
3618 	this(int width, int height, string title = null) {
3619 		super(width, height, title);
3620 	}
3621 
3622 	///
3623 	abstract void OK();
3624 
3625 	///
3626 	void Cancel() {
3627 		this.close();
3628 	}
3629 }
3630 
3631 ///
3632 class LabeledLineEdit : Widget {
3633 	///
3634 	this(string label, Widget parent = null) {
3635 		super(parent);
3636 		tabStop = false;
3637 		auto hl = new HorizontalLayout(this);
3638 		this.label = new TextLabel(label, hl);
3639 		this.lineEdit = new LineEdit(hl);
3640 	}
3641 	TextLabel label; ///
3642 	LineEdit lineEdit; ///
3643 
3644 	override int minHeight() { return Window.lineHeight + 4; }
3645 	override int maxHeight() { return Window.lineHeight + 4; }
3646 
3647 	///
3648 	string content() {
3649 		return lineEdit.content;
3650 	}
3651 	///
3652 	void content(string c) {
3653 		return lineEdit.content(c);
3654 	}
3655 
3656 	///
3657 	void selectAll() {
3658 		lineEdit.selectAll();
3659 	}
3660 
3661 	override void focus() {
3662 		lineEdit.focus();
3663 	}
3664 }
3665 
3666 ///
3667 class MainWindow : Window {
3668 	///
3669 	this(string title = null) {
3670 		super(500, 500, title);
3671 
3672 		_clientArea = new ClientAreaWidget();
3673 		_clientArea.x = 0;
3674 		_clientArea.y = 0;
3675 		_clientArea.width = this.width;
3676 		_clientArea.height = this.height;
3677 		_clientArea.tabStop = false;
3678 
3679 		super.addChild(_clientArea);
3680 
3681 		statusBar = new StatusBar(this);
3682 	}
3683 
3684 	/++
3685 		Adds a menu and toolbar from annotated functions.
3686 
3687 	---
3688         struct Commands {
3689                 @menu("File") {
3690                         void New() {}
3691                         void Open() {}
3692                         void Save() {}
3693                         @seperator
3694                         void eXit() @accelerator("Alt+F4") {
3695                                 window.close();
3696                         }
3697                 }
3698 
3699                 @menu("Edit") {
3700                         void Undo() {
3701                                 undo();
3702                         }
3703                         @seperator
3704                         void Cut() {}
3705                         void Copy() {}
3706                         void Paste() {}
3707                 }
3708 
3709                 @menu("Help") {
3710                         void About() {}
3711                 }
3712         }
3713 
3714         Commands commands;
3715 
3716         window.setMenuAndToolbarFromAnnotatedCode(commands);
3717 	---
3718 
3719 	+/
3720 	void setMenuAndToolbarFromAnnotatedCode(T)(ref T t) if(!is(T == class) && !is(T == interface)) {
3721 		setMenuAndToolbarFromAnnotatedCode_internal(t);
3722 	}
3723 	void setMenuAndToolbarFromAnnotatedCode(T)(T t) if(is(T == class) || is(T == interface)) {
3724 		setMenuAndToolbarFromAnnotatedCode_internal(t);
3725 	}
3726 	void setMenuAndToolbarFromAnnotatedCode_internal(T)(ref T t) {
3727 		Action[] toolbarActions;
3728 		auto menuBar = new MenuBar();
3729 		Menu[string] mcs;
3730 
3731 		void delegate() triggering;
3732 
3733 		foreach(memberName; __traits(derivedMembers, T)) {
3734 			static if(__traits(compiles, triggering = &__traits(getMember, t, memberName))) {
3735 				.menu menu;
3736 				.toolbar toolbar;
3737 				bool seperator;
3738 				.accelerator accelerator;
3739 				.icon icon;
3740 				string label;
3741 				foreach(attr; __traits(getAttributes, __traits(getMember, T, memberName))) {
3742 					static if(is(typeof(attr) == .menu))
3743 						menu = attr;
3744 					else static if(is(typeof(attr) == .toolbar))
3745 						toolbar = attr;
3746 					else static if(is(attr == .seperator))
3747 						seperator = true;
3748 					else static if(is(typeof(attr) == .accelerator))
3749 						accelerator = attr;
3750 					else static if(is(typeof(attr) == .icon))
3751 						icon = attr;
3752 					else static if(is(typeof(attr) == .label))
3753 						label = attr.label;
3754 				}
3755 
3756 				if(menu !is .menu.init || toolbar !is .toolbar.init) {
3757 					ushort correctIcon = icon.id; // FIXME
3758 					if(label.length == 0)
3759 						label = memberName;
3760 					auto action = new Action(label, correctIcon, &__traits(getMember, t, memberName));
3761 
3762 					if(accelerator.keyString.length) {
3763 						auto ke = KeyEvent.parse(accelerator.keyString);
3764 						action.accelerator = ke;
3765 						accelerators[ke.toStr] = &__traits(getMember, t, memberName);
3766 					}
3767 
3768 					if(toolbar !is .toolbar.init)
3769 						toolbarActions ~= action;
3770 					if(menu !is .menu.init) {
3771 						Menu mc;
3772 						if(menu.name in mcs) {
3773 							mc = mcs[menu.name];
3774 						} else {
3775 							mc = new Menu(menu.name);
3776 							menuBar.addItem(mc);
3777 							mcs[menu.name] = mc;
3778 						}
3779 
3780 						if(seperator)
3781 							mc.addSeparator();
3782 						mc.addItem(new MenuItem(action));
3783 					}
3784 				}
3785 			}
3786 		}
3787 
3788 		this.menuBar = menuBar;
3789 
3790 		if(toolbarActions.length) {
3791 			auto tb = new ToolBar(toolbarActions, this);
3792 		}
3793 	}
3794 
3795 	void delegate()[string] accelerators;
3796 
3797 	override void defaultEventHandler_keydown(Event event) {
3798 		auto str = event.originalKeyEvent.toStr;
3799 		if(auto acl = str in accelerators)
3800 			(*acl)();
3801 		super.defaultEventHandler_keydown(event);
3802 	}
3803 
3804 	override void defaultEventHandler_mouseover(Event event) {
3805 		super.defaultEventHandler_mouseover(event);
3806 		if(this.statusBar !is null && event.target.statusTip.length)
3807 			this.statusBar.parts[0].content = event.target.statusTip;
3808 		else if(this.statusBar !is null && this.statusTip.length)
3809 			this.statusBar.parts[0].content = this.statusTip; // ~ " " ~ event.target.toString();
3810 	}
3811 
3812 	override void addChild(Widget c, int position = int.max) {
3813 		if(auto tb = cast(ToolBar) c)
3814 			super.addChild(c, menuBar ? 1 : 0);
3815 		else
3816 			clientArea.addChild(c, position);
3817 	}
3818 
3819 	ToolBar _toolBar;
3820 	///
3821 	ToolBar toolBar() { return _toolBar; }
3822 	///
3823 	ToolBar toolBar(ToolBar t) {
3824 		_toolBar = t;
3825 		foreach(child; this.children)
3826 			if(child is t)
3827 				return t;
3828 		version(win32_widgets)
3829 			super.addChild(t, 0);
3830 		else version(custom_widgets)
3831 			super.addChild(t, menuBar ? 1 : 0);
3832 		else static assert(0);
3833 		return t;
3834 	}
3835 
3836 	MenuBar _menu;
3837 	///
3838 	MenuBar menuBar() { return _menu; }
3839 	///
3840 	MenuBar menuBar(MenuBar m) {
3841 		if(_menu !is null) {
3842 			// make sure it is sanely removed
3843 			// FIXME
3844 		}
3845 
3846 		_menu = m;
3847 
3848 		version(win32_widgets) {
3849 			SetMenu(parentWindow.win.impl.hwnd, m.handle);
3850 		} else version(custom_widgets) {
3851 			super.addChild(m, 0);
3852 
3853 		//	clientArea.y = menu.height;
3854 		//	clientArea.height = this.height - menu.height;
3855 
3856 			recomputeChildLayout();
3857 		} else static assert(false);
3858 
3859 		return _menu;
3860 	}
3861 	private Widget _clientArea;
3862 	///
3863 	@property Widget clientArea() { return _clientArea; }
3864 	protected @property void clientArea(Widget wid) {
3865 		_clientArea = wid;
3866 	}
3867 
3868 	private StatusBar _statusBar;
3869 	///
3870 	@property StatusBar statusBar() { return _statusBar; }
3871 	///
3872 	@property void statusBar(StatusBar bar) {
3873 		_statusBar = bar;
3874 		super.addChild(_statusBar);
3875 	}
3876 
3877 	///
3878 	@property string title() { return parentWindow.win.title; }
3879 	///
3880 	@property void title(string title) { parentWindow.win.title = title; }
3881 }
3882 
3883 class ClientAreaWidget : Widget {
3884 	this(Widget parent = null) {
3885 		super(parent);
3886 	}
3887 }
3888 
3889 /**
3890 	Toolbars are lists of buttons (typically icons) that appear under the menu.
3891 	Each button ought to correspond to a menu item.
3892 */
3893 class ToolBar : Widget {
3894 	version(win32_widgets) {
3895 		private const int idealHeight;
3896 		override int minHeight() { return idealHeight; }
3897 		override int maxHeight() { return idealHeight; }
3898 	} else version(custom_widgets) {
3899 		override int minHeight() { return toolbarIconSize; }// Window.lineHeight * 3/2; }
3900 		override int maxHeight() { return toolbarIconSize; } //Window.lineHeight * 3/2; }
3901 	} else static assert(false);
3902 	override int heightStretchiness() { return 0; }
3903 
3904 	version(win32_widgets) 
3905 		HIMAGELIST imageList;
3906 
3907 	this(Widget parent) {
3908 		this(null, parent);
3909 	}
3910 
3911 	///
3912 	this(Action[] actions, Widget parent = null) {
3913 		super(parent);
3914 
3915 		tabStop = false;
3916 
3917 		version(win32_widgets) {
3918 			createWin32Window(this, "ToolbarWindow32"w, "", 0);
3919 
3920 			imageList = ImageList_Create(
3921 				// width, height
3922 				16, 16,
3923 				ILC_COLOR16 | ILC_MASK,
3924 				16 /*numberOfButtons*/, 0);
3925 
3926 			SendMessageA(hwnd, TB_SETIMAGELIST, cast(WPARAM) 0, cast(LPARAM) imageList);
3927 			SendMessageA(hwnd, TB_LOADIMAGES, cast(WPARAM) IDB_STD_SMALL_COLOR, cast(LPARAM) HINST_COMMCTRL);
3928 
3929 			TBBUTTON[] buttons;
3930 
3931 			// FIXME: I_IMAGENONE is if here is no icon
3932 			foreach(action; actions)
3933 				buttons ~= TBBUTTON(MAKELONG(cast(ushort)(action.iconId ? (action.iconId - 1) : -2 /* I_IMAGENONE */), 0), action.id, TBSTATE_ENABLED, 0, 0, 0, cast(int) toStringzInternal(action.label));
3934 
3935 			SendMessageA(hwnd, TB_BUTTONSTRUCTSIZE, cast(WPARAM)TBBUTTON.sizeof, 0);
3936 			SendMessageA(hwnd, TB_ADDBUTTONSA,       cast(WPARAM) buttons.length,      cast(LPARAM)buttons.ptr);
3937 
3938 			SIZE size;
3939 			import core.sys.windows.commctrl;
3940 			SendMessageA(hwnd, TB_GETMAXSIZE, 0, cast(LPARAM) &size);
3941 			idealHeight = size.cy + 4; // the plus 4 is a hack
3942 
3943 			/*
3944 			RECT rect;
3945 			GetWindowRect(hwnd, &rect);
3946 			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
3947 			*/
3948 
3949 			assert(idealHeight);
3950 		} else version(custom_widgets) {
3951 			foreach(action; actions)
3952 				addChild(new ToolButton(action));
3953 		} else static assert(false);
3954 	}
3955 
3956 	override void recomputeChildLayout() {
3957 		.recomputeChildLayout!"width"(this);
3958 	}
3959 }
3960 
3961 enum toolbarIconSize = 24;
3962 
3963 ///
3964 class ToolButton : Button {
3965 	///
3966 	this(string label, Widget parent = null) {
3967 		super(label, parent);
3968 		tabStop = false;
3969 	}
3970 	///
3971 	this(Action action, Widget parent = null) {
3972 		super(action.label, parent);
3973 		tabStop = false;
3974 		this.action = action;
3975 	}
3976 
3977 	version(custom_widgets)
3978 	override void defaultEventHandler_click(Event event) {
3979 		foreach(handler; action.triggered)
3980 			handler();
3981 	}
3982 
3983 	Action action;
3984 
3985 	override int maxWidth() { return toolbarIconSize; }
3986 	override int minWidth() { return toolbarIconSize; }
3987 	override int maxHeight() { return toolbarIconSize; }
3988 	override int minHeight() { return toolbarIconSize; }
3989 
3990 	version(custom_widgets)
3991 	override void paint(ScreenPainter painter) {
3992 		this.draw3dFrame(painter, isDepressed ? FrameStyle.sunk : FrameStyle.risen, currentButtonColor);
3993 
3994 		painter.outlineColor = Color.black;
3995 
3996 		// I want to get from 16 to 24. that's * 3 / 2
3997 		static assert(toolbarIconSize >= 16);
3998 		enum multiplier = toolbarIconSize / 8;
3999 		enum divisor = 2 + ((toolbarIconSize % 8) ? 1 : 0);
4000 		switch(action.iconId) {
4001 			case GenericIcons.New:
4002 				painter.fillColor = Color.white;
4003 				painter.drawPolygon(
4004 					Point(3, 2) * multiplier / divisor, Point(3, 13) * multiplier / divisor, Point(12, 13) * multiplier / divisor, Point(12, 6) * multiplier / divisor,
4005 					Point(8, 2) * multiplier / divisor, Point(8, 6) * multiplier / divisor, Point(12, 6) * multiplier / divisor, Point(8, 2) * multiplier / divisor
4006 				);
4007 			break;
4008 			case GenericIcons.Save:
4009 				painter.fillColor = Color.black;
4010 				painter.drawRectangle(Point(2, 2) * multiplier / divisor, Point(13, 13) * multiplier / divisor);
4011 
4012 				painter.fillColor = Color.white;
4013 				painter.outlineColor = Color.white;
4014 				// the slider
4015 				painter.drawRectangle(Point(5, 2) * multiplier / divisor, Point(10, 5) * multiplier / divisor);
4016 				// the label
4017 				painter.drawRectangle(Point(4, 8) * multiplier / divisor, Point(11, 12) * multiplier / divisor);
4018 
4019 				painter.fillColor = Color.black;
4020 				painter.outlineColor = Color.black;
4021 				// the disc window
4022 				painter.drawRectangle(Point(8, 3) * multiplier / divisor, Point(9, 4) * multiplier / divisor);
4023 			break;
4024 			case GenericIcons.Open:
4025 				painter.fillColor = Color.white;
4026 				painter.drawPolygon(
4027 					Point(3, 4) * multiplier / divisor, Point(3, 12) * multiplier / divisor, Point(14, 12) * multiplier / divisor, Point(14, 3) * multiplier / divisor,
4028 					Point(10, 3) * multiplier / divisor, Point(10, 4) * multiplier / divisor, Point(3, 4) * multiplier / divisor);
4029 				painter.drawPolygon(
4030 					Point(1, 6) * multiplier / divisor, Point(12, 6) * multiplier / divisor,
4031 					Point(14, 12) * multiplier / divisor, Point(3, 12) * multiplier / divisor);
4032 				//painter.drawLine(Point(9, 6) * multiplier / divisor, Point(13, 7) * multiplier / divisor);
4033 			break;
4034 			case GenericIcons.Copy:
4035 				painter.fillColor = Color.white;
4036 				painter.drawRectangle(Point(3, 2) * multiplier / divisor, Point(9, 10) * multiplier / divisor);
4037 				painter.drawRectangle(Point(6, 5) * multiplier / divisor, Point(12, 13) * multiplier / divisor);
4038 			break;
4039 			case GenericIcons.Cut:
4040 				painter.fillColor = Color.transparent;
4041 				painter.drawLine(Point(3, 2) * multiplier / divisor, Point(10, 9) * multiplier / divisor);
4042 				painter.drawLine(Point(4, 9) * multiplier / divisor, Point(11, 2) * multiplier / divisor);
4043 				painter.drawRectangle(Point(3, 9) * multiplier / divisor, Point(5, 13) * multiplier / divisor);
4044 				painter.drawRectangle(Point(9, 9) * multiplier / divisor, Point(11, 12) * multiplier / divisor);
4045 			break;
4046 			case GenericIcons.Paste:
4047 				painter.fillColor = Color.white;
4048 				painter.drawRectangle(Point(2, 3) * multiplier / divisor, Point(11, 11) * multiplier / divisor);
4049 				painter.drawRectangle(Point(6, 8) * multiplier / divisor, Point(13, 13) * multiplier / divisor);
4050 				painter.drawLine(Point(6, 2) * multiplier / divisor, Point(4, 5) * multiplier / divisor);
4051 				painter.drawLine(Point(6, 2) * multiplier / divisor, Point(9, 5) * multiplier / divisor);
4052 				painter.fillColor = Color.black;
4053 				painter.drawRectangle(Point(4, 5) * multiplier / divisor, Point(9, 6) * multiplier / divisor);
4054 			break;
4055 			case GenericIcons.Help:
4056 				painter.drawText(Point(0, 0), "?", Point(width, height), TextAlignment.Center | TextAlignment.VerticalCenter);
4057 			break;
4058 			default:
4059 				painter.drawText(Point(0, 0), action.label, Point(width, height), TextAlignment.Center | TextAlignment.VerticalCenter);
4060 		}
4061 	}
4062 
4063 }
4064 
4065 
4066 ///
4067 class MenuBar : Widget {
4068 	MenuItem[] items;
4069 
4070 	version(win32_widgets) {
4071 		HMENU handle;
4072 		///
4073 		this(Widget parent = null) {
4074 			super(parent);
4075 
4076 			handle = CreateMenu();
4077 			tabStop = false;
4078 		}
4079 	} else version(custom_widgets) {
4080 		///
4081 		this(Widget parent = null) {
4082 			tabStop = false; // these are selected some other way
4083 			super(parent);
4084 		}
4085 
4086 		mixin Padding!q{2};
4087 	} else static assert(false);
4088 
4089 	version(custom_widgets)
4090 	override void paint(ScreenPainter painter) {
4091 		draw3dFrame(this, painter, FrameStyle.risen);
4092 	}
4093 
4094 	///
4095 	MenuItem addItem(MenuItem item) {
4096 		this.addChild(item);
4097 		items ~= item;
4098 		version(win32_widgets) {
4099 			AppendMenuA(handle, MF_STRING, item.action is null ? 9000 : item.action.id, toStringzInternal(item.label)); // XXX
4100 		}
4101 		return item;
4102 	}
4103 
4104 
4105 	///
4106 	Menu addItem(Menu item) {
4107 		auto mbItem = new MenuItem(item.label, this.parentWindow);
4108 
4109 		addChild(mbItem);
4110 		items ~= mbItem;
4111 
4112 		version(win32_widgets) {
4113 			AppendMenuA(handle, MF_STRING | MF_POPUP, cast(UINT) item.handle, toStringzInternal(item.label)); // XXX
4114 		} else version(custom_widgets) {
4115 			mbItem.defaultEventHandlers["mousedown"] = (Widget e, Event ev) {
4116 				item.popup(mbItem);
4117 			};
4118 		} else static assert(false);
4119 
4120 		return item;
4121 	}
4122 
4123 	override void recomputeChildLayout() {
4124 		.recomputeChildLayout!"width"(this);
4125 	}
4126 
4127 	override int maxHeight() { return Window.lineHeight + 4; }
4128 	override int minHeight() { return Window.lineHeight + 4; }
4129 }
4130 
4131 
4132 /**
4133 	Status bars appear at the bottom of a MainWindow.
4134 	They are made out of Parts, with a width and content.
4135 
4136 	They can have multiple parts or be in simple mode. FIXME: implement
4137 
4138 
4139 	sb.parts[0].content = "Status bar text!";
4140 */
4141 class StatusBar : Widget {
4142 	private Part[] partsArray;
4143 	///
4144 	struct Parts {
4145 		@disable this();
4146 		this(StatusBar owner) { this.owner = owner; }
4147 		//@disable this(this);
4148 		///
4149 		@property int length() { return cast(int) owner.partsArray.length; }
4150 		private StatusBar owner;
4151 		private this(StatusBar owner, Part[] parts) {
4152 			this.owner.partsArray = parts;
4153 			this.owner = owner;
4154 		}
4155 		///
4156 		Part opIndex(int p) {
4157 			if(owner.partsArray.length == 0)
4158 				this ~= new StatusBar.Part(300);
4159 			return owner.partsArray[p];
4160 		}
4161 
4162 		///
4163 		Part opOpAssign(string op : "~" )(Part p) {
4164 			assert(owner.partsArray.length < 255);
4165 			p.owner = this.owner;
4166 			p.idx = cast(int) owner.partsArray.length;
4167 			owner.partsArray ~= p;
4168 			version(win32_widgets) {
4169 				int[256] pos;
4170 				int cpos = 0;
4171 				foreach(idx, part; owner.partsArray) {
4172 					if(part.width)
4173 						cpos += part.width;
4174 					else
4175 						cpos += 100;
4176 
4177 					if(idx + 1 == owner.partsArray.length)
4178 						pos[idx] = -1;
4179 					else
4180 						pos[idx] = cpos;
4181 				}
4182 				SendMessageA(owner.hwnd, WM_USER + 4 /*SB_SETPARTS*/, owner.partsArray.length, cast(int) pos.ptr);
4183 			} else version(custom_widgets) {
4184 				owner.redraw();
4185 			} else static assert(false);
4186 
4187 			return p;
4188 		}
4189 	}
4190 
4191 	private Parts _parts;
4192 	///
4193 	@property Parts parts() {
4194 		return _parts;
4195 	}
4196 
4197 	///
4198 	static class Part {
4199 		int width;
4200 		StatusBar owner;
4201 
4202 		///
4203 		this(int w = 100) { width = w; }
4204 
4205 		private int idx;
4206 		private string _content;
4207 		///
4208 		@property string content() { return _content; }
4209 		///
4210 		@property void content(string s) {
4211 			version(win32_widgets) {
4212 				_content = s;
4213 				WCharzBuffer bfr = WCharzBuffer(s);
4214 				SendMessageW(owner.hwnd, SB_SETTEXT, idx, cast(LPARAM) bfr.ptr);
4215 			} else version(custom_widgets) {
4216 				if(_content != s) {
4217 					_content = s;
4218 					owner.redraw();
4219 				}
4220 			} else static assert(false);
4221 		}
4222 	}
4223 	string simpleModeContent;
4224 	bool inSimpleMode;
4225 
4226 
4227 	///
4228 	this(Widget parent = null) {
4229 		super(null); // FIXME
4230 		_parts = Parts(this);
4231 		tabStop = false;
4232 		version(win32_widgets) {
4233 			parentWindow = parent.parentWindow;
4234 			createWin32Window(this, "msctls_statusbar32"w, "", 0);
4235 
4236 			RECT rect;
4237 			GetWindowRect(hwnd, &rect);
4238 			idealHeight = rect.bottom - rect.top;
4239 			assert(idealHeight);
4240 		} else version(custom_widgets) {
4241 		} else static assert(false);
4242 	}
4243 
4244 	version(custom_widgets)
4245 	override void paint(ScreenPainter painter) {
4246 		this.draw3dFrame(painter, FrameStyle.sunk);
4247 		int cpos = 0;
4248 		int remainingLength = this.width;
4249 		foreach(idx, part; this.partsArray) {
4250 			auto partWidth = part.width ? part.width : ((idx + 1 == this.partsArray.length) ? remainingLength : 100);
4251 			painter.setClipRectangle(Point(cpos, 0), partWidth, height);
4252 			draw3dFrame(cpos, 0, partWidth, height, painter, FrameStyle.sunk);
4253 			painter.setClipRectangle(Point(cpos + 2, 2), partWidth - 4, height - 4);
4254 			painter.drawText(Point(cpos + 4, 0), part.content, Point(width, height), TextAlignment.VerticalCenter);
4255 			cpos += partWidth;
4256 			remainingLength -= partWidth;
4257 		}
4258 	}
4259 
4260 
4261 	version(win32_widgets) {
4262 		private const int idealHeight;
4263 		override int maxHeight() { return idealHeight; }
4264 		override int minHeight() { return idealHeight; }
4265 	} else version(custom_widgets) {
4266 		override int maxHeight() { return Window.lineHeight + 4; }
4267 		override int minHeight() { return Window.lineHeight + 4; }
4268 	} else static assert(false);
4269 }
4270 
4271 /// Displays an in-progress indicator without known values
4272 version(none)
4273 class IndefiniteProgressBar : Widget {
4274 	version(win32_widgets)
4275 	this(Widget parent = null) {
4276 		super(parent);
4277 		createWin32Window(this, "msctls_progress32"w, "", 8 /* PBS_MARQUEE */);
4278 		tabStop = false;
4279 	}
4280 	override int minHeight() { return 10; }
4281 }
4282 
4283 /// A progress bar with a known endpoint and completion amount
4284 class ProgressBar : Widget {
4285 	this(Widget parent = null) {
4286 		version(win32_widgets) {
4287 			super(parent);
4288 			createWin32Window(this, "msctls_progress32"w, "", 0);
4289 			tabStop = false;
4290 		} else version(custom_widgets) {
4291 			super(parent);
4292 			max = 100;
4293 			step = 10;
4294 			tabStop = false;
4295 		} else static assert(0);
4296 	}
4297 
4298 	version(custom_widgets)
4299 	override void paint(ScreenPainter painter) {
4300 		this.draw3dFrame(painter, FrameStyle.sunk);
4301 		painter.fillColor = Color.blue;
4302 		painter.drawRectangle(Point(0, 0), width * current / max, height);
4303 	}
4304 
4305 
4306 	version(custom_widgets) {
4307 		int current;
4308 		int max;
4309 		int step;
4310 	}
4311 
4312 	///
4313 	void advanceOneStep() {
4314 		version(win32_widgets)
4315 			SendMessageA(hwnd, PBM_STEPIT, 0, 0);
4316 		else version(custom_widgets)
4317 			addToPosition(step);
4318 		else static assert(false);
4319 	}
4320 
4321 	///
4322 	void setStepIncrement(int increment) {
4323 		version(win32_widgets)
4324 			SendMessageA(hwnd, PBM_SETSTEP, increment, 0);
4325 		else version(custom_widgets)
4326 			step = increment;
4327 		else static assert(false);
4328 	}
4329 
4330 	///
4331 	void addToPosition(int amount) {
4332 		version(win32_widgets)
4333 			SendMessageA(hwnd, PBM_DELTAPOS, amount, 0);
4334 		else version(custom_widgets)
4335 			setPosition(current + amount);
4336 		else static assert(false);
4337 	}
4338 
4339 	///
4340 	void setPosition(int pos) {
4341 		version(win32_widgets)
4342 			SendMessageA(hwnd, PBM_SETPOS, pos, 0);
4343 		else version(custom_widgets) {
4344 			current = pos;
4345 			if(current > max)
4346 				current = max;
4347 			redraw();
4348 		}
4349 		else static assert(false);
4350 	}
4351 
4352 	///
4353 	void setRange(ushort min, ushort max) {
4354 		version(win32_widgets)
4355 			SendMessageA(hwnd, PBM_SETRANGE, 0, MAKELONG(min, max));
4356 		else version(custom_widgets) {
4357 			this.max = max;
4358 		}
4359 		else static assert(false);
4360 	}
4361 
4362 	override int minHeight() { return 10; }
4363 }
4364 
4365 ///
4366 class Fieldset : Widget {
4367 	// FIXME: on Windows,it doesn't draw the background on the label
4368 	// on X, it doesn't fix the clipping rectangle for it
4369 	version(win32_widgets)
4370 		override int paddingTop() { return Window.lineHeight; }
4371 	else version(custom_widgets)
4372 		override int paddingTop() { return Window.lineHeight + 2; }
4373 	else static assert(false);
4374 	override int paddingBottom() { return 6; }
4375 	override int paddingLeft() { return 6; }
4376 	override int paddingRight() { return 6; }
4377 
4378 	override int marginLeft() { return 6; }
4379 	override int marginRight() { return 6; }
4380 	override int marginTop() { return 2; }
4381 	override int marginBottom() { return 2; }
4382 
4383 	string legend;
4384 
4385 	///
4386 	this(string legend, Widget parent) {
4387 		version(win32_widgets) {
4388 			super(parent);
4389 			this.legend = legend;
4390 			createWin32Window(this, "button"w, legend, BS_GROUPBOX);
4391 			tabStop = false;
4392 		} else version(custom_widgets) {
4393 			super(parent);
4394 			tabStop = false;
4395 			this.legend = legend;
4396 		} else static assert(0);
4397 	}
4398 
4399 	version(custom_widgets)
4400 	override void paint(ScreenPainter painter) {
4401 		painter.fillColor = Color.transparent;
4402 		painter.pen = Pen(Color.black, 1);
4403 		painter.drawRectangle(Point(0, Window.lineHeight / 2), width, height - Window.lineHeight / 2);
4404 
4405 		auto tx = painter.textSize(legend);
4406 		painter.outlineColor = Color.transparent;
4407 
4408 		static if(UsingSimpledisplayX11) {
4409 			painter.fillColor = windowBackgroundColor;
4410 			painter.drawRectangle(Point(8, 0), tx.width, tx.height);
4411 		} else version(Windows) {
4412 			auto b = SelectObject(painter.impl.hdc, GetSysColorBrush(COLOR_3DFACE));
4413 			painter.drawRectangle(Point(8, -tx.height/2), tx.width, tx.height);
4414 			SelectObject(painter.impl.hdc, b);
4415 		} else static assert(0);
4416 		painter.outlineColor = Color.black;
4417 		painter.drawText(Point(8, 0), legend);
4418 	}
4419 
4420 
4421 	override int maxHeight() {
4422 		auto m = paddingTop() + paddingBottom();
4423 		foreach(child; children) {
4424 			m += child.maxHeight();
4425 			m += child.marginBottom();
4426 			m += child.marginTop();
4427 		}
4428 		return m + 6;
4429 	}
4430 
4431 	override int minHeight() {
4432 		auto m = paddingTop() + paddingBottom();
4433 		foreach(child; children) {
4434 			m += child.minHeight();
4435 			m += child.marginBottom();
4436 			m += child.marginTop();
4437 		}
4438 		return m + 6;
4439 	}
4440 }
4441 
4442 /// Draws a line
4443 class HorizontalRule : Widget {
4444 	mixin Margin!q{ 2 };
4445 	override int minHeight() { return 2; }
4446 	override int maxHeight() { return 2; }
4447 
4448 	///
4449 	this(Widget parent = null) {
4450 		super(parent);
4451 	}
4452 
4453 	override void paint(ScreenPainter painter) {
4454 		painter.outlineColor = Color(128, 128, 128);
4455 		painter.drawLine(Point(0, 0), Point(width, 0));
4456 		painter.outlineColor = Color(223, 223, 223);
4457 		painter.drawLine(Point(0, 1), Point(width, 1));
4458 	}
4459 }
4460 
4461 /// ditto
4462 class VerticalRule : Widget {
4463 	mixin Margin!q{ 2 };
4464 	override int minWidth() { return 2; }
4465 	override int maxWidth() { return 2; }
4466 
4467 	///
4468 	this(Widget parent = null) {
4469 		super(parent);
4470 	}
4471 
4472 	override void paint(ScreenPainter painter) {
4473 		painter.outlineColor = Color(128, 128, 128);
4474 		painter.drawLine(Point(0, 0), Point(0, height));
4475 		painter.outlineColor = Color(223, 223, 223);
4476 		painter.drawLine(Point(1, 0), Point(1, height));
4477 	}
4478 }
4479 
4480 
4481 ///
4482 class Menu : Window {
4483 	void remove() {
4484 		foreach(i, child; parentWindow.children)
4485 			if(child is this) {
4486 				parentWindow.children = parentWindow.children[0 .. i] ~ parentWindow.children[i + 1 .. $];
4487 				break;
4488 			}
4489 		parentWindow.redraw();
4490 
4491 		parentWindow.releaseMouseCapture();
4492 	}
4493 
4494 	///
4495 	void addSeparator() {
4496 		version(win32_widgets)
4497 			AppendMenu(handle, MF_SEPARATOR, 0, null);
4498 		else version(custom_widgets)
4499 			auto hr = new HorizontalRule(this);
4500 		else static assert(0);
4501 	}
4502 
4503 	override int paddingTop() { return 4; }
4504 	override int paddingBottom() { return 4; }
4505 	override int paddingLeft() { return 2; }
4506 	override int paddingRight() { return 2; }
4507 
4508 	version(win32_widgets) {}
4509 	else version(custom_widgets) {
4510 		SimpleWindow dropDown;
4511 		Widget menuParent;
4512 		void popup(Widget parent) {
4513 			this.menuParent = parent;
4514 
4515 			auto w = 150;
4516 			auto h = paddingTop + paddingBottom;
4517 			Widget previousChild;
4518 			foreach(child; this.children) {
4519 				h += child.minHeight();
4520 				h += mymax(child.marginTop(), previousChild ? previousChild.marginBottom() : 0);
4521 				previousChild = child;
4522 			}
4523 
4524 			if(previousChild)
4525 			h += previousChild.marginBottom();
4526 
4527 			auto coord = parent.globalCoordinates();
4528 			dropDown.moveResize(coord.x, coord.y + parent.parentWindow.lineHeight, w, h);
4529 			this.x = 0;
4530 			this.y = 0;
4531 			this.width = dropDown.width;
4532 			this.height = dropDown.height;
4533 			this.drawableWindow = dropDown;
4534 			this.recomputeChildLayout();
4535 
4536 			static if(UsingSimpledisplayX11)
4537 				XSync(XDisplayConnection.get, 0);
4538 
4539 			dropDown.visibilityChanged = (bool visible) {
4540 				if(visible) {
4541 					this.redraw();
4542 					dropDown.grabInput();
4543 				} else {
4544 					dropDown.releaseInputGrab();
4545 				}
4546 			};
4547 
4548 			dropDown.show();
4549 
4550 			bool firstClick = true;
4551 
4552 			clickListener = this.addEventListener(EventType.click, (Event ev) {
4553 				if(firstClick) {
4554 					firstClick = false;
4555 					//return;
4556 				}
4557 				//if(ev.clientX < 0 || ev.clientY < 0 || ev.clientX > width || ev.clientY > height)
4558 					unpopup();
4559 			});
4560 		}
4561 
4562 		EventListener clickListener;
4563 	}
4564 	else static assert(false);
4565 
4566 	version(custom_widgets)
4567 	void unpopup() {
4568 		mouseLastOver = mouseLastDownOn = null;
4569 		dropDown.hide();
4570 		if(!menuParent.parentWindow.win.closed) {
4571 			if(auto maw = cast(MouseActivatedWidget) menuParent) {
4572 				maw.isDepressed = false;
4573 				maw.isHovering = false;
4574 				maw.redraw();
4575 			}
4576 			menuParent.parentWindow.win.focus();
4577 		}
4578 		clickListener.disconnect();
4579 	}
4580 
4581 	MenuItem[] items;
4582 
4583 	///
4584 	MenuItem addItem(MenuItem item) {
4585 		addChild(item);
4586 		items ~= item;
4587 		version(win32_widgets) {
4588 			AppendMenuA(handle, MF_STRING, item.action is null ? 9000 : item.action.id, toStringzInternal(item.label)); // XXX
4589 		}
4590 		return item;
4591 	}
4592 
4593 	string label;
4594 
4595 	version(win32_widgets) {
4596 		HMENU handle;
4597 		///
4598 		this(string label, Widget parent = null) {
4599 			super(parent);
4600 			this.label = label;
4601 			handle = CreatePopupMenu();
4602 		}
4603 	} else version(custom_widgets) {
4604 		///
4605 		this(string label, Widget parent = null) {
4606 
4607 			if(dropDown) {
4608 				dropDown.close();
4609 			}
4610 			dropDown = new SimpleWindow(
4611 				150, 4,
4612 				null, OpenGlOptions.no, Resizability.fixedSize, WindowTypes.dropdownMenu, WindowFlags.dontAutoShow/*, window*/);
4613 
4614 			this.label = label;
4615 
4616 			super(dropDown);
4617 		}
4618 	} else static assert(false);
4619 
4620 	override int maxHeight() { return Window.lineHeight; }
4621 	override int minHeight() { return Window.lineHeight; }
4622 
4623 	version(custom_widgets)
4624 	override void paint(ScreenPainter painter) {
4625 		this.draw3dFrame(painter, FrameStyle.risen);
4626 	}
4627 }
4628 
4629 ///
4630 class MenuItem : MouseActivatedWidget {
4631 	Menu submenu;
4632 
4633 	Action action;
4634 	string label;
4635 
4636 	override int paddingLeft() { return 4; }
4637 
4638 	override int maxHeight() { return Window.lineHeight + 4; }
4639 	override int minHeight() { return Window.lineHeight + 4; }
4640 	override int minWidth() { return Window.lineHeight * cast(int) label.length + 8; }
4641 	override int maxWidth() {
4642 		if(cast(MenuBar) parent)
4643 			return Window.lineHeight / 2 * cast(int) label.length + 8;
4644 		return int.max;
4645 	}
4646 	///
4647 	this(string lbl, Widget parent = null) {
4648 		super(parent);
4649 		//label = lbl; // FIXME
4650 		foreach(char ch; lbl) // FIXME
4651 			if(ch != '&') // FIXME
4652 				label ~= ch; // FIXME
4653 		tabStop = false; // these are selected some other way
4654 	}
4655 
4656 	version(custom_widgets)
4657 	override void paint(ScreenPainter painter) {
4658 		if(isDepressed)
4659 			this.draw3dFrame(painter, FrameStyle.sunk);
4660 		if(isHovering)
4661 			painter.outlineColor = Color.blue;
4662 		else
4663 			painter.outlineColor = Color.black;
4664 		painter.fillColor = Color.transparent;
4665 		painter.drawText(Point(cast(MenuBar) this.parent ? 4 : 20, 2), label, Point(width, height), TextAlignment.Left);
4666 		if(action && action.accelerator !is KeyEvent.init) {
4667 			painter.drawText(Point(cast(MenuBar) this.parent ? 4 : 20, 2), action.accelerator.toStr(), Point(width - 4, height), TextAlignment.Right);
4668 
4669 		}
4670 	}
4671 
4672 
4673 	///
4674 	this(Action action, Widget parent = null) {
4675 		assert(action !is null);
4676 		this(action.label);
4677 		this.action = action;
4678 		tabStop = false; // these are selected some other way
4679 	}
4680 
4681 	override void defaultEventHandler_triggered(Event event) {
4682 		if(action)
4683 		foreach(handler; action.triggered)
4684 			handler();
4685 
4686 		if(auto pmenu = cast(Menu) this.parent)
4687 			pmenu.remove();
4688 
4689 		super.defaultEventHandler_triggered(event);
4690 	}
4691 }
4692 
4693 version(win32_widgets)
4694 ///
4695 class MouseActivatedWidget : Widget {
4696 	bool isChecked() {
4697 		assert(hwnd);
4698 		return SendMessageA(hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED;
4699 
4700 	}
4701 	void isChecked(bool state) {
4702 		assert(hwnd);
4703 		SendMessageA(hwnd, BM_SETCHECK, state ? BST_CHECKED : BST_UNCHECKED, 0);
4704 
4705 	}
4706 
4707 	this(Widget parent = null) {
4708 		super(parent);
4709 	}
4710 }
4711 else version(custom_widgets)
4712 ///
4713 class MouseActivatedWidget : Widget {
4714 	bool isDepressed = false;
4715 	bool isHovering = false;
4716 	bool isChecked = false;
4717 
4718 	override void attachedToWindow(Window w) {
4719 		w.addEventListener("mouseup", delegate (Widget _this, Event ev) {
4720 			isDepressed = false;
4721 		});
4722 	}
4723 
4724 	this(Widget parent = null) {
4725 		super(parent);
4726 		addEventListener("mouseenter", delegate (Widget _this, Event ev) {
4727 			isHovering = true;
4728 			redraw();
4729 		});
4730 
4731 		addEventListener("mouseleave", delegate (Widget _this, Event ev) {
4732 			isHovering = false;
4733 			isDepressed = false;
4734 			redraw();
4735 		});
4736 
4737 		addEventListener("mousedown", delegate (Widget _this, Event ev) {
4738 			isDepressed = true;
4739 			redraw();
4740 		});
4741 
4742 		addEventListener("mouseup", delegate (Widget _this, Event ev) {
4743 			isDepressed = false;
4744 			redraw();
4745 		});
4746 	}
4747 
4748 	override void defaultEventHandler_focus(Event ev) {
4749 		super.defaultEventHandler_focus(ev);
4750 		this.redraw();
4751 	}
4752 	override void defaultEventHandler_blur(Event ev) {
4753 		super.defaultEventHandler_blur(ev);
4754 		isDepressed = false;
4755 		isHovering = false;
4756 		this.redraw();
4757 	}
4758 	override void defaultEventHandler_keydown(Event ev) {
4759 		super.defaultEventHandler_keydown(ev);
4760 		if(ev.key == Key.Space || ev.key == Key.Enter || ev.key == Key.PadEnter) {
4761 			isDepressed = true;
4762 			this.redraw();
4763 		}
4764 	}
4765 	override void defaultEventHandler_keyup(Event ev) {
4766 		super.defaultEventHandler_keyup(ev);
4767 		if(!isDepressed)
4768 			return;
4769 		isDepressed = false;
4770 		this.redraw();
4771 
4772 		auto event = new Event("triggered", this);
4773 		event.sendDirectly();
4774 	}
4775 	override void defaultEventHandler_click(Event ev) {
4776 		super.defaultEventHandler_click(ev);
4777 		if(this.tabStop)
4778 			this.focus();
4779 		auto event = new Event("triggered", this);
4780 		event.sendDirectly();
4781 	}
4782 
4783 }
4784 else static assert(false);
4785 
4786 
4787 ///
4788 class Checkbox : MouseActivatedWidget {
4789 
4790 	version(win32_widgets) {
4791 		override int maxHeight() { return 16; }
4792 		override int minHeight() { return 16; }
4793 	} else version(custom_widgets) {
4794 		override int maxHeight() { return Window.lineHeight; }
4795 		override int minHeight() { return Window.lineHeight; }
4796 	} else static assert(0);
4797 
4798 	override int marginLeft() { return 4; }
4799 
4800 	private string label;
4801 
4802 	///
4803 	this(string label, Widget parent = null) {
4804 		super(parent);
4805 		this.label = label;
4806 		version(win32_widgets) {
4807 			createWin32Window(this, "button"w, label, BS_AUTOCHECKBOX);
4808 		} else version(custom_widgets) {
4809 
4810 		} else static assert(0);
4811 	}
4812 
4813 	version(custom_widgets)
4814 	override void paint(ScreenPainter painter) {
4815 		if(isFocused()) {
4816 			painter.pen = Pen(Color.black, 1, Pen.Style.Dotted);
4817 			painter.fillColor = windowBackgroundColor;
4818 			painter.drawRectangle(Point(0, 0), width, height);
4819 			painter.pen = Pen(Color.black, 1, Pen.Style.Solid);
4820 		} else {
4821 			painter.pen = Pen(windowBackgroundColor, 1, Pen.Style.Solid);
4822 			painter.fillColor = windowBackgroundColor;
4823 			painter.drawRectangle(Point(0, 0), width, height);
4824 		}
4825 
4826 
4827 		enum buttonSize = 16;
4828 
4829 		painter.outlineColor = Color.black;
4830 		painter.fillColor = Color.white;
4831 		painter.drawRectangle(Point(2, 2), buttonSize - 2, buttonSize - 2);
4832 
4833 		if(isChecked) {
4834 			painter.pen = Pen(Color.black, 2);
4835 			// I'm using height so the checkbox is square
4836 			enum padding = 5;
4837 			painter.drawLine(Point(padding, padding), Point(buttonSize - (padding-2), buttonSize - (padding-2)));
4838 			painter.drawLine(Point(buttonSize-(padding-2), padding), Point(padding, buttonSize - (padding-2)));
4839 
4840 			painter.pen = Pen(Color.black, 1);
4841 		}
4842 
4843 		painter.drawText(Point(buttonSize + 4, 0), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
4844 	}
4845 
4846 	override void defaultEventHandler_triggered(Event ev) {
4847 		isChecked = !isChecked;
4848 
4849 		auto event = new Event(EventType.change, this);
4850 		event.dispatch();
4851 
4852 		redraw();
4853 	};
4854 
4855 }
4856 
4857 /// Adds empty space to a layout.
4858 class VerticalSpacer : Widget {
4859 	///
4860 	this(Widget parent = null) {
4861 		super(parent);
4862 	}
4863 }
4864 
4865 /// ditto
4866 class HorizontalSpacer : Widget {
4867 	///
4868 	this(Widget parent = null) {
4869 		super(parent);
4870 	}
4871 }
4872 
4873 
4874 ///
4875 class Radiobox : MouseActivatedWidget {
4876 
4877 	version(win32_widgets) {
4878 		override int maxHeight() { return 16; }
4879 		override int minHeight() { return 16; }
4880 	} else version(custom_widgets) {
4881 		override int maxHeight() { return Window.lineHeight; }
4882 		override int minHeight() { return Window.lineHeight; }
4883 	} else static assert(0);
4884 
4885 	override int marginLeft() { return 4; }
4886 
4887 	private string label;
4888 
4889 	version(win32_widgets)
4890 	this(string label, Widget parent = null) {
4891 		super(parent);
4892 		this.label = label;
4893 		createWin32Window(this, "button"w, label, BS_AUTORADIOBUTTON);
4894 	}
4895 	else version(custom_widgets)
4896 	this(string label, Widget parent = null) {
4897 		super(parent);
4898 		this.label = label;
4899 		height = 16;
4900 		width = height + 4 + cast(int) label.length * 16;
4901 	}
4902 	else static assert(false);
4903 
4904 	version(custom_widgets)
4905 	override void paint(ScreenPainter painter) {
4906 		if(isFocused) {
4907 			painter.fillColor = windowBackgroundColor;
4908 			painter.pen = Pen(Color.black, 1, Pen.Style.Dotted);
4909 		} else {
4910 			painter.fillColor = windowBackgroundColor;
4911 			painter.outlineColor = windowBackgroundColor;
4912 		}
4913 		painter.drawRectangle(Point(0, 0), width, height);
4914 
4915 		painter.pen = Pen(Color.black, 1, Pen.Style.Solid);
4916 
4917 		enum buttonSize = 16;
4918 
4919 		painter.outlineColor = Color.black;
4920 		painter.fillColor = Color.white;
4921 		painter.drawEllipse(Point(2, 2), Point(buttonSize - 2, buttonSize - 2));
4922 		if(isChecked) {
4923 			painter.outlineColor = Color.black;
4924 			painter.fillColor = Color.black;
4925 			// I'm using height so the checkbox is square
4926 			painter.drawEllipse(Point(5, 5), Point(buttonSize - 5, buttonSize - 5));
4927 		}
4928 
4929 		painter.drawText(Point(buttonSize + 4, 0), label, Point(width, height), TextAlignment.Left | TextAlignment.VerticalCenter);
4930 	}
4931 
4932 
4933 	override void defaultEventHandler_triggered(Event ev) {
4934 		isChecked = true;
4935 
4936 		if(this.parent) {
4937 			foreach(child; this.parent.children) {
4938 				if(child is this) continue;
4939 				if(auto rb = cast(Radiobox) child) {
4940 					rb.isChecked = false;
4941 					auto event = new Event(EventType.change, rb);
4942 					event.dispatch();
4943 					rb.redraw();
4944 				}
4945 			}
4946 		}
4947 
4948 		auto event = new Event(EventType.change, this);
4949 		event.dispatch();
4950 
4951 		redraw();
4952 	}
4953 
4954 }
4955 
4956 
4957 ///
4958 class Button : MouseActivatedWidget {
4959 	Color normalBgColor;
4960 	Color hoverBgColor;
4961 	Color depressedBgColor;
4962 
4963 	override int heightStretchiness() { return 3; }
4964 	override int widthStretchiness() { return 3; }
4965 
4966 	version(win32_widgets)
4967 	override void handleWmCommand(ushort cmd, ushort id) {
4968 		auto event = new Event("triggered", this);
4969 		event.dispatch();
4970 	}
4971 
4972 	version(win32_widgets) {}
4973 	else version(custom_widgets)
4974 	Color currentButtonColor() {
4975 		if(isHovering) {
4976 			return isDepressed ? depressedBgColor : hoverBgColor;
4977 		}
4978 
4979 		return normalBgColor;
4980 	}
4981 	else static assert(false);
4982 
4983 	private string label;
4984 
4985 	version(win32_widgets)
4986 	this(string label, Widget parent = null) {
4987 		// FIXME: use ideal button size instead
4988 		width = 50;
4989 		height = 30;
4990 		super(parent);
4991 		createWin32Window(this, "button"w, label, BS_PUSHBUTTON);
4992 
4993 		this.label = label;
4994 	}
4995 	else version(custom_widgets)
4996 	this(string label, Widget parent = null) {
4997 		width = 50;
4998 		height = 30;
4999 		super(parent);
5000 		normalBgColor = Color(192, 192, 192);
5001 		hoverBgColor = Color(215, 215, 215);
5002 		depressedBgColor = Color(160, 160, 160);
5003 
5004 		this.label = label;
5005 	}
5006 	else static assert(false);
5007 
5008 	override int minHeight() { return Window.lineHeight + 4; }
5009 
5010 	version(custom_widgets)
5011 	override void paint(ScreenPainter painter) {
5012 		this.draw3dFrame(painter, isDepressed ? FrameStyle.sunk : FrameStyle.risen, currentButtonColor);
5013 
5014 
5015 		painter.outlineColor = Color.black;
5016 		painter.drawText(Point(0, 0), label, Point(width, height), TextAlignment.Center | TextAlignment.VerticalCenter);
5017 
5018 		if(isFocused()) {
5019 			painter.fillColor = Color.transparent;
5020 			painter.pen = Pen(Color.black, 1, Pen.Style.Dotted);
5021 			painter.drawRectangle(Point(2, 2), width - 4, height - 4);
5022 			painter.pen = Pen(Color.black, 1, Pen.Style.Solid);
5023 
5024 		}
5025 	}
5026 
5027 }
5028 
5029 ///
5030 enum ArrowDirection {
5031 	left, ///
5032 	right, ///
5033 	up, ///
5034 	down ///
5035 }
5036 
5037 ///
5038 version(custom_widgets)
5039 class ArrowButton : Button {
5040 	///
5041 	this(ArrowDirection direction, Widget parent = null) {
5042 		super("", parent);
5043 		this.direction = direction;
5044 	}
5045 
5046 	private ArrowDirection direction;
5047 
5048 	override int minHeight() { return 16; }
5049 	override int maxHeight() { return 16; }
5050 	override int minWidth() { return 16; }
5051 	override int maxWidth() { return 16; }
5052 
5053 	override void paint(ScreenPainter painter) {
5054 		super.paint(painter);
5055 
5056 		painter.outlineColor = Color.black;
5057 		painter.fillColor = Color.black;
5058 
5059 		auto offset = Point((this.width - 16) / 2, (this.height - 16) / 2);
5060 
5061 		final switch(direction) {
5062 			case ArrowDirection.up:
5063 				painter.drawPolygon(
5064 					Point(4, 12) + offset,
5065 					Point(8, 6) + offset,
5066 					Point(12, 12) + offset
5067 				);
5068 			break;
5069 			case ArrowDirection.down:
5070 				painter.drawPolygon(
5071 					Point(4, 6) + offset,
5072 					Point(8, 12) + offset,
5073 					Point(12, 6) + offset
5074 				);
5075 			break;
5076 			case ArrowDirection.left:
5077 				painter.drawPolygon(
5078 					Point(12, 4) + offset,
5079 					Point(6, 8) + offset,
5080 					Point(12, 12) + offset
5081 				);
5082 			break;
5083 			case ArrowDirection.right:
5084 				painter.drawPolygon(
5085 					Point(6, 4) + offset,
5086 					Point(12, 8) + offset,
5087 					Point(6, 12) + offset
5088 				);
5089 			break;
5090 		}
5091 	}
5092 }
5093 
5094 private
5095 int[2] getChildPositionRelativeToParentOrigin(Widget c) nothrow {
5096 	int x, y;
5097 	Widget par = c;
5098 	while(par) {
5099 		x += par.x;
5100 		y += par.y;
5101 		par = par.parent;
5102 	}
5103 	return [x, y];
5104 }
5105 
5106 version(win32_widgets)
5107 private
5108 int[2] getChildPositionRelativeToParentHwnd(Widget c) nothrow {
5109 	int x, y;
5110 	Widget par = c;
5111 	while(par) {
5112 		x += par.x;
5113 		y += par.y;
5114 		par = par.parent;
5115 		if(par !is null && par.hwnd !is null)
5116 			break;
5117 	}
5118 	return [x, y];
5119 }
5120 
5121 ///
5122 class TextLabel : Widget {
5123 	override int maxHeight() { return Window.lineHeight; }
5124 	override int minHeight() { return Window.lineHeight; }
5125 	override int minWidth() { return 32; }
5126 
5127 	string label_;
5128 
5129 	///
5130 	@scriptable
5131 	string label() { return label_; }
5132 
5133 	///
5134 	@scriptable
5135 	void label(string l) {
5136 		label_ = l;
5137 		redraw();
5138 	}
5139 
5140 	///
5141 	this(string label, Widget parent = null) {
5142 		this.label_ = label;
5143 		this.alignment = TextAlignment.Right;
5144 		this.tabStop = false;
5145 		super(parent);
5146 	}
5147 
5148 	///
5149 	this(string label, TextAlignment alignment, Widget parent = null) {
5150 		this.label_ = label;
5151 		this.alignment = alignment;
5152 		this.tabStop = false;
5153 		super(parent);
5154 	}
5155 
5156 	TextAlignment alignment;
5157 
5158 	override void paint(ScreenPainter painter) {
5159 		painter.outlineColor = Color.black;
5160 		painter.drawText(Point(0, 0), this.label, Point(width,height), alignment);
5161 	}
5162 
5163 }
5164 
5165 version(custom_widgets)
5166 	private mixin ExperimentalTextComponent;
5167 
5168 version(win32_widgets)
5169 	alias EditableTextWidgetParent = Widget; ///
5170 else version(custom_widgets)
5171 	alias EditableTextWidgetParent = ScrollableWidget; ///
5172 else static assert(0);
5173 
5174 /// Contains the implementation of text editing
5175 abstract class EditableTextWidget : EditableTextWidgetParent {
5176 	this(Widget parent = null) {
5177 		super(parent);
5178 	}
5179 
5180 	override int minWidth() { return 16; }
5181 	override int minHeight() { return Window.lineHeight + 0; } // the +0 is to leave room for the padding
5182 	override int widthStretchiness() { return 7; }
5183 
5184 	void selectAll() {
5185 		version(win32_widgets)
5186 			SendMessage(hwnd, EM_SETSEL, 0, -1);
5187 		else version(custom_widgets) {
5188 			textLayout.selectAll();
5189 			redraw();
5190 		}
5191 	}
5192 
5193 	@property string content() {
5194 		version(win32_widgets) {
5195 			wchar[4096] bufferstack;
5196 			wchar[] buffer;
5197 			auto len = GetWindowTextLength(hwnd);
5198 			if(len < bufferstack.length)
5199 				buffer = bufferstack[0 .. len + 1];
5200 			else
5201 				buffer = new wchar[](len + 1);
5202 
5203 			auto l = GetWindowTextW(hwnd, buffer.ptr, cast(int) buffer.length);
5204 			if(l >= 0)
5205 				return makeUtf8StringFromWindowsString(buffer[0 .. l]);
5206 			else
5207 				return null;
5208 		} else version(custom_widgets) {
5209 			return textLayout.getPlainText();
5210 		} else static assert(false);
5211 	}
5212 	@property void content(string s) {
5213 		version(win32_widgets) {
5214 			WCharzBuffer bfr = WCharzBuffer(s);
5215 			SetWindowTextW(hwnd, bfr.ptr);
5216 		} else version(custom_widgets) {
5217 			textLayout.clear();
5218 			textLayout.addText(s);
5219 
5220 			{
5221 			// FIXME: it should be able to get this info easier
5222 			auto painter = draw();
5223 			textLayout.redoLayout(painter);
5224 			}
5225 			auto cbb = textLayout.contentBoundingBox();
5226 			setContentSize(cbb.width, cbb.height);
5227 			/*
5228 			textLayout.addText(ForegroundColor.red, s);
5229 			textLayout.addText(ForegroundColor.blue, TextFormat.underline, "http://dpldocs.info/");
5230 			textLayout.addText(" is the best!");
5231 			*/
5232 			redraw();
5233 		}
5234 		else static assert(false);
5235 	}
5236 
5237 	void addText(string txt) {
5238 		version(custom_widgets) {
5239 			textLayout.addText(txt);
5240 
5241 			{
5242 			// FIXME: it should be able to get this info easier
5243 			auto painter = draw();
5244 			textLayout.redoLayout(painter);
5245 			}
5246 			auto cbb = textLayout.contentBoundingBox();
5247 			setContentSize(cbb.width, cbb.height);
5248 
5249 		} else
5250 			content = content ~ txt;
5251 	}
5252 
5253 	version(custom_widgets)
5254 	override void paintFrameAndBackground(ScreenPainter painter) {
5255 		this.draw3dFrame(painter, FrameStyle.sunk, Color.white);
5256 	}
5257 
5258 	version(win32_widgets) { /* will do it with Windows calls in the classes */ }
5259 	else version(custom_widgets) {
5260 		// FIXME
5261 
5262 		Timer caretTimer;
5263 		TextLayout textLayout;
5264 
5265 		void setupCustomTextEditing() {
5266 			textLayout = new TextLayout(Rectangle(4, 2, width - 8, height - 4));
5267 		}
5268 
5269 		override void paint(ScreenPainter painter) {
5270 			if(parentWindow.win.closed) return;
5271 
5272 			textLayout.boundingBox = Rectangle(4, 2, width - 8, height - 4);
5273 
5274 			/*
5275 			painter.outlineColor = Color.white;
5276 			painter.fillColor = Color.white;
5277 			painter.drawRectangle(Point(4, 4), contentWidth, contentHeight);
5278 			*/
5279 
5280 			painter.outlineColor = Color.black;
5281 			// painter.drawText(Point(4, 4), content, Point(width - 4, height - 4));
5282 
5283 			textLayout.caretShowingOnScreen = false;
5284 
5285 			textLayout.drawInto(painter, !parentWindow.win.closed && isFocused());
5286 		}
5287 
5288 
5289 		override MouseCursor cursor() {
5290 			return GenericCursor.Text;
5291 		}
5292 	}
5293 	else static assert(false);
5294 
5295 
5296 
5297 	version(custom_widgets)
5298 	override void defaultEventHandler_mousedown(Event ev) {
5299 		super.defaultEventHandler_mousedown(ev);
5300 		if(parentWindow.win.closed) return;
5301 		if(ev.button == MouseButton.left) {
5302 			textLayout.moveCaretToPixelCoordinates(ev.clientX, ev.clientY);
5303 			this.focus();
5304 			this.parentWindow.win.grabInput();
5305 		} else if(ev.button == MouseButton.middle) {
5306 			static if(UsingSimpledisplayX11) {
5307 				getPrimarySelection(parentWindow.win, (txt) {
5308 					textLayout.insert(txt);
5309 					redraw();
5310 
5311 					auto cbb = textLayout.contentBoundingBox();
5312 					setContentSize(cbb.width, cbb.height);
5313 				});
5314 			}
5315 		}
5316 	}
5317 
5318 	version(custom_widgets)
5319 	override void defaultEventHandler_mouseup(Event ev) {
5320 		this.parentWindow.win.releaseInputGrab();
5321 		super.defaultEventHandler_mouseup(ev);
5322 	}
5323 
5324 	version(custom_widgets)
5325 	override void defaultEventHandler_mousemove(Event ev) {
5326 		super.defaultEventHandler_mousemove(ev);
5327 		if(ev.state & ModifierState.leftButtonDown) {
5328 			textLayout.selectToPixelCoordinates(ev.clientX, ev.clientY);
5329 			redraw();
5330 		}
5331 	}
5332 
5333 	version(custom_widgets)
5334 	override void defaultEventHandler_focus(Event ev) {
5335 		super.defaultEventHandler_focus(ev);
5336 		if(parentWindow.win.closed) return;
5337 		auto painter = this.draw();
5338 		textLayout.drawCaret(painter);
5339 
5340 		if(caretTimer) {
5341 			caretTimer.destroy();
5342 			caretTimer = null;
5343 		}
5344 
5345 		bool blinkingCaret = true;
5346 		static if(UsingSimpledisplayX11)
5347 			if(!Image.impl.xshmAvailable)
5348 				blinkingCaret = false; // if on a remote connection, don't waste bandwidth on an expendable blink
5349 
5350 		if(blinkingCaret)
5351 		caretTimer = new Timer(500, {
5352 			if(parentWindow.win.closed) {
5353 				caretTimer.destroy();
5354 				return;
5355 			}
5356 			if(isFocused()) {
5357 				auto painter = this.draw();
5358 				textLayout.drawCaret(painter);
5359 			} else if(textLayout.caretShowingOnScreen) {
5360 				auto painter = this.draw();
5361 				textLayout.eraseCaret(painter);
5362 			}
5363 		});
5364 	}
5365 
5366 	override void defaultEventHandler_blur(Event ev) {
5367 		super.defaultEventHandler_blur(ev);
5368 		if(parentWindow.win.closed) return;
5369 		version(custom_widgets) {
5370 			auto painter = this.draw();
5371 			textLayout.eraseCaret(painter);
5372 			if(caretTimer) {
5373 				caretTimer.destroy();
5374 				caretTimer = null;
5375 			}
5376 		}
5377 
5378 		auto evt = new Event(EventType.change, this);
5379 		evt.stringValue = this.content;
5380 		evt.dispatch();
5381 	}
5382 
5383 	version(custom_widgets)
5384 	override void defaultEventHandler_char(Event ev) {
5385 		super.defaultEventHandler_char(ev);
5386 		textLayout.insert(ev.character);
5387 		redraw();
5388 
5389 		// FIXME: too inefficient
5390 		auto cbb = textLayout.contentBoundingBox();
5391 		setContentSize(cbb.width, cbb.height);
5392 	}
5393 	version(custom_widgets)
5394 	override void defaultEventHandler_keydown(Event ev) {
5395 		//super.defaultEventHandler_keydown(ev);
5396 		switch(ev.key) {
5397 			case Key.Delete:
5398 				textLayout.delete_();
5399 				redraw();
5400 			break;
5401 			case Key.Left:
5402 				textLayout.moveLeft();
5403 				redraw();
5404 			break;
5405 			case Key.Right:
5406 				textLayout.moveRight();
5407 				redraw();
5408 			break;
5409 			case Key.Up:
5410 				textLayout.moveUp();
5411 				redraw();
5412 			break;
5413 			case Key.Down:
5414 				textLayout.moveDown();
5415 				redraw();
5416 			break;
5417 			case Key.Home:
5418 				textLayout.moveHome();
5419 				redraw();
5420 			break;
5421 			case Key.End:
5422 				textLayout.moveEnd();
5423 				redraw();
5424 			break;
5425 			case Key.PageUp:
5426 				foreach(i; 0 .. 32)
5427 				textLayout.moveUp();
5428 				redraw();
5429 			break;
5430 			case Key.PageDown:
5431 				foreach(i; 0 .. 32)
5432 				textLayout.moveDown();
5433 				redraw();
5434 			break;
5435 
5436 			default:
5437 				 {} // intentionally blank, let "char" handle it
5438 		}
5439 		/*
5440 		if(ev.key == Key.Backspace) {
5441 			textLayout.backspace();
5442 			redraw();
5443 		}
5444 		*/
5445 		ensureVisibleInScroll(textLayout.caretBoundingBox());
5446 	}
5447 
5448 
5449 }
5450 
5451 ///
5452 class LineEdit : EditableTextWidget {
5453 	// FIXME: hack
5454 	version(custom_widgets) {
5455 	override bool showingVerticalScroll() { return false; }
5456 	override bool showingHorizontalScroll() { return false; }
5457 	}
5458 
5459 	///
5460 	this(Widget parent = null) {
5461 		super(parent);
5462 		version(win32_widgets) {
5463 			createWin32Window(this, "edit"w, "", 
5464 				0, WS_EX_CLIENTEDGE);//|WS_HSCROLL|ES_AUTOHSCROLL);
5465 		} else version(custom_widgets) {
5466 			setupCustomTextEditing();
5467 			addEventListener("char", delegate(Widget _this, Event ev) {
5468 				if(ev.character == '\n')
5469 					ev.preventDefault();
5470 			});
5471 		} else static assert(false);
5472 	}
5473 	override int maxHeight() { return Window.lineHeight + 4; }
5474 	override int minHeight() { return Window.lineHeight + 4; }
5475 }
5476 
5477 ///
5478 class TextEdit : EditableTextWidget {
5479 	///
5480 	this(Widget parent = null) {
5481 		super(parent);
5482 		version(win32_widgets) {
5483 			createWin32Window(this, "edit"w, "", 
5484 				0|WS_VSCROLL|WS_HSCROLL|ES_MULTILINE|ES_WANTRETURN|ES_AUTOHSCROLL|ES_AUTOVSCROLL, WS_EX_CLIENTEDGE);
5485 		} else version(custom_widgets) {
5486 			setupCustomTextEditing();
5487 		} else static assert(false);
5488 	}
5489 	override int maxHeight() { return int.max; }
5490 	override int heightStretchiness() { return 7; }
5491 }
5492 
5493 
5494 
5495 ///
5496 class MessageBox : Window {
5497 	private string message;
5498 	MessageBoxButton buttonPressed = MessageBoxButton.None;
5499 	///
5500 	this(string message, string[] buttons = ["OK"], MessageBoxButton[] buttonIds = [MessageBoxButton.OK]) {
5501 		super(300, 100);
5502 
5503 		assert(buttons.length);
5504 		assert(buttons.length ==  buttonIds.length);
5505 
5506 		this.message = message;
5507 
5508 		int buttonsWidth = cast(int) buttons.length * 50 + (cast(int) buttons.length - 1) * 16;
5509 
5510 		int x = this.width / 2 - buttonsWidth / 2;
5511 
5512 		foreach(idx, buttonText; buttons) {
5513 			auto button = new Button(buttonText, this);
5514 			button.x = x;
5515 			button.y = height - (button.height + 10);
5516 			button.addEventListener(EventType.triggered, ((size_t idx) { return () {
5517 				this.buttonPressed = buttonIds[idx];
5518 				win.close();
5519 			}; })(idx));
5520 
5521 			button.registerMovement();
5522 			x += button.width;
5523 			x += 16;
5524 			if(idx == 0)
5525 				button.focus();
5526 		}
5527 
5528 		win.show();
5529 		redraw();
5530 	}
5531 
5532 	override void paint(ScreenPainter painter) {
5533 		super.paint(painter);
5534 		painter.outlineColor = Color.black;
5535 		painter.drawText(Point(0, 0), message, Point(width, height / 2), TextAlignment.Center | TextAlignment.VerticalCenter);
5536 	}
5537 
5538 	// this one is all fixed position
5539 	override void recomputeChildLayout() {}
5540 }
5541 
5542 ///
5543 enum MessageBoxStyle {
5544 	OK, ///
5545 	OKCancel, ///
5546 	RetryCancel, ///
5547 	YesNo, ///
5548 	YesNoCancel, ///
5549 	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.
5550 }
5551 
5552 ///
5553 enum MessageBoxIcon {
5554 	None, ///
5555 	Info, ///
5556 	Warning, ///
5557 	Error ///
5558 }
5559 
5560 /// Identifies the button the user pressed on a message box.
5561 enum MessageBoxButton {
5562 	None, /// The user closed the message box without clicking any of the buttons.
5563 	OK, ///
5564 	Cancel, ///
5565 	Retry, ///
5566 	Yes, ///
5567 	No, ///
5568 	Continue ///
5569 }
5570 
5571 
5572 /++
5573 	Displays a modal message box, blocking until the user dismisses it.
5574 
5575 	Returns: the button pressed.
5576 +/
5577 MessageBoxButton messageBox(string title, string message, MessageBoxStyle style = MessageBoxStyle.OK, MessageBoxIcon icon = MessageBoxIcon.None) {
5578 	version(win32_widgets) {
5579 		WCharzBuffer t = WCharzBuffer(title);
5580 		WCharzBuffer m = WCharzBuffer(message);
5581 		UINT type;
5582 		with(MessageBoxStyle)
5583 		final switch(style) {
5584 			case OK: type |= MB_OK; break;
5585 			case OKCancel: type |= MB_OKCANCEL; break;
5586 			case RetryCancel: type |= MB_RETRYCANCEL; break;
5587 			case YesNo: type |= MB_YESNO; break;
5588 			case YesNoCancel: type |= MB_YESNOCANCEL; break;
5589 			case RetryCancelContinue: type |= MB_CANCELTRYCONTINUE; break;
5590 		}
5591 		with(MessageBoxIcon)
5592 		final switch(icon) {
5593 			case None: break;
5594 			case Info: type |= MB_ICONINFORMATION; break;
5595 			case Warning: type |= MB_ICONWARNING; break;
5596 			case Error: type |= MB_ICONERROR; break;
5597 		}
5598 		switch(MessageBoxW(null, m.ptr, t.ptr, type)) {
5599 			case IDOK: return MessageBoxButton.OK;
5600 			case IDCANCEL: return MessageBoxButton.Cancel;
5601 			case IDTRYAGAIN, IDRETRY: return MessageBoxButton.Retry;
5602 			case IDYES: return MessageBoxButton.Yes;
5603 			case IDNO: return MessageBoxButton.No;
5604 			case IDCONTINUE: return MessageBoxButton.Continue;
5605 			default: return MessageBoxButton.None;
5606 		}
5607 	} else {
5608 		string[] buttons;
5609 		MessageBoxButton[] buttonIds;
5610 		with(MessageBoxStyle)
5611 		final switch(style) {
5612 			case OK:
5613 				buttons = ["OK"];
5614 				buttonIds = [MessageBoxButton.OK];
5615 			break;
5616 			case OKCancel:
5617 				buttons = ["OK", "Cancel"];
5618 				buttonIds = [MessageBoxButton.OK, MessageBoxButton.Cancel];
5619 			break;
5620 			case RetryCancel:
5621 				buttons = ["Retry", "Cancel"];
5622 				buttonIds = [MessageBoxButton.Retry, MessageBoxButton.Cancel];
5623 			break;
5624 			case YesNo:
5625 				buttons = ["Yes", "No"];
5626 				buttonIds = [MessageBoxButton.Yes, MessageBoxButton.No];
5627 			break;
5628 			case YesNoCancel:
5629 				buttons = ["Yes", "No", "Cancel"];
5630 				buttonIds = [MessageBoxButton.Yes, MessageBoxButton.No, MessageBoxButton.Cancel];
5631 			break;
5632 			case RetryCancelContinue:
5633 				buttons = ["Try Again", "Cancel", "Continue"];
5634 				buttonIds = [MessageBoxButton.Retry, MessageBoxButton.Cancel, MessageBoxButton.Continue];
5635 			break;
5636 		}
5637 		auto mb = new MessageBox(message, buttons, buttonIds);
5638 		EventLoop el = EventLoop.get;
5639 		el.run(() { return !mb.win.closed; });
5640 		return mb.buttonPressed;
5641 	}
5642 }
5643 
5644 /// ditto
5645 int messageBox(string message, MessageBoxStyle style = MessageBoxStyle.OK, MessageBoxIcon icon = MessageBoxIcon.None) {
5646 	return messageBox(null, message, style, icon);
5647 }
5648 
5649 
5650 
5651 ///
5652 alias void delegate(Widget handlerAttachedTo, Event event) EventHandler;
5653 
5654 ///
5655 struct EventListener {
5656 	Widget widget;
5657 	string event;
5658 	EventHandler handler;
5659 	bool useCapture;
5660 
5661 	///
5662 	void disconnect() {
5663 		widget.removeEventListener(this);
5664 	}
5665 }
5666 
5667 ///
5668 enum EventType : string {
5669 	click = "click", ///
5670 
5671 	mouseenter = "mouseenter", ///
5672 	mouseleave = "mouseleave", ///
5673 	mousein = "mousein", ///
5674 	mouseout = "mouseout", ///
5675 	mouseup = "mouseup", ///
5676 	mousedown = "mousedown", ///
5677 	mousemove = "mousemove", ///
5678 
5679 	keydown = "keydown", ///
5680 	keyup = "keyup", ///
5681 	char_ = "char", ///
5682 
5683 	focus = "focus", ///
5684 	blur = "blur", ///
5685 
5686 	triggered = "triggered", ///
5687 
5688 	change = "change", ///
5689 }
5690 
5691 ///
5692 class Event {
5693 	///
5694 	this(string eventName, Widget target) {
5695 		this.eventName = eventName;
5696 		this.srcElement = target;
5697 	}
5698 
5699 	/// Prevents the default event handler (if there is one) from being called
5700 	void preventDefault() {
5701 		lastDefaultPrevented = true;
5702 		defaultPrevented = true;
5703 	}
5704 
5705 	/// Stops the event propagation immediately.
5706 	void stopPropagation() {
5707 		propagationStopped = true;
5708 	}
5709 
5710 	private bool defaultPrevented;
5711 	private bool propagationStopped;
5712 	private string eventName;
5713 
5714 	Widget srcElement; ///
5715 	alias srcElement target;
5716 
5717 	Widget relatedTarget; ///
5718 
5719 	// for mouse events
5720 	int clientX; ///
5721 	int clientY; ///
5722 
5723 	int viewportX; ///
5724 	int viewportY; ///
5725 
5726 	int button; ///
5727 	int buttonLinear; ///
5728 
5729 	// for key events
5730 	Key key; ///
5731 
5732 	KeyEvent originalKeyEvent;
5733 
5734 	// char character events
5735 	dchar character; ///
5736 
5737 	// for several event types
5738 	int state; ///
5739 
5740 	// for change events
5741 	int intValue; ///
5742 	string stringValue; ///
5743 
5744 	bool shiftKey; ///
5745 
5746 	private bool isBubbling;
5747 
5748 	private void adjustScrolling() {
5749 	version(custom_widgets) { // TEMP
5750 		viewportX = clientX;
5751 		viewportY = clientY;
5752 		if(auto se = cast(ScrollableWidget) srcElement) {
5753 			clientX += se.scrollOrigin.x;
5754 			clientY += se.scrollOrigin.y;
5755 		}
5756 	}
5757 	}
5758 
5759 	/// this sends it only to the target. If you want propagation, use dispatch() instead.
5760 	void sendDirectly() {
5761 		if(srcElement is null)
5762 			return;
5763 
5764 		//debug if(eventName != "mousemove" && target !is null && target.parentWindow && target.parentWindow.devTools)
5765 			//target.parentWindow.devTools.log("Event ", eventName, " dispatched directly to ", srcElement);
5766 
5767 		adjustScrolling();
5768 
5769 		auto e = srcElement;
5770 
5771 		if(eventName in e.bubblingEventHandlers)
5772 		foreach(handler; e.bubblingEventHandlers[eventName])
5773 			handler(e, this);
5774 
5775 		if(!defaultPrevented)
5776 			if(eventName in e.defaultEventHandlers)
5777 				e.defaultEventHandlers[eventName](e, this);
5778 	}
5779 
5780 	/// this dispatches the element using the capture -> target -> bubble process
5781 	void dispatch() {
5782 		if(srcElement is null)
5783 			return;
5784 
5785 		//debug if(eventName != "mousemove" && target !is null && target.parentWindow && target.parentWindow.devTools)
5786 			//target.parentWindow.devTools.log("Event ", eventName, " dispatched to ", srcElement);
5787 
5788 		adjustScrolling();
5789 		// first capture, then bubble
5790 
5791 		Widget[] chain;
5792 		Widget curr = srcElement;
5793 		while(curr) {
5794 			auto l = curr;
5795 			chain ~= l;
5796 			curr = curr.parent;
5797 		}
5798 
5799 		isBubbling = false;
5800 
5801 		foreach_reverse(e; chain) {
5802 			if(eventName in e.capturingEventHandlers)
5803 			foreach(handler; e.capturingEventHandlers[eventName])
5804 				if(handler !is null)
5805 					handler(e, this);
5806 
5807 			// the default on capture should really be to always do nothing
5808 
5809 			//if(!defaultPrevented)
5810 			//	if(eventName in e.defaultEventHandlers)
5811 			//		e.defaultEventHandlers[eventName](e.element, this);
5812 
5813 			if(propagationStopped)
5814 				break;
5815 		}
5816 
5817 		isBubbling = true;
5818 		if(!propagationStopped)
5819 		foreach(e; chain) {
5820 			if(eventName in e.bubblingEventHandlers)
5821 			foreach(handler; e.bubblingEventHandlers[eventName])
5822 				if(handler !is null)
5823 					handler(e, this);
5824 
5825 			if(propagationStopped)
5826 				break;
5827 		}
5828 
5829 		if(!defaultPrevented)
5830 		foreach(e; chain) {
5831 			if(eventName in e.defaultEventHandlers)
5832 				e.defaultEventHandlers[eventName](e, this);
5833 		}
5834 	}
5835 }
5836 
5837 private bool isAParentOf(Widget a, Widget b) {
5838 	if(a is null || b is null)
5839 		return false;
5840 
5841 	while(b !is null) {
5842 		if(a is b)
5843 			return true;
5844 		b = b.parent;
5845 	}
5846 
5847 	return false;
5848 }
5849 
5850 private struct WidgetAtPointResponse {
5851 	Widget widget;
5852 	int x;
5853 	int y;
5854 }
5855 
5856 private WidgetAtPointResponse widgetAtPoint(Widget starting, int x, int y) {
5857 	assert(starting !is null);
5858 	auto child = starting.getChildAtPosition(x, y);
5859 	while(child) {
5860 		if(child.hidden)
5861 			continue;
5862 		starting = child;
5863 		x -= child.x;
5864 		y -= child.y;
5865 		auto r = starting.widgetAtPoint(x, y);//starting.getChildAtPosition(x, y);
5866 		child = r.widget;
5867 		if(child is starting)
5868 			break;
5869 	}
5870 	return WidgetAtPointResponse(starting, x, y);
5871 }
5872 
5873 version(win32_widgets) {
5874 	import core.sys.windows.windows;
5875 	import gdi = core.sys.windows.wingdi;
5876 	// import win32.commctrl;
5877 	// import win32.winuser;
5878 	import core.sys.windows.commctrl;
5879 
5880 	pragma(lib, "comctl32");
5881 	shared static this() {
5882 		// http://msdn.microsoft.com/en-us/library/windows/desktop/bb775507(v=vs.85).aspx
5883 		INITCOMMONCONTROLSEX ic;
5884 		ic.dwSize = cast(DWORD) ic.sizeof;
5885 		ic.dwICC = ICC_UPDOWN_CLASS | ICC_WIN95_CLASSES | ICC_BAR_CLASSES | ICC_PROGRESS_CLASS | ICC_COOL_CLASSES | ICC_STANDARD_CLASSES | ICC_USEREX_CLASSES;
5886 		if(!InitCommonControlsEx(&ic)) {
5887 			//import std.stdio; writeln("ICC failed");
5888 		}
5889 	}
5890 
5891 
5892 	// everything from here is just win32 headers copy pasta
5893 private:
5894 extern(Windows):
5895 
5896 	alias HANDLE HMENU;
5897 	HMENU CreateMenu();
5898 	bool SetMenu(HWND, HMENU);
5899 	HMENU CreatePopupMenu();
5900 	enum MF_POPUP = 0x10;
5901 	enum MF_STRING = 0;
5902 
5903 
5904 	BOOL InitCommonControlsEx(const INITCOMMONCONTROLSEX*);
5905 	struct INITCOMMONCONTROLSEX {
5906 		DWORD dwSize;
5907 		DWORD dwICC;
5908 	}
5909 	enum HINST_COMMCTRL = cast(HINSTANCE) (-1);
5910 enum {
5911         IDB_STD_SMALL_COLOR,
5912         IDB_STD_LARGE_COLOR,
5913         IDB_VIEW_SMALL_COLOR = 4,
5914         IDB_VIEW_LARGE_COLOR = 5
5915 }
5916 enum {
5917         STD_CUT,
5918         STD_COPY,
5919         STD_PASTE,
5920         STD_UNDO,
5921         STD_REDOW,
5922         STD_DELETE,
5923         STD_FILENEW,
5924         STD_FILEOPEN,
5925         STD_FILESAVE,
5926         STD_PRINTPRE,
5927         STD_PROPERTIES,
5928         STD_HELP,
5929         STD_FIND,
5930         STD_REPLACE,
5931         STD_PRINT // = 14
5932 }
5933 
5934 alias HANDLE HIMAGELIST;
5935 	HIMAGELIST ImageList_Create(int, int, UINT, int, int);
5936 	int ImageList_Add(HIMAGELIST, HBITMAP, HBITMAP);
5937         BOOL ImageList_Destroy(HIMAGELIST);
5938 
5939 uint MAKELONG(ushort a, ushort b) {
5940         return cast(uint) ((b << 16) | a);
5941 }
5942 
5943 
5944 struct TBBUTTON {
5945 	int   iBitmap;
5946 	int   idCommand;
5947 	BYTE  fsState;
5948 	BYTE  fsStyle;
5949 	BYTE[2]  bReserved; // FIXME: isn't that different on 64 bit?
5950 	DWORD dwData;
5951 	int   iString;
5952 }
5953 
5954 	enum {
5955 		TB_ADDBUTTONSA   = WM_USER + 20,
5956 		TB_INSERTBUTTONA = WM_USER + 21,
5957 		TB_GETIDEALSIZE = WM_USER + 99,
5958 	}
5959 
5960 struct SIZE {
5961 	LONG cx;
5962 	LONG cy;
5963 }
5964 
5965 
5966 enum {
5967 	TBSTATE_CHECKED       = 1,
5968 	TBSTATE_PRESSED       = 2,
5969 	TBSTATE_ENABLED       = 4,
5970 	TBSTATE_HIDDEN        = 8,
5971 	TBSTATE_INDETERMINATE = 16,
5972 	TBSTATE_WRAP          = 32
5973 }
5974 
5975 
5976 
5977 enum {
5978 	ILC_COLOR    = 0,
5979 	ILC_COLOR4   = 4,
5980 	ILC_COLOR8   = 8,
5981 	ILC_COLOR16  = 16,
5982 	ILC_COLOR24  = 24,
5983 	ILC_COLOR32  = 32,
5984 	ILC_COLORDDB = 254,
5985 	ILC_MASK     = 1,
5986 	ILC_PALETTE  = 2048
5987 }
5988 
5989 
5990 alias TBBUTTON*       PTBBUTTON, LPTBBUTTON;
5991 
5992 
5993 enum {
5994 	TB_ENABLEBUTTON          = WM_USER + 1,
5995 	TB_CHECKBUTTON,
5996 	TB_PRESSBUTTON,
5997 	TB_HIDEBUTTON,
5998 	TB_INDETERMINATE, //     = WM_USER + 5,
5999 	TB_ISBUTTONENABLED       = WM_USER + 9,
6000 	TB_ISBUTTONCHECKED,
6001 	TB_ISBUTTONPRESSED,
6002 	TB_ISBUTTONHIDDEN,
6003 	TB_ISBUTTONINDETERMINATE, // = WM_USER + 13,
6004 	TB_SETSTATE              = WM_USER + 17,
6005 	TB_GETSTATE              = WM_USER + 18,
6006 	TB_ADDBITMAP             = WM_USER + 19,
6007 	TB_DELETEBUTTON          = WM_USER + 22,
6008 	TB_GETBUTTON,
6009 	TB_BUTTONCOUNT,
6010 	TB_COMMANDTOINDEX,
6011 	TB_SAVERESTOREA,
6012 	TB_CUSTOMIZE,
6013 	TB_ADDSTRINGA,
6014 	TB_GETITEMRECT,
6015 	TB_BUTTONSTRUCTSIZE,
6016 	TB_SETBUTTONSIZE,
6017 	TB_SETBITMAPSIZE,
6018 	TB_AUTOSIZE, //          = WM_USER + 33,
6019 	TB_GETTOOLTIPS           = WM_USER + 35,
6020 	TB_SETTOOLTIPS           = WM_USER + 36,
6021 	TB_SETPARENT             = WM_USER + 37,
6022 	TB_SETROWS               = WM_USER + 39,
6023 	TB_GETROWS,
6024 	TB_GETBITMAPFLAGS,
6025 	TB_SETCMDID,
6026 	TB_CHANGEBITMAP,
6027 	TB_GETBITMAP,
6028 	TB_GETBUTTONTEXTA,
6029 	TB_REPLACEBITMAP, //     = WM_USER + 46,
6030 	TB_GETBUTTONSIZE         = WM_USER + 58,
6031 	TB_SETBUTTONWIDTH        = WM_USER + 59,
6032 	TB_GETBUTTONTEXTW        = WM_USER + 75,
6033 	TB_SAVERESTOREW          = WM_USER + 76,
6034 	TB_ADDSTRINGW            = WM_USER + 77,
6035 }
6036 
6037 extern(Windows)
6038 BOOL EnumChildWindows(HWND, WNDENUMPROC, LPARAM);
6039 
6040 alias extern(Windows) BOOL function (HWND, LPARAM) WNDENUMPROC;
6041 
6042 
6043 	enum {
6044 		TB_SETINDENT = WM_USER + 47,
6045 		TB_SETIMAGELIST,
6046 		TB_GETIMAGELIST,
6047 		TB_LOADIMAGES,
6048 		TB_GETRECT,
6049 		TB_SETHOTIMAGELIST,
6050 		TB_GETHOTIMAGELIST,
6051 		TB_SETDISABLEDIMAGELIST,
6052 		TB_GETDISABLEDIMAGELIST,
6053 		TB_SETSTYLE,
6054 		TB_GETSTYLE,
6055 		//TB_GETBUTTONSIZE,
6056 		//TB_SETBUTTONWIDTH,
6057 		TB_SETMAXTEXTROWS,
6058 		TB_GETTEXTROWS // = WM_USER + 61
6059 	}
6060 
6061 enum {
6062 	CCM_FIRST            = 0x2000,
6063 	CCM_LAST             = CCM_FIRST + 0x200,
6064 	CCM_SETBKCOLOR       = 8193,
6065 	CCM_SETCOLORSCHEME   = 8194,
6066 	CCM_GETCOLORSCHEME   = 8195,
6067 	CCM_GETDROPTARGET    = 8196,
6068 	CCM_SETUNICODEFORMAT = 8197,
6069 	CCM_GETUNICODEFORMAT = 8198,
6070 	CCM_SETVERSION       = 0x2007,
6071 	CCM_GETVERSION       = 0x2008,
6072 	CCM_SETNOTIFYWINDOW  = 0x2009
6073 }
6074 
6075 
6076 enum {
6077 	PBM_SETRANGE     = WM_USER + 1,
6078 	PBM_SETPOS,
6079 	PBM_DELTAPOS,
6080 	PBM_SETSTEP,
6081 	PBM_STEPIT,   // = WM_USER + 5
6082 	PBM_SETRANGE32   = 1030,
6083 	PBM_GETRANGE,
6084 	PBM_GETPOS,
6085 	PBM_SETBARCOLOR, // = 1033
6086 	PBM_SETBKCOLOR   = CCM_SETBKCOLOR
6087 }
6088 
6089 enum {
6090 	PBS_SMOOTH   = 1,
6091 	PBS_VERTICAL = 4
6092 }
6093 
6094 enum {
6095         ICC_LISTVIEW_CLASSES = 1,
6096         ICC_TREEVIEW_CLASSES = 2,
6097         ICC_BAR_CLASSES      = 4,
6098         ICC_TAB_CLASSES      = 8,
6099         ICC_UPDOWN_CLASS     = 16,
6100         ICC_PROGRESS_CLASS   = 32,
6101         ICC_HOTKEY_CLASS     = 64,
6102         ICC_ANIMATE_CLASS    = 128,
6103         ICC_WIN95_CLASSES    = 255,
6104         ICC_DATE_CLASSES     = 256,
6105         ICC_USEREX_CLASSES   = 512,
6106         ICC_COOL_CLASSES     = 1024,
6107 	ICC_STANDARD_CLASSES = 0x00004000,
6108 }
6109 
6110 	enum WM_USER = 1024;
6111 }
6112 
6113 version(win32_widgets)
6114 	pragma(lib, "comdlg32");
6115 
6116 
6117 ///
6118 enum GenericIcons : ushort {
6119 	None, ///
6120 	// these happen to match the win32 std icons numerically if you just subtract one from the value
6121 	Cut, ///
6122 	Copy, ///
6123 	Paste, ///
6124 	Undo, ///
6125 	Redo, ///
6126 	Delete, ///
6127 	New, ///
6128 	Open, ///
6129 	Save, ///
6130 	PrintPreview, ///
6131 	Properties, ///
6132 	Help, ///
6133 	Find, ///
6134 	Replace, ///
6135 	Print, ///
6136 }
6137 
6138 /// Represents a mouse cursor (aka the mouse pointer, the image seen on screen that indicates where the mouse is pointing).
6139 /// See [GenericCursor]
6140 class MouseCursor {
6141 	int osId;
6142 	bool isStockCursor;
6143 	private this(int osId) {
6144 		this.osId = osId;
6145 		this.isStockCursor = true;
6146 	}
6147 
6148 	// https://msdn.microsoft.com/en-us/library/windows/desktop/ms648385(v=vs.85).aspx
6149 	this(int xHotSpot, int yHotSpot, ubyte[] andMask, ubyte[] xorMask) {}
6150 
6151 	version(Windows) {
6152 		HCURSOR cursor_;
6153 		HCURSOR cursorHandle() {
6154 			if(cursor_ is null)
6155 				cursor_ = LoadCursor(null, MAKEINTRESOURCE(osId));
6156 			return cursor_;
6157 		}
6158 
6159 	} else static if(UsingSimpledisplayX11) {
6160 		Cursor cursor_ = None;
6161 		int xDisplaySequence;
6162 
6163 		Cursor cursorHandle() {
6164 			if(this.osId == None)
6165 				return None;
6166 
6167 			// we need to reload if we on a new X connection
6168 			if(cursor_ == None || XDisplayConnection.connectionSequenceNumber != xDisplaySequence) {
6169 				cursor_ = XCreateFontCursor(XDisplayConnection.get(), this.osId);
6170 				xDisplaySequence = XDisplayConnection.connectionSequenceNumber;
6171 			}
6172 			return cursor_;
6173 		}
6174 	}
6175 }
6176 
6177 // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
6178 // https://tronche.com/gui/x/xlib/appendix/b/
6179 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms648391(v=vs.85).aspx
6180 ///
6181 enum GenericCursorType {
6182 	Default, /// The default arrow pointer.
6183 	Wait, /// A cursor indicating something is loading and the user must wait.
6184 	Hand, /// A pointing finger, like the one used hovering over hyperlinks in a web browser.
6185 	Help, /// A cursor indicating the user can get help about the pointer location.
6186 	Cross, /// A crosshair.
6187 	Text, /// An i-beam shape, typically used to indicate text selection is possible.
6188 	Move, /// Pointer indicating movement is possible.
6189 	UpArrow, /// An arrow pointing straight up.
6190 }
6191 
6192 /// You get one by `GenericCursor.SomeTime`. See [GenericCursorType] for a list of types.
6193 static struct GenericCursor {
6194 	static:
6195 	///
6196 	MouseCursor opDispatch(string str)() if(__traits(hasMember, GenericCursorType, str)) {
6197 		static MouseCursor mc;
6198 
6199 		auto type = __traits(getMember, GenericCursorType, str);
6200 
6201 		if(mc is null) {
6202 
6203 			version(Windows) {
6204 				int osId;
6205 				final switch(type) {
6206 					case GenericCursorType.Default: osId = IDC_ARROW; break;
6207 					case GenericCursorType.Wait: osId = IDC_WAIT; break;
6208 					case GenericCursorType.Hand: osId = IDC_HAND; break;
6209 					case GenericCursorType.Help: osId = IDC_HELP; break;
6210 					case GenericCursorType.Cross: osId = IDC_CROSS; break;
6211 					case GenericCursorType.Text: osId = IDC_IBEAM; break;
6212 					case GenericCursorType.Move: osId = IDC_SIZEALL; break;
6213 					case GenericCursorType.UpArrow: osId = IDC_UPARROW; break;
6214 				}
6215 			} else static if(UsingSimpledisplayX11) {
6216 				int osId;
6217 				final switch(type) {
6218 					case GenericCursorType.Default: osId = None; break;
6219 					case GenericCursorType.Wait: osId = 150 /* XC_watch */; break;
6220 					case GenericCursorType.Hand: osId = 60 /* XC_hand2 */; break;
6221 					case GenericCursorType.Help: osId = 92 /* XC_question_arrow */; break;
6222 					case GenericCursorType.Cross: osId = 34 /* XC_crosshair */; break;
6223 					case GenericCursorType.Text: osId = 152 /* XC_xterm */; break;
6224 					case GenericCursorType.Move: osId = 52 /* XC_fleur */; break;
6225 					case GenericCursorType.UpArrow: osId = 22 /* XC_center_ptr */; break;
6226 				}
6227 
6228 			} else static assert(0);
6229 
6230 			mc = new MouseCursor(osId);
6231 		}
6232 		return mc;
6233 	}
6234 }
6235 
6236 ///
6237 void getOpenFileName(
6238 	void delegate(string) onOK,
6239 	string prefilledName = null,
6240 	string[] filters = null
6241 )
6242 {
6243 	return getFileName(true, onOK, prefilledName, filters);
6244 }
6245 
6246 ///
6247 void getSaveFileName(
6248 	void delegate(string) onOK,
6249 	string prefilledName = null,
6250 	string[] filters = null
6251 )
6252 {
6253 	return getFileName(false, onOK, prefilledName, filters);
6254 }
6255 
6256 void getFileName(
6257 	bool openOrSave,
6258 	void delegate(string) onOK,
6259 	string prefilledName = null,
6260 	string[] filters = null,
6261 )
6262 {
6263 
6264 	version(win32_widgets) {
6265 	/*
6266 	Ofn.lStructSize = sizeof(OPENFILENAME); 
6267 	Ofn.hwndOwner = hWnd; 
6268 	Ofn.lpstrFilter = szFilter; 
6269 	Ofn.lpstrFile= szFile; 
6270 	Ofn.nMaxFile = sizeof(szFile)/ sizeof(*szFile); 
6271 	Ofn.lpstrFileTitle = szFileTitle; 
6272 	Ofn.nMaxFileTitle = sizeof(szFileTitle); 
6273 	Ofn.lpstrInitialDir = (LPSTR)NULL; 
6274 	Ofn.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT; 
6275 	Ofn.lpstrTitle = szTitle; 
6276 	 */
6277 
6278 
6279 		wchar[1024] file = 0;
6280 		makeWindowsString(prefilledName, file[]);
6281 		OPENFILENAME ofn;
6282 		ofn.lStructSize = ofn.sizeof;
6283 		ofn.lpstrFile = file.ptr;
6284 		ofn.nMaxFile = file.length;
6285 		if(openOrSave ? GetOpenFileName(&ofn) : GetSaveFileName(&ofn)) {
6286 			onOK(makeUtf8StringFromWindowsString(ofn.lpstrFile));
6287 		}
6288 	} else version(custom_widgets) {
6289 		auto picker = new FilePicker(prefilledName);
6290 		picker.onOK = onOK;
6291 		picker.show();
6292 	}
6293 }
6294 
6295 version(custom_widgets)
6296 private
6297 class FilePicker : Dialog {
6298 	void delegate(string) onOK;
6299 	LineEdit lineEdit;
6300 	this(string prefilledName, Window owner = null) {
6301 		super(300, 200, "Choose File..."); // owner);
6302 
6303 		auto listWidget = new ListWidget(this);
6304 
6305 		lineEdit = new LineEdit(this);
6306 		lineEdit.focus();
6307 		lineEdit.addEventListener("char", (Event event) {
6308 			if(event.character == '\t' || event.character == '\n')
6309 				event.preventDefault();
6310 		});
6311 
6312 		listWidget.addEventListener(EventType.change, () {
6313 			foreach(o; listWidget.options)
6314 				if(o.selected)
6315 					lineEdit.content = o.label;
6316 		});
6317 
6318 		lineEdit.addEventListener(EventType.keydown, (Event event) {
6319 			if(event.key == Key.Tab) {
6320 				import std.file; // FIXME: so slow building :(
6321 				listWidget.clear();
6322 
6323 				string commonPrefix;
6324 				auto cnt = lineEdit.content;
6325 				if(cnt.length >= 2 && cnt[0 ..2] == "./")
6326 					cnt = cnt[2 .. $];
6327 				foreach(string name; dirEntries(".", cnt ~ "*", SpanMode.shallow)) {
6328 					listWidget.addOption(name);
6329 					if(commonPrefix is null)
6330 						commonPrefix = name;
6331 					else {
6332 						foreach(idx, char i; name) {
6333 							if(idx >= commonPrefix.length || i != commonPrefix[idx]) {
6334 								commonPrefix = commonPrefix[0 .. idx];
6335 								break;
6336 							}
6337 						}
6338 					}
6339 				}
6340 				lineEdit.content = commonPrefix;
6341 				event.preventDefault();
6342 			}
6343 		});
6344 
6345 		lineEdit.content = prefilledName;
6346 
6347 		auto hl = new HorizontalLayout(this);
6348 		auto cancelButton = new Button("Cancel", hl);
6349 		auto okButton = new Button("OK", hl);
6350 
6351 		recomputeChildLayout(); // FIXME hack
6352 
6353 		cancelButton.addEventListener(EventType.triggered, &Cancel);
6354 		okButton.addEventListener(EventType.triggered, &OK);
6355 
6356 		this.addEventListener("keydown", (Event event) {
6357 			if(event.key == Key.Enter || event.key == Key.PadEnter) {
6358 				event.preventDefault();
6359 				OK();
6360 			}
6361 			if(event.key == Key.Escape)
6362 				Cancel();
6363 		});
6364 
6365 	}
6366 
6367 	override void OK() {
6368 		if(onOK)
6369 			onOK(lineEdit.content);
6370 		close();
6371 	}
6372 }
6373 
6374 /*
6375 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775947%28v=vs.85%29.aspx#check_boxes
6376 http://msdn.microsoft.com/en-us/library/windows/desktop/ms633574%28v=vs.85%29.aspx
6377 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775943%28v=vs.85%29.aspx
6378 http://msdn.microsoft.com/en-us/library/windows/desktop/bb775951%28v=vs.85%29.aspx
6379 http://msdn.microsoft.com/en-us/library/windows/desktop/ms632680%28v=vs.85%29.aspx
6380 http://msdn.microsoft.com/en-us/library/windows/desktop/ms644996%28v=vs.85%29.aspx#message_box
6381 http://www.sbin.org/doc/Xlib/chapt_03.html
6382 
6383 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760433%28v=vs.85%29.aspx
6384 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760446%28v=vs.85%29.aspx
6385 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760443%28v=vs.85%29.aspx
6386 http://msdn.microsoft.com/en-us/library/windows/desktop/bb760476%28v=vs.85%29.aspx
6387 */
6388 
6389 
6390 // These are all for setMenuAndToolbarFromAnnotatedCode
6391 /// This item in the menu will be preceded by a separator line
6392 struct seperator {}
6393 /// Program-wide keyboard shortcut to trigger the action
6394 struct accelerator { string keyString; }
6395 /// tells which menu the action will be on
6396 struct menu { string name; }
6397 /// Describes which toolbar section the action appears on
6398 struct toolbar { string groupName; }
6399 ///
6400 struct icon { ushort id; }
6401 ///
6402 struct label { string label; }
6403 
6404 
6405 
6406 /++
6407 	Creates a dialog based on a data structure.
6408 
6409 	dialog((YourStructure value) {
6410 		// the user filled in the struct and clicked OK,
6411 		// you can check the members now
6412 	});
6413 +/
6414 void dialog(T)(void delegate(T) onOK, void delegate() onCancel = null) {
6415 	auto dg = new AutomaticDialog!T(onOK, onCancel);
6416 	dg.show();
6417 }
6418 
6419 private static template I(T...) { alias I = T; }
6420 
6421 class AutomaticDialog(T) : Dialog {
6422 	T t;
6423 
6424 	void delegate(T) onOK;
6425 	void delegate() onCancel;
6426 
6427 	this(void delegate(T) onOK, void delegate() onCancel) {
6428 		static if(is(T == class))
6429 			t = new T();
6430 		this.onOK = onOK;
6431 		this.onCancel = onCancel;
6432 		super(400, 300, T.stringof);
6433 
6434 		foreach(memberName; __traits(allMembers, T)) {
6435 			alias member = I!(__traits(getMember, t, memberName))[0];
6436 			alias type = typeof(member);
6437 			static if(is(type == string)) {
6438 				auto le = new LabeledLineEdit(memberName ~ ": ", this);
6439 				le.addEventListener(EventType.change, (Event ev) {
6440 					__traits(getMember, t, memberName) = ev.stringValue;
6441 				});
6442 			} else static if(is(type : long)) {
6443 				auto le = new LabeledLineEdit(memberName ~ ": ", this);
6444 				le.addEventListener("char", (Event ev) {
6445 					if(ev.character < '0' || ev.character > '9')
6446 						ev.preventDefault();
6447 				});
6448 				le.addEventListener(EventType.change, (Event ev) {
6449 					import std.conv;
6450 					try {
6451 						__traits(getMember, t, memberName) = to!type(ev.stringValue);
6452 					} catch(Exception e) {
6453 						// FIXME
6454 					}
6455 				});
6456 			}
6457 		}
6458 
6459 		auto hl = new HorizontalLayout(this);
6460 		auto ok = new Button("OK", hl);
6461 		auto cancel = new Button("Cancel", hl);
6462 		ok.addEventListener(EventType.triggered, &OK);
6463 		cancel.addEventListener(EventType.triggered, &Cancel);
6464 
6465 		this.addEventListener(EventType.keydown, (Event ev) {
6466 			if(ev.key == Key.Enter || ev.key == Key.PadEnter) {
6467 				ok.focus();
6468 				OK();
6469 				ev.preventDefault();
6470 			}
6471 			if(ev.key == Key.Escape) {
6472 				Cancel();
6473 				ev.preventDefault();
6474 			}
6475 		});
6476 
6477 		this.children[0].focus();
6478 	}
6479 
6480 	override void OK() {
6481 		onOK(t);
6482 		close();
6483 	}
6484 
6485 	override void Cancel() {
6486 		if(onCancel)
6487 			onCancel();
6488 		close();
6489 	}
6490 }
Suggestion Box / Bug Report