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