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