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