changeset 55:c138461bf845

Add focusing and other changes that are related like descendantAdded/Removed events, Window.activated event, and updating List. Window.state was also added, even though focusing does not depend on it.
author Jordan Miner <jminer7@gmail.com>
date Sat, 08 Aug 2009 15:42:27 -0500
parents 3738a2d0bac3
children c2566ab82535
files dynamin/c/windows.d dynamin/core/list.d dynamin/gui/container.d dynamin/gui/control.d dynamin/gui/events.d dynamin/gui/list_box.d dynamin/gui/notebook.d dynamin/gui/radio_button.d dynamin/gui/window.d dynamin/gui/windows_theme.d dynamin/gui/windows_window.d
diffstat 11 files changed, 474 insertions(+), 76 deletions(-) [+]
line wrap: on
line diff
--- a/dynamin/c/windows.d	Sat Aug 08 15:31:24 2009 -0500
+++ b/dynamin/c/windows.d	Sat Aug 08 15:42:27 2009 -0500
@@ -271,6 +271,8 @@
 
 BOOL ScreenToClient(HWND hWnd, POINT* lpPoint);
 
+BOOL SetForegroundWindow(HWND hWnd);
+
 //{{{ messages
 enum {
 	WM_NULL                   = 0x0000,
--- a/dynamin/core/list.d	Sat Aug 08 15:31:24 2009 -0500
+++ b/dynamin/core/list.d	Sat Aug 08 15:42:27 2009 -0500
@@ -37,28 +37,37 @@
 // TODO: QuickSort()
 // TODO: HeapSort()
 // TODO: Sort() - calls HeapSort() so stable sort is default
-// TODO: when D has template inheritance, have separate const_List and List
-class List(T) {
+class List(T, bool hasDelegates = false) {
 protected:
 	T[] _data;
 	uint _count;
-	void delegate() whenChanged; // TODO: have an index and length...
+	static if(hasDelegates) {
+		void delegate(T, int) whenAdded;
+		void delegate(T, int) whenRemoved;
+	}
 public:
 	this() {
-		this(16, {});
+		this(16);
 	}
 	this(uint capacity) {
-		this(capacity, {});
-	}
-	this(void delegate() whenChanged) {
-		this(16, whenChanged);
+		_data = new T[capacity];
 	}
-	this(uint capacity, void delegate() whenChanged) {
-		_data = new T[capacity];
-		this.whenChanged = whenChanged;
+	static if(hasDelegates) {
+		/// whenAdded or whenRemoved is called right after an item is added
+		/// or removed
+		this(void delegate(T, int) whenAdded,
+				void delegate(T, int) whenRemoved) {
+			this(16, whenAdded, whenRemoved);
+		}
+		this(uint capacity, void delegate(T, int) whenAdded,
+				void delegate(T, int) whenRemoved) {
+			this(capacity);
+			this.whenAdded = whenAdded;
+			this.whenRemoved = whenRemoved;
+		}
 	}
-	static List fromArray(T[] arr...) {
-		List list = new List!(T)();
+	static List!(T) fromArray(T[] arr...) {
+		auto list = new List!(T)();
 		list._data = arr.dup;
 		list._count = arr.length;
 		return list;
@@ -105,43 +114,46 @@
 		static if(is(T == class) || is(T == interface))
 			_data[_count-1] = cast(T)null;
 		--_count;
-		whenChanged();
+		static if(hasDelegates)
+			whenRemoved(item, _count);
 		return item;
 	}
 	void add(T item) {
-		insert(_count, item);
+		insert(item, _count);
 	}
 	// TODO: AddRange?
 	void remove(T item) {
 		uint i = find(item);
 		if(i == -1)
 			return;
-		removeRange(i);
-	}
-	void removeRange(uint index, uint length = 1) {
-		arrayCopy!(T)(_data, index+length, _data, index, _count - (index+length));
+
+		for(++i; i < _count; ++i)
+			_data[i-1] = _data[i];
 		// must null out to allow to be collected
 		static if(is(T == class) || is(T == interface))
-			for(uint i = _count-length; i < _count; ++i)
-				_data[i] = cast(T)null;
-		_count -= length;
-		whenChanged();
+			_data[_count-1] = cast(T)null;
+		--_count;
+
+		static if(hasDelegates)
+			whenRemoved(item, i);
 	}
-	void insert(uint index, T item) {
+	void insert(T item, uint index) {
 		maybeEnlarge(_count+1);
 		arrayCopy!(T)(_data, index, _data, index+1, _count - index);
 		_data[index] = item;
 		++_count;
-		whenChanged();
+		static if(hasDelegates)
+			whenAdded(item, index);
 	}
 	// TODO: InsertRange?
 	void clear() {
-		// must null out to allow to be collected
-		static if(is(T == class) || is(T == interface))
-			for(uint i = 0; i < count; ++i)
-				data[i] = cast(T)null;
-		_count = 0;
-		whenChanged();
+		for(; _count > 0; --_count) {
+			static if(hasDelegates)
+				whenRemoved(_data[_count-1], _count-1);
+			// must null out to allow to be collected
+			static if(is(T == class) || is(T == interface))
+				data[_count-1] = cast(T)null;
+		}
 	}
 	uint find(T item) {
 		foreach(i, item2; _data)
@@ -179,12 +191,28 @@
 	list.add('t');
 	assert(list.data == "Hello, Matt");
 	assert(list.pop() == 't');
-	list.removeRange(1, 7);
-	assert(list.data == "Hat");
+	assert(list.data == "Hello, Mat");
+	list.insert('!', 5);
+	assert(list.data == "Hello!, Mat");
+	list.remove('l');
+	assert(list.data == "Helo!, Mat");
 	list.clear();
 	assert(list.data == "");
-	auto list2 = new List!(string);
+
+	int a = 0, r = 0;
+	void added(string, int) { a++; };
+	void removed(string, int) { r++; };
+	auto list2 = new List!(string, true)(&added, &removed);
+	assert(a == 0 && r == 0);
 	list2.add("hello");
+	assert(a == 1 && r == 0);
 	assert(list2.pop() == "hello");
+	assert(a == 1 && r == 1);
+
+	list2.add("Hi");
+	list2.add("Jacob!");
+	assert(a == 3 && r == 1);
+	list2.clear();
+	assert(a == 3 && r == 3);
 }
 
--- a/dynamin/gui/container.d	Sat Aug 08 15:31:24 2009 -0500
+++ b/dynamin/gui/container.d	Sat Aug 08 15:42:27 2009 -0500
@@ -32,7 +32,7 @@
 import dynamin.gui.events;
 import tango.io.Stdout;
 
-alias List!(Control) ControlList;
+alias List!(Control, true) ControlList;
 
 ///
 class Container : Control {
@@ -44,6 +44,100 @@
 	override void whenResized(EventArgs e) {
 		layout();
 	}
+	// If the specified array is large enough to hold the results, no heap
+	// allocation will be done.
+	Control[] getFocusableDescendants(Control[] des = null) {
+		uint cur = 0;
+
+		void addDescendants(Container c) {
+			if(c._focusable) { // TODO: && c.enabled) {
+				if(cur == des.length)
+					des.length = des.length + 20;
+				des[cur++] = c;
+			}
+			foreach(ch; c._children) {
+				if(cast(Container)ch)
+					addDescendants(cast(Container)ch);
+				else if(ch.focusable) { // TODO: && ch.enabled) {
+					if(cur == des.length)
+						des.length = des.length + 20;
+					des[cur++] = ch;
+				}
+			}
+		}
+		addDescendants(this);
+		return des[0..cur];
+	}
+	unittest {
+		class MyControl : Control {
+			this() {
+				_focusable = true;
+			}
+		}
+		auto container1 = new Container();
+		auto container2 = new Container();
+		auto container3 = new Container();
+		auto container4 = new Container();
+		container2._focusable = true;
+		container4._focusable = true;
+		auto ctrl1 = new MyControl();
+		auto ctrl2 = new MyControl();
+		auto ctrl3 = new MyControl();
+
+		container1.add(container2);
+		container1.add(container3);
+		container3.add(container4);
+		container2.add(ctrl1);
+		container4.add(ctrl2);
+		container4.add(ctrl3);
+		assert(container1.getFocusableDescendants() ==
+			[cast(Control)container2, ctrl1, container4, ctrl2, ctrl3]);
+		Control[5] buf;
+		assert(container1.getFocusableDescendants(buf).ptr == buf.ptr);
+	}
+
+	// not an event
+	void whenChildAdded(Control child, int) {
+		if(child.parent)
+			child.parent.remove(child);
+		child.parent = this;
+		repaint();
+
+		void callAdded(Control ctrl) {
+			scope e = new HierarchyEventArgs(ctrl);
+			descendantAdded(e);
+
+			if(auto cntr = cast(Container)ctrl) {
+				foreach(c; cntr._children)
+					callAdded(c);
+			}
+		}
+		callAdded(child);
+	}
+
+	// not an event
+	void whenChildRemoved(Control child, int) {
+		child.parent = null;
+		repaint();
+
+		scope e = new HierarchyEventArgs(child);
+		descendantRemoved(e);
+	}
+
+	void dispatchDescendantAdded(HierarchyEventArgs e) {
+		descendantAdded.callHandlers(e);
+		descendantAdded.callMainHandler(e);
+		e.levels = e.levels + 1;
+		if(_parent)
+			_parent.descendantAdded(e);
+	}
+	void dispatchDescendantRemoved(HierarchyEventArgs e) {
+		descendantRemoved.callHandlers(e);
+		descendantRemoved.callMainHandler(e);
+		e.levels = e.levels + 1;
+		if(_parent)
+			_parent.descendantRemoved(e);
+	}
 public:
 	/// Override this method in a subclass to handle the minSizeChanged event.
 	protected void whenMinSizeChanged(EventArgs e) { }
@@ -55,10 +149,25 @@
 	/// This event occurs after the control's maximum size has been changed.
 	Event!(whenMaxSizeChanged) maxSizeChanged;
 
+	/// Override this method in a subclass to handle the descendantAdded event.
+	protected void whenDescendantAdded(HierarchyEventArgs e) { }
+	/// This event occurs after a control is added as a descendant of this container.
+	Event!(whenDescendantAdded) descendantAdded;
+
+	/// Override this method in a subclass to handle the descendantRemoved event.
+	protected void whenDescendantRemoved(HierarchyEventArgs e) { }
+	/// This event occurs after a descendant of this container has been removed.
+	Event!(whenDescendantRemoved) descendantRemoved;
+
 	this() {
 		minSizeChanged.mainHandler = &whenMinSizeChanged;
 		maxSizeChanged.mainHandler = &whenMaxSizeChanged;
-		_children = new ControlList();
+		descendantAdded.mainHandler = &whenDescendantAdded;
+		descendantAdded.dispatcher = &dispatchDescendantAdded;
+		descendantRemoved.mainHandler = &whenDescendantRemoved;
+		descendantRemoved.dispatcher = &dispatchDescendantRemoved;
+
+		_children = new ControlList(&whenChildAdded, &whenChildRemoved);
 
 		elasticX = true;
 		elasticY = true;
@@ -222,19 +331,11 @@
 	}
 
 	protected void add(Control child) {
-		if(child.parent)
-			child.parent.remove(child);
 		_children.add(child);
-		child.parent = this;
-		repaint();
-		//ControlAdded(EventArgs e); // TODO: add event
 	}
 
 	protected void remove(Control child) {
 		_children.remove(child);
-		child.parent = null;
-		repaint();
-		//ControlRemoved(EventArgs e); // TODO: add event
 	}
 
 	int opApply(int delegate(inout Control item) dg) {
@@ -254,6 +355,15 @@
 		return 0;
 	}
 }
+unittest {
+	auto i = 0;
+	auto container = new Panel;
+	container.descendantAdded += (HierarchyEventArgs e) { i++; };
+	auto sub = new Panel;
+	sub.add(new Control);
+	container.add(sub);
+	assert(i == 2);
+}
 
 // TODO: calling panel.children.add(button) will cause a crash
 // because the button's parent is not set to the panel
--- a/dynamin/gui/control.d	Sat Aug 08 15:31:24 2009 -0500
+++ b/dynamin/gui/control.d	Sat Aug 08 15:42:27 2009 -0500
@@ -33,6 +33,7 @@
 import dynamin.gui.cursor;
 import tango.io.Stdout;
 
+//{{{ hotControl
 Control hotControl;
 // the hot control is the one the mouse is over
 package void setHotControl(Control c) {
@@ -45,11 +46,30 @@
 	}
 }
 package Control getHotControl() { return hotControl; }
+//}}}
+
+//{{{ captorControl
 Control captorControl;
 package void setCaptorControl(Control c) {
 	captorControl = c;
 }
 package Control getCaptorControl() { return captorControl; }
+//}}}
+
+//{{{ focusedControl
+Control focusedControl;
+package void setFocusedControl(Control c) {
+	if(focusedControl is c)
+		return;
+	scope e = new EventArgs;
+	if(focusedControl)
+		focusedControl.focusLost(e);
+	focusedControl = c;
+	if(focusedControl)
+		focusedControl.focusGained(e);
+}
+package Control getFocusedControl() { return focusedControl; }
+//}}}
 
 /**
  * The painting event is an exception to the rule that added handlers are called
@@ -205,6 +225,20 @@
 	/// This event occurs when the control needs to be painted.
 	Event!(whenPainting) painting;
 
+	/// Override this method in a subclass to handle the focusGained event.
+	protected void whenFocusGained(EventArgs e) {
+		repaint();
+	}
+	/// This event occurs after this control is focused.
+	Event!(whenFocusGained) focusGained;
+
+	/// Override this method in a subclass to handle the focusLost event.
+	protected void whenFocusLost(EventArgs e) {
+		repaint();
+	}
+	/// This event occurs after this control loses focus.
+	Event!(whenFocusLost) focusLost;
+
 	this() {
 		moved.mainHandler = &whenMoved;
 		resized.mainHandler = &whenResized;
@@ -229,6 +263,8 @@
 		keyUp.dispatcher = &dispatchKeyUp;
 		painting.mainHandler = &whenPainting;
 		painting.dispatcher = &dispatchPainting;
+		focusGained.mainHandler = &whenFocusGained;
+		focusLost.mainHandler = &whenFocusLost;
 
 		_location = Point(0, 0);
 		_size = Size(100, 100);
@@ -277,10 +313,36 @@
 	}
 
 	/**
-	 *
+	 * Returns whether or not this control can receive focus.
+	 */
+	bool focusable() {
+		return _focusable;
+	}
+	void focusable(bool f) {
+		_focusable = f;
+		// TODO:
+	}
+
+	/**
+	 * Returns whether this control currently has focus. A control with focus
+	 * receives keyboard events.
 	 */
 	bool focused() {
-		return _focused;
+		return getFocusedControl() is this;
+	}
+
+	/**
+	 * Returns true if this control should visually show when it has focus
+	 * and returns false if not. Focus is usually hidden until the
+	 * user uses the keyboard to navigate.
+	 *
+	 * A text box is one control that shows when it is
+	 * focused (by its caret), regardless of this value. (Because showing
+	 * focus isn't the sole purpose of a caret.)
+	 */
+	bool showFocus() {
+		auto top = getTopLevel();
+		return top && (cast(Window)top).showFocus;
 	}
 
 	/**
@@ -307,15 +369,8 @@
 		auto top = getTopLevel();
 		if(!top)
 			return;
-		if(auto win = cast(Window)top) {
-			if(win.focusedControl) {
-				win.focusedControl._focused = false;
-				win.focusedControl.repaint();
-			}
+		if(auto win = cast(Window)top)
 			win.focusedControl = this;
-			_focused = true;
-			repaint();
-		}
 	}
 
 	/**
--- a/dynamin/gui/events.d	Sat Aug 08 15:31:24 2009 -0500
+++ b/dynamin/gui/events.d	Sat Aug 08 15:42:27 2009 -0500
@@ -28,6 +28,8 @@
 import dynamin.all_core;
 import dynamin.all_painting;
 import dynamin.all_gui;
+import dynamin.gui.control;
+import dynamin.gui.container;
 
 ///
 enum MouseButton {
@@ -162,3 +164,27 @@
 	dchar character() { return _ch; }
 }
 
+///
+class HierarchyEventArgs : EventArgs {
+	int _levels = 0;
+	Control _control;
+public:
+	this(Control c) {
+		_control = c;
+	}
+	/**
+	 * An immediate child would be a level of 0.
+	 */
+	int levels() { return _levels; }
+	/// ditto
+	void levels(int l) { _levels = l; }
+	/**
+	 *
+	 */
+	Control descendant() { return _control; }
+	/**
+	 *
+	 */
+	Container ancestor() { return cast(Container)_control; }
+}
+
--- a/dynamin/gui/list_box.d	Sat Aug 08 15:31:24 2009 -0500
+++ b/dynamin/gui/list_box.d	Sat Aug 08 15:42:27 2009 -0500
@@ -38,7 +38,7 @@
  */
 class ListBox : Scrollable {
 protected:
-	List!(string) _items;
+	List!(string, true) _items;
 	int _selectedIndex = -1;
 
 	override void whenKeyDown(KeyEventArgs e) {
@@ -83,7 +83,7 @@
 	/// This event occurs after the selection has changed.
 	Event!(whenSelectionChanged) selectionChanged;
 
-	void listItemsChanged() {
+	void whenListItemsChanged(string, int) {
 		super.layout();
 		repaint();
 	}
@@ -91,7 +91,7 @@
 	///
 	this() {
 		selectionChanged.mainHandler = &whenSelectionChanged;
-		_items = new List!(string)(&listItemsChanged);
+		_items = new List!(string, true)(&whenListItemsChanged, &whenListItemsChanged);
 
 		super();
 		_focusable = true;
@@ -99,7 +99,7 @@
 		backColor = WindowsTheme.getColor(5);
 	}
 	///
-	List!(string) items() {
+	List!(string, true) items() {
 		return _items;
 	}
 	///
--- a/dynamin/gui/notebook.d	Sat Aug 08 15:31:24 2009 -0500
+++ b/dynamin/gui/notebook.d	Sat Aug 08 15:42:27 2009 -0500
@@ -68,7 +68,7 @@
  */
 class Notebook : Container {
 protected:
-	List!(TabPage) _tabPages;
+	List!(TabPage, true) _tabPages;
 	int _selectedIndex = -1;
 	bool _multipleLines = true;
 	Control _content;
@@ -91,7 +91,7 @@
 		}
 		Theme.current.Tab_paint(selectedTabPage, this, e.graphics);
 	}
-	void whenTabPagesChanged() {
+	void whenTabPagesChanged(TabPage page, int) {
 		if(_tabPages.count == 0)
 			selectedIndex = -1;
 		else if(selectedIndex == -1)
@@ -116,7 +116,7 @@
 	this() {
 		selectionChanged.mainHandler = &whenSelectionChanged;
 
-		_tabPages = new List!(TabPage)(&whenTabPagesChanged);
+		_tabPages = new List!(TabPage, true)(&whenTabPagesChanged, &whenTabPagesChanged);
 		_focusable = true;
 	}
 	override void layout() {
@@ -143,7 +143,7 @@
 	  * tabbedView.TabPages.Add(advancedPage);
 	  * -----
 	  */
-	List!(TabPage) tabPages() { return _tabPages; }
+	List!(TabPage, true) tabPages() { return _tabPages; }
 	/**
 	 * Gets or sets the selected tab using its index. An index of -1 means
 	 * there is no selected tab.
--- a/dynamin/gui/radio_button.d	Sat Aug 08 15:31:24 2009 -0500
+++ b/dynamin/gui/radio_button.d	Sat Aug 08 15:42:27 2009 -0500
@@ -98,6 +98,7 @@
 	}
 public:
 	this() {
+		_focusable = false;
 	}
 	this(string text) {
 		this();
@@ -106,6 +107,11 @@
 	override Size bestSize() {
 		return Size(70, 15);
 	}
+	alias CheckBox.checked checked;
+	override void checked(bool b) {
+		focusable = b;
+		super.checked(b);
+	}
 	/**
 	 * Gets or sets what group this radio button is a part of. The default is 1.
 	 */
--- a/dynamin/gui/window.d	Sat Aug 08 15:31:24 2009 -0500
+++ b/dynamin/gui/window.d	Sat Aug 08 15:42:27 2009 -0500
@@ -37,6 +37,7 @@
 import tango.io.Stdout;
 import tango.core.Exception;
 import tango.core.Thread;
+import tango.text.Util;
 
 ///
 static class Application {
@@ -109,6 +110,26 @@
 }
 
 /**
+ * The different states a window may be in. It may not be in more than one of
+ * these states at a time.
+ */
+enum WindowState {
+	/**
+	 * Specifies that the window is neither minimized or maximized.
+	 */
+	Normal,
+	/**
+	 * Specifies that the window is only visible as an icon and/or text on
+	 * the taskbar or dock.
+	 */
+	Minimized,
+	/**
+	 * Specifies that the window covers the screen in at least one direction.
+	 */
+	Maximized
+}
+
+/**
  * The different types of borders that a window may have.
  * These do not affect whether the window is resizable--
  * use Window.resizable for that.
@@ -154,21 +175,107 @@
 	mixin WindowBackend;
 	BorderSize _borderSize;
 	Window _owner;
+	package bool _active;
+	package WindowState _state;
 	WindowBorderStyle _borderStyle;
 	bool _resizable = true;
 	Panel _content;
+	bool _showFocus;
+	// _focusedControl might not be focused at the current time (that is
+	// getFocusedControl()), but will at least be focused when this
+	// window is active
 	Control _focusedControl;
 	package Control focusedControl() { return _focusedControl; }
 	package void focusedControl(Control c) {
 		_focusedControl = c;
+		if(active)
+			setFocusedControl(_focusedControl);
 	}
 	override void dispatchPainting(PaintingEventArgs e) {
 		Theme.current.Window_paint(this, e.graphics);
 		super.dispatchPainting(e);
 	}
+	override void whenDescendantAdded(HierarchyEventArgs e) {
+		super.whenDescendantAdded(e);
+		if(focusedControl is null && e.descendant.focusable) {
+				// && e.descendant.enabled) {
+			focusedControl = e.descendant;
+		}
+	}
+
+	//{{{ focusing
+	public override bool showFocus() { return _showFocus; }
+	override void whenKeyDown(KeyEventArgs e) {
+		if(e.key == Key.Tab) {
+			getNextFocusable().focus();
+			_showFocus = true;
+		}
+	}
+
+	// will not return null
+	Control getNextFocusable() {
+		Control foc = focusedControl;
+
+		Control[32] buffer;
+		auto des = getFocusableDescendants(buffer);
+		if(des.length == 0)
+			return this;
+		else if(des.length == 1)
+			return des[0];
+
+		int focI = locate(des, foc);
+
+		// look _after_ this control for one with the same tab index
+		foreach(c; des[focI+1..$])
+			if(c.tabIndex == foc.tabIndex)
+				return c;
+
+		// if none are found, look for the next largest tab index
+		// from the beginning of the array
+		Control smallest;
+		Control nextLargest;
+		foreach(c; des) {
+			if(c.tabIndex > foc.tabIndex)
+				if(nextLargest is null || c.tabIndex < nextLargest.tabIndex)
+					nextLargest = c;
+			if(smallest is null || c.tabIndex < smallest.tabIndex)
+				smallest = c;
+		}
+
+		if(nextLargest)
+			return nextLargest;
+		else
+			return smallest;
+	}
+
+	// will not return null
+	Control getPreviousFocusable() {
+		return null;
+	}
+	//}}}
+
 public:
+	/// Override this method in a subclass to handle the activated event.
+	protected void whenActivated(EventArgs e) {
+		setFocusedControl(_focusedControl is null ? content : _focusedControl);
+	}
+	/// This event occurs after this window is activated.
+	Event!(whenActivated) activated;
+
+	/// Override this method in a subclass to handle the deactivated event.
+	protected void whenDeactivated(EventArgs e) {
+		setFocusedControl(null);
+	}
+	/// This event occurs after this window is deactivated.
+	Event!(whenDeactivated) deactivated;
+
+	/**
+	 *
+	 */
 	this() {
-		_children = new ControlList();
+		activated.mainHandler = &whenActivated;
+		deactivated.mainHandler = &whenDeactivated;
+
 		content = new Panel;
 
 		_visible = false;
@@ -183,6 +290,13 @@
 		this.text = text;
 	}
 
+	/**
+	 *
+	 */
+	Panel content() {
+		return _content;
+	}
+	/// ditto
 	void content(Panel panel) {
 		if(panel is null)
 			throw new IllegalArgumentException("content must not be null");
@@ -228,9 +342,6 @@
 		_content.size = _size-_borderSize;
 		ignoreResize = false;
 	}
-	Panel content() {
-		return _content;
-	}
 
 	/**
 	 * If the handle has not yet been created, calling this will cause it to be.
@@ -293,6 +404,32 @@
 	}
 
 	/**
+	 *
+	 */
+	bool active() { return _active; }
+	/**
+	 *
+	 */
+	void activate() {
+		if(!handleCreated)
+			return;
+		backend_activate();
+	}
+
+	/**
+	 * Gets or sets whether the window's state is normal, minimized, or
+	 * maximized.
+	 */
+	WindowState state() { return _state; }
+	/// ditto
+	void state(WindowState s) {
+		_state = s;
+		if(!handleCreated)
+			return;
+		backend_state = s;
+	}
+
+	/**
 	 * Gets or sets what border this window will have around its contents.
 	 * The default is WindowBorderStyle.Normal.
 	 */
--- a/dynamin/gui/windows_theme.d	Sat Aug 08 15:31:24 2009 -0500
+++ b/dynamin/gui/windows_theme.d	Sat Aug 08 15:42:27 2009 -0500
@@ -216,7 +216,7 @@
 			auto uxState = findUxState(c, PBS_DISABLED, PBS_NORMAL, PBS_HOT, PBS_PRESSED);
 			Ux.drawBackground(g, Rect(0, 0, c.width, c.height), "BUTTON", BP_PUSHBUTTON, uxState);
 
-			if(c.focused)
+			if(c.focused && c.showFocus)
 				drawFocus(g, COLOR_WINDOWTEXT, 3.5, 3.5, c.width-7, c.height-7);
 			g.source = getColor(COLOR_WINDOWTEXT);
 			c.paintFore(g);
@@ -236,7 +236,7 @@
 			draw3dRectangle(g, 1, 1, c.width-2, c.height-2,
 				getColor(COLOR_3DLIGHT), getColor(COLOR_3DSHADOW));
 		}
-		if(c.focused)
+		if(c.focused && c.showFocus)
 			drawFocus(g, COLOR_WINDOWTEXT, 3.5, 3.5, c.width-7, c.height-7);
 		g.source = getColor(COLOR_WINDOWTEXT);
 		c.paintFore(g);
@@ -255,7 +255,7 @@
 			}
 			Ux.drawBackground(g, Rect(0, 0, 13, c.height), "BUTTON", BP_CHECKBOX, uxState);
 
-			if(c.focused)
+			if(c.focused && c.showFocus)
 				drawFocus(g, COLOR_WINDOWTEXT,
 					15.5, 0.5, c.width-16, c.height-1);
 			g.source = getColor(COLOR_WINDOWTEXT);
@@ -281,7 +281,7 @@
 			drawCheck(g, 3, 3);
 		//	drawCheck(g, 0, 0, 13, 13);
 
-		if(c.focused)
+		if(c.focused && c.showFocus)
 			drawFocus(g, COLOR_WINDOWTEXT, 15.5, 0.5, c.width-16, c.height-1);
 		g.source = getColor(COLOR_WINDOWTEXT);
 		g.translate(16, 0);
@@ -301,7 +301,7 @@
 			}
 			Ux.drawBackground(g, Rect(0, 0, 13, c.height), "BUTTON", BP_RADIOBUTTON, uxState);
 
-			if(c.focused)
+			if(c.focused && c.showFocus)
 				drawFocus(g, COLOR_WINDOWTEXT,
 					15.5, 0.5, c.width-16, c.height-1);
 			g.source = getColor(COLOR_WINDOWTEXT);
@@ -361,7 +361,7 @@
 			g.translate(-4, -4);
 		}
 
-		if(c.focused)
+		if(c.focused && c.showFocus)
 			drawFocus(g, COLOR_WINDOWTEXT, 15.5, 0.5, c.width-16, c.height-1);
 		g.source = getColor(COLOR_WINDOWTEXT);
 		g.translate(16, 0);
--- a/dynamin/gui/windows_window.d	Sat Aug 08 15:31:24 2009 -0500
+++ b/dynamin/gui/windows_window.d	Sat Aug 08 15:42:27 2009 -0500
@@ -191,8 +191,26 @@
 		return g;
 	}
 	void backend_visible(bool b) {
-		//if not created, create the handle by calling Handle()
-		ShowWindow(handle, b ? SW_SHOW : SW_HIDE);
+		if(b)
+			// visible has been set to true by now...use state() to show window
+			backend_state = _state;
+		else
+			//if not created, create the handle by calling handle()
+			ShowWindow(handle, SW_HIDE);
+	}
+	void backend_state(WindowState s) {
+		if(!visible)
+			return;
+		//if not created, create the handle by calling handle()
+		if(s == WindowState.Normal)
+			ShowWindow(handle, SW_RESTORE);
+		else if(s == WindowState.Minimized)
+			ShowWindow(handle, SW_MINIMIZE);
+		else if(s == WindowState.Maximized)
+			ShowWindow(handle, SW_MAXIMIZE);
+	}
+	void backend_activate() {
+		SetForegroundWindow(_handle);
 	}
 	void backend_borderStyle(WindowBorderStyle border) {
 		backend_updateWindowStyles();
@@ -657,8 +675,14 @@
 		c.moved(args);
 		return 0;
 	case WM_SIZE:
-		if(wParam == SIZE_MINIMIZED)
-			break;
+		if(wParam == SIZE_RESTORED)
+			c._state = WindowState.Normal;
+		else if(wParam == SIZE_MINIMIZED) {
+			c._state = WindowState.Minimized;
+			break;   // don't update size if minimized (would be wierd size)
+		} else if(wParam == SIZE_MAXIMIZED)
+			c._state = WindowState.Maximized;
+
 		RECT rect;
 		GetWindowRect(hwnd, &rect);
 		c._size = Size(rect.right-rect.left, rect.bottom-rect.top);
@@ -666,6 +690,16 @@
 		scope args = new EventArgs();
 		c.resized(args);
 		return 0;
+	case WM_ACTIVATE:
+		scope e = new EventArgs;
+		if(LOWORD(wParam) == WA_ACTIVE || LOWORD(wParam) == WA_CLICKACTIVE) {
+			c._active = true;
+			c.activated(e);
+		} else if(LOWORD(wParam) == WA_INACTIVE) {
+			c._active = false;
+			c.deactivated(e);
+		}
+		return 0;
 	case WM_MOUSEMOVE:
 		if(!trackingMouseLeave) {
 			TRACKMOUSEEVENT tme;