diff --git a/util/designer2.cpp b/util/designer2.cpp
index acb781a..713af05 100644
--- a/util/designer2.cpp
+++ b/util/designer2.cpp
@@ -14,16 +14,16 @@
 
 // x86_64-w64-mingw32-gcc -O3 -o bin/designer2.exe -D UI_WINDOWS util/designer2.cpp  -DUNICODE -lgdi32 -luser32 -lkernel32 -Wl,--subsystem,windows -fno-exceptions -fno-rtti
 
-// Needed to replace the old designer:
-// TODO Previewing state transitions.
-// TODO Implement path layers, and test radial gradients with them.
-// TODO Import and reorganize old theming data.
-// TODO Export.
+// TODO Needed to replace the old designer:
+// 	Prototyping display: previewing state transitions.
+// 	Implement path layers, and test radial gradients with them.
+// 	Import and reorganize old theming data.
+// 	Export.
 
-// Additional features:
-// TODO Resizing objects?
-// TODO Find object in graph by name.
-// TODO Prototyping display. (Multiple instances of each object can be placed, resized and interacted with).
+// TODO Additional features:
+// 	Resizing objects?
+// 	Find object in graph by name.
+// 	Auto-layout in the prototype display.
 		
 //////////////////////////////////////////////////////////////
 
@@ -179,13 +179,30 @@ const EsInstanceClassEditorSettings instanceClassEditorSettings = {
 };
 #endif
 
-UIWindow *window;
-UIElement *canvas;
-UIElement *inspector;
-UIElement *canvasControls;
-UILabel *labelMessage;
+struct Canvas : UIElement {
+	float panX, panY;
+	float lastPanPointX, lastPanPointY;
+	bool dragging, canDrag, selecting;
+	int32_t dragDeltaX, dragDeltaY;
+	int32_t selectX, selectY;
+	int32_t dragOffsetX, dragOffsetY;
+	int32_t leftDownX, leftDownY;
+	bool showArrows;
+	bool showPrototype;
+	UIRectangle originalBounds;
+	UIRectangle resizeOffsets;
+	bool resizing;
+	UIElement *resizeHandles[4];
+};
 
-uint64_t selectedObjectID;
+struct Prototype : UIElement {
+};
+
+UIWindow *window;
+UIElement *inspector;
+UIPanel *graphControls;
+UIPanel *prototypeControls;
+Canvas *canvas;
 
 void InspectorAnnouncePropertyChanged(uint64_t objectID, const char *cPropertyName);
 void InspectorPopulate();
@@ -219,6 +236,7 @@ enum ObjectType : uint8_t {
 
 	OBJ_STYLE,
 	OBJ_COMMENT,
+	OBJ_INSTANCE,
 
 	OBJ_VAR_COLOR = 0x40,
 	OBJ_VAR_INT,
@@ -244,6 +262,7 @@ struct Object {
 #define OBJECT_NAME_SIZE (46)
 	char cName[OBJECT_NAME_SIZE];
 #define OBJECT_IS_SELECTED (1 << 0)
+#define OBJECT_IN_PROTOTYPE (1 << 1)
 	uint8_t flags;
 	uint64_t id;
 	Array<Property> properties;
@@ -255,6 +274,7 @@ enum StepType : uint8_t {
 	STEP_RENAME_OBJECT,
 	STEP_ADD_OBJECT,
 	STEP_DELETE_OBJECT,
+	STEP_SET_OBJECT_DEPTH,
 };
 
 enum StepApplyMode {
@@ -274,12 +294,14 @@ struct Step {
 		Property property;
 		char cName[OBJECT_NAME_SIZE];
 		Object object;
+		uintptr_t depth;
 	};
 };
 
 Array<Step> undoStack;
 Array<Step> redoStack;
 bool documentModified;
+uint64_t selectedObjectID;
 
 // Document state:
 Array<Object> objects;
@@ -478,7 +500,7 @@ void DocumentApplyStep(Step step, StepApplyMode mode = STEP_APPLY_NORMAL) {
 			}
 		}
 
-		UIElementRepaint(canvas, nullptr);
+		UIElementRefresh(canvas);
 
 		if (step.flags & STEP_UPDATE_INSPECTOR) {
 			InspectorPopulate();
@@ -517,8 +539,25 @@ void DocumentApplyStep(Step step, StepApplyMode mode = STEP_APPLY_NORMAL) {
 		}
 
 		step.object.flags = 0;
-		UIElementRepaint(canvas, nullptr);
+		UIElementRefresh(canvas);
 		InspectorPopulate();
+	} else if (step.type == STEP_SET_OBJECT_DEPTH) {
+		uintptr_t newDepth = step.depth;
+		bool found = false;
+
+		for (uintptr_t i = 0; i < objects.Length(); i++) {
+			if (objects[i].id == step.objectID) {
+				step.depth = i;
+				Object object = objects[i];
+				objects.Delete(i);
+				objects.Insert(object, newDepth);
+				found = true;
+				break;
+			}
+		}
+
+		UI_ASSERT(found);
+		UIElementRefresh(canvas);
 	} else {
 		UI_ASSERT(false);
 	}
@@ -666,8 +705,6 @@ InspectorBindingData *inspectorPickData;
 void InspectorPickTargetEnd() {
 	if (inspectorPickData) {
 		inspectorPickData = nullptr;
-		UILabelSetContent(labelMessage, "", -1);
-		UIElementRefresh(&labelMessage->e);
 		UIElementRepaint(canvas, nullptr);
 	}
 }
@@ -866,10 +903,12 @@ int InspectorBoundMessage(UIElement *element, UIMessage message, int di, void *d
 			if (0 == strcmp(result, "OK")) {
 				uint64_t id = 0;
 
-				for (uintptr_t i = 0; i < objects.Length(); i++) {
-					if (0 == strcmp(objects[i].cName, name)) {
-						id = objects[i].id;
-						break;
+				if (name && name[0]) {
+					for (uintptr_t i = 0; i < objects.Length(); i++) {
+						if (0 == strcmp(objects[i].cName, name)) {
+							id = objects[i].id;
+							break;
+						}
 					}
 				}
 
@@ -1073,8 +1112,6 @@ void InspectorPickTargetCommand(void *cp) {
 	}
 
 	inspectorPickData = (InspectorBindingData *) cp;
-	UILabelSetContent(labelMessage, "** Click an object to link it. **", -1);
-	UIElementRefresh(&labelMessage->e);
 	UIElementRepaint(canvas, nullptr);
 }
 
@@ -1090,6 +1127,33 @@ void InspectorFindTargetCommand(void *cp) {
 	}
 }
 
+void InspectorGoToInstanceStyle(void *) {
+	Property *property = PropertyFind(ObjectFind(selectedObjectID), "style", PROP_OBJECT);
+	Object *target = ObjectFind(property ? property->object : 0);
+
+	if (target) {
+		CanvasSelectObject(target);
+	} else {
+		UIDialogShow(window, 0, "Error: The object does not exist.\n%f%b", "OK");
+	}
+}
+
+void InspectorToFrontCommand(void *) {
+	Step step = {};
+	step.type = STEP_SET_OBJECT_DEPTH;
+	step.depth = objects.Length() - 1;
+	step.objectID = selectedObjectID;
+	DocumentApplyStep(step);
+}
+
+void InspectorToBackCommand(void *) {
+	Step step = {};
+	step.type = STEP_SET_OBJECT_DEPTH;
+	step.depth = 0;
+	step.objectID = selectedObjectID;
+	DocumentApplyStep(step);
+}
+
 int InspectorTabPaneMessage(UIElement *element, UIMessage message, int di, void *dp) {
 	if (message == UI_MSG_LEFT_DOWN) {
 		element->messageClass(element, message, di, dp);
@@ -1187,7 +1251,7 @@ void InspectorPopulate() {
 
 	Object *object = ObjectFind(selectedObjectID);
 
-	if (object) {
+	if (object && object->type != OBJ_INSTANCE) {
 		UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_BORDER | UI_PANEL_MEDIUM_SPACING | UI_PANEL_EXPAND);
 			UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL);
 			char buffer[256];
@@ -1451,6 +1515,56 @@ void InspectorPopulate() {
 			UILabelCreate(0, 0, "Factor (%):", -1);
 			InspectorBind(&UITextboxCreate(0, UI_ELEMENT_H_FILL)->e, object->id, "factor", INSPECTOR_INTEGER_TEXTBOX);
 		}
+	} else if (object && object->type == OBJ_INSTANCE) {
+		Property *property = PropertyFind(object, "style", PROP_OBJECT);
+		Object *style = ObjectFind(property ? property->object : 0);
+		char buffer[128];
+
+		if (style) {
+			snprintf(buffer, sizeof(buffer), "Instance of style '%s'.", style->cName);
+		} else {
+			snprintf(buffer, sizeof(buffer), "Instance of deleted style.");
+		}
+
+		UILabelCreate(0, 0, buffer, -1);
+
+		if (style) {
+			UIButtonCreate(&UIPanelCreate(0, UI_PANEL_HORIZONTAL)->e, 0, "View in graph \x1A", -1)->invoke = InspectorGoToInstanceStyle;
+		}
+
+		UISpacerCreate(0, 0, 0, 10);
+
+		UILabelCreate(0, 0, "Depth:", -1);
+		UIPanelCreate(0, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
+		UIButtonCreate(0, 0, "To front", -1)->invoke = InspectorToFrontCommand;
+		UIButtonCreate(0, 0, "To back", -1)->invoke = InspectorToBackCommand;
+		UIParentPop();
+
+		UISpacerCreate(0, 0, 0, 10);
+
+		UILabelCreate(0, 0, "Preview state:", -1);
+		UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL);
+		UIButtonCreate(0, UI_BUTTON_SMALL | UI_BUTTON_CHECKED, "Idle", -1);
+		UIButtonCreate(0, UI_BUTTON_SMALL, "Hovered", -1);
+		UIButtonCreate(0, UI_BUTTON_SMALL, "Pressed", -1);
+		UIButtonCreate(0, UI_BUTTON_SMALL, "Disabled", -1);
+		UIButtonCreate(0, UI_BUTTON_SMALL, "Inactive", -1);
+		UIParentPop();
+		UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_EXPAND)->gap = -5;
+		UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL)->gap = 8;
+		UICheckboxCreate(0, 0, cStateBitStrings[0], -1);
+		UICheckboxCreate(0, 0, cStateBitStrings[1], -1);
+		UICheckboxCreate(0, 0, cStateBitStrings[2], -1);
+		UICheckboxCreate(0, 0, cStateBitStrings[3], -1);
+		UICheckboxCreate(0, 0, cStateBitStrings[4], -1);
+		UIParentPop();
+		UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL)->gap = 8;
+		UICheckboxCreate(0, 0, cStateBitStrings[5], -1);
+		UICheckboxCreate(0, 0, cStateBitStrings[6], -1);
+		UICheckboxCreate(0, 0, cStateBitStrings[7], -1);
+		UICheckboxCreate(0, 0, cStateBitStrings[8], -1);
+		UIParentPop();
+		UIParentPop();
 	} else {
 		UILabelCreate(0, 0, "Select an object to inspect.", -1);
 	}
@@ -1652,27 +1766,25 @@ void ExportPaintAsLayerBox(Object *object, EsBuffer *data) {
 
 #define CANVAS_ALIGN (20)
 
-float canvasPanX, canvasPanY;
-float canvasLastPanPointX, canvasLastPanPointY;
-bool canvasDragging, canvasCanDrag, canvasSelecting;
-int32_t canvasDragDeltaX, canvasDragDeltaY;
-int32_t canvasSelectX, canvasSelectY;
-int32_t canvasDragOffsetX, canvasDragOffsetY;
-int32_t canvasLeftDownX, canvasLeftDownY;
-bool canvasShowArrows;
-
 UIRectangle CanvasGetObjectBounds(Object *object) {
-	int32_t x = PropertyReadInt32(object, "_graphX") - canvasPanX + canvas->bounds.l;
-	int32_t y = PropertyReadInt32(object, "_graphY") - canvasPanY + canvas->bounds.t;
+	int32_t x = PropertyReadInt32(object, "_graphX") - canvas->panX + canvas->bounds.l;
+	int32_t y = PropertyReadInt32(object, "_graphY") - canvas->panY + canvas->bounds.t;
 	int32_t w = PropertyReadInt32(object, "_graphW");
 	int32_t h = PropertyReadInt32(object, "_graphH");
 
-	if (object->flags & OBJECT_IS_SELECTED && canvasDragging) {
-		x += canvasDragDeltaX;
-		y += canvasDragDeltaY;
+	UIRectangle bounds = UI_RECT_4(x, x + w, y, y + h);
+
+	if (object->flags & OBJECT_IS_SELECTED) {
+		if (canvas->dragging) {
+			bounds = UIRectangleAdd(bounds, UI_RECT_2(canvas->dragDeltaX, canvas->dragDeltaY));
+		}
+
+		if (canvas->resizing) {
+			bounds = UIRectangleAdd(bounds, canvas->resizeOffsets);
+		}
 	}
 
-	return UI_RECT_4(x, x + w, y, y + h);
+	return bounds;
 }
 
 void CanvasSelectObject(Object *object) {
@@ -1681,10 +1793,11 @@ void CanvasSelectObject(Object *object) {
 	}
 
 	UIRectangle bounds = CanvasGetObjectBounds(object);
-	canvasPanX += bounds.l - UI_RECT_WIDTH(canvas->bounds) / 2;
-	canvasPanY += bounds.t - UI_RECT_HEIGHT(canvas->bounds) / 2;
+	canvas->panX += bounds.l - UI_RECT_WIDTH(canvas->bounds) / 2;
+	canvas->panY += bounds.t - UI_RECT_HEIGHT(canvas->bounds) / 2;
 	ObjectSetSelected(object->id);
-	UIElementRepaint(canvas, nullptr);
+	canvas->showPrototype = false;
+	UIElementRefresh(canvas);
 	InspectorPopulate();
 }
 
@@ -1842,19 +1955,69 @@ void CanvasDrawStyle(Object *object, UIRectangle bounds, UIPainter *painter, int
 	}
 }
 
+int ResizeHandleMessage(UIElement *element, UIMessage message, int di, void *dp) {
+	uintptr_t side = (uintptr_t) element->cp;
+
+	if (message == UI_MSG_PAINT) {
+		UIDrawRectangle((UIPainter *) dp, element->bounds, 0xFFF8F8F8, 0xFF404040, UI_RECT_1(1));
+	} else if (message == UI_MSG_LEFT_DOWN) {
+		canvas->originalBounds = CanvasGetObjectBounds(ObjectFind(selectedObjectID));
+	} else if (message == UI_MSG_MOUSE_DRAG && element->window->pressedButton == 1) {
+		if (side == 0) canvas->resizeOffsets = UI_RECT_4(element->window->cursorX - canvas->originalBounds.l, 0, 0, 0);
+		if (side == 1) canvas->resizeOffsets = UI_RECT_4(0, element->window->cursorX - canvas->originalBounds.r, 0, 0);
+		if (side == 2) canvas->resizeOffsets = UI_RECT_4(0, 0, element->window->cursorY - canvas->originalBounds.t, 0);
+		if (side == 3) canvas->resizeOffsets = UI_RECT_4(0, 0, 0, element->window->cursorY - canvas->originalBounds.b);
+		canvas->resizing = true;
+		UIElementRefresh(canvas);
+	} else if (message == UI_MSG_LEFT_UP) {
+		Object *object = ObjectFind(selectedObjectID);
+		UIRectangle canvasOffset = UI_RECT_2((int32_t) canvas->panX - canvas->bounds.l, (int32_t) canvas->panY - canvas->bounds.t);
+		UIRectangle newBounds = UIRectangleAdd(CanvasGetObjectBounds(object), canvasOffset);
+		canvas->resizing = false;
+
+		if (object) {
+			Step step = {};
+			step.type = STEP_MODIFY_PROPERTY;
+			step.property.type = PROP_INT;
+			step.objectID = selectedObjectID;
+
+			strcpy(step.property.cName, "_graphX");
+			step.property.integer = newBounds.l;
+			DocumentApplyStep(step, STEP_APPLY_GROUPED);
+			strcpy(step.property.cName, "_graphY");
+			step.property.integer = newBounds.t;
+			DocumentApplyStep(step, STEP_APPLY_GROUPED);
+			strcpy(step.property.cName, "_graphW");
+			step.property.integer = UI_RECT_WIDTH(newBounds);
+			DocumentApplyStep(step, STEP_APPLY_GROUPED);
+			strcpy(step.property.cName, "_graphH");
+			step.property.integer = UI_RECT_HEIGHT(newBounds);
+			DocumentApplyStep(step);
+		}
+	} else if (message == UI_MSG_GET_CURSOR) {
+		if (side == 0) return UI_CURSOR_RESIZE_LEFT;
+		if (side == 1) return UI_CURSOR_RESIZE_RIGHT;
+		if (side == 2) return UI_CURSOR_RESIZE_UP;
+		if (side == 3) return UI_CURSOR_RESIZE_DOWN;
+	}
+
+	return 0;
+}
+
 int CanvasMessage(UIElement *element, UIMessage message, int di, void *dp) {
 	if (message == UI_MSG_PAINT) {
 		UIPainter *painter = (UIPainter *) dp;
 		UIDrawBlock(painter, element->bounds, 0xFFC0C0C0);
-		UIRectangle selectionBounds = UI_RECT_4(MinimumInteger(canvasLeftDownX, canvasSelectX), MaximumInteger(canvasLeftDownX, canvasSelectX),
-				MinimumInteger(canvasLeftDownY, canvasSelectY), MaximumInteger(canvasLeftDownY, canvasSelectY));
+		UIRectangle selectionBounds = UI_RECT_4(MinimumInteger(canvas->leftDownX, canvas->selectX), MaximumInteger(canvas->leftDownX, canvas->selectX),
+				MinimumInteger(canvas->leftDownY, canvas->selectY), MaximumInteger(canvas->leftDownY, canvas->selectY));
 
-		if (canvasSelecting) {
+		if (canvas->selecting) {
 			UIDrawBlock(painter, selectionBounds, 0xFF99CCFF);
 		}
 
 		for (uintptr_t i = 0; i < objects.Length(); i++) {
 			Object *object = &objects[i];
+			if (!!(object->flags & OBJECT_IN_PROTOTYPE) != canvas->showPrototype) continue;
 			UIRectangle bounds = CanvasGetObjectBounds(object);
 
 			if (bounds.r < element->bounds.l || bounds.l > element->bounds.r
@@ -1863,26 +2026,36 @@ int CanvasMessage(UIElement *element, UIMessage message, int di, void *dp) {
 			}
 
 			UIRectangle selectionIntersection = UIRectangleIntersection(bounds, selectionBounds);
+			bool isSelected = (object->flags & OBJECT_IS_SELECTED) == (inspectorPickData == nullptr)
+				|| (canvas->selecting && UI_RECT_VALID(selectionIntersection));
 
-			if ((object->flags & OBJECT_IS_SELECTED) == (inspectorPickData == nullptr)
-					|| (canvasSelecting && UI_RECT_VALID(selectionIntersection))) {
-				UIDrawBorder(painter, UIRectangleAdd(bounds, UI_RECT_1I(-3)), 0xFF4092FF, UI_RECT_1(3));
-			} 
+			if (!canvas->showPrototype) {
+				if (isSelected) {
+					UIDrawBorder(painter, UIRectangleAdd(bounds, UI_RECT_1I(-3)), 0xFF4092FF, UI_RECT_1(3));
+				} 
 
-			UIDrawString(painter, UI_RECT_4(bounds.l, element->bounds.r, bounds.t - ui.glyphHeight, bounds.t), 
-					object->cName, -1, 0xFF000000, UI_ALIGN_LEFT, nullptr);
+				UIDrawString(painter, UI_RECT_4(bounds.l, element->bounds.r, bounds.t - ui.glyphHeight, bounds.t), 
+						object->cName, -1, 0xFF000000, UI_ALIGN_LEFT, nullptr);
 
-			UIDrawRectangle(painter, bounds, 0xFFE0E0E0, 0xFF404040, UI_RECT_1(1));
-			UIDrawBlock(painter, UI_RECT_4(bounds.l + 1, bounds.r + 1, bounds.b, bounds.b + 1), 0xFF404040);
-			UIDrawBlock(painter, UI_RECT_4(bounds.r, bounds.r + 1, bounds.t + 1, bounds.b + 1), 0xFF404040);
+				UIDrawRectangle(painter, bounds, 0xFFE0E0E0, 0xFF404040, UI_RECT_1(1));
+				UIDrawBlock(painter, UI_RECT_4(bounds.l + 1, bounds.r + 1, bounds.b, bounds.b + 1), 0xFF404040);
+				UIDrawBlock(painter, UI_RECT_4(bounds.r, bounds.r + 1, bounds.t + 1, bounds.b + 1), 0xFF404040);
 
-			if (ObjectIsConditional(object)) {
-				UIRectangle indicator = UI_RECT_4(bounds.l - ui.glyphWidth, bounds.l, bounds.t, bounds.t + ui.glyphHeight);
-				UIDrawBlock(painter, indicator, 0xFFFFFF00);
-				UIDrawString(painter, indicator, "?", -1, 0xFF000000, UI_ALIGN_CENTER, nullptr);
+				if (ObjectIsConditional(object)) {
+					UIRectangle indicator = UI_RECT_4(bounds.l - ui.glyphWidth, bounds.l, bounds.t, bounds.t + ui.glyphHeight);
+					UIDrawBlock(painter, indicator, 0xFFFFFF00);
+					UIDrawString(painter, indicator, "?", -1, 0xFF000000, UI_ALIGN_CENTER, nullptr);
+				}
+
+				bounds = UIRectangleAdd(bounds, UI_RECT_1I(3));
 			}
 
-			bounds = UIRectangleAdd(bounds, UI_RECT_1I(3));
+			if (selectedObjectID == object->id && canvas->resizing) {
+				char buffer[32];
+				snprintf(buffer, sizeof(buffer), "%dx%d", UI_RECT_WIDTH(bounds), UI_RECT_HEIGHT(bounds));
+				UIDrawString(painter, UI_RECT_4(bounds.l, bounds.r, bounds.t - ui.glyphHeight, bounds.t), 
+						buffer, -1, 0xFF000000, UI_ALIGN_CENTER, nullptr);
+			}
 
 			if (object->type == OBJ_VAR_INT || object->type == OBJ_MOD_MULTIPLY) {
 				int32_t value = GraphGetInteger(object);
@@ -1903,17 +2076,21 @@ int CanvasMessage(UIElement *element, UIMessage message, int di, void *dp) {
 				UIDrawString(painter, area, buffer, -1, isLight ? 0xFF000000 : 0xFFFFFFFF, UI_ALIGN_CENTER, nullptr);
 			} else if (object->type == OBJ_VAR_TEXT_STYLE || object->type == OBJ_VAR_ICON_STYLE || object->type == OBJ_STYLE) {
 				CanvasDrawStyle(object, bounds, painter);
+			} else if (object->type == OBJ_INSTANCE) {
+				Property *style = PropertyFind(object, "style", PROP_OBJECT);
+				CanvasDrawStyle(ObjectFind(style ? style->object : 0), bounds, painter);
 			} else {
 				// TODO Preview for the metrics layer. Show the preferred size, insets and gaps?
 			}
 		}
 
-		if (canvasShowArrows) {
+		if (canvas->showArrows && !canvas->showPrototype) {
 			// Draw object connections.
 			// TODO This will be awfully slow when there's many objects...
 
 			for (uintptr_t i = 0; i < objects.Length(); i++) {
 				Object *object = &objects[i];
+				if (!!(object->flags & OBJECT_IN_PROTOTYPE) != canvas->showPrototype) continue;
 				UIRectangle b1 = CanvasGetObjectBounds(object);
 
 				for (uintptr_t j = 0; j < object->properties.Length(); j++) {
@@ -1927,16 +2104,17 @@ int CanvasMessage(UIElement *element, UIMessage message, int di, void *dp) {
 			}
 		}
 	} else if (message == UI_MSG_LEFT_DOWN) {
-		canvasCanDrag = true;
+		canvas->canDrag = true;
 		bool foundObjectToSelect = false;
 
 		for (uintptr_t i = objects.Length(); i > 0; i--) {
 			Object *object = &objects[i - 1];
+			if (!!(object->flags & OBJECT_IN_PROTOTYPE) != canvas->showPrototype) continue;
 			UIRectangle bounds = CanvasGetObjectBounds(object);
 
 			if (UIRectangleContains(bounds, element->window->cursorX, element->window->cursorY)) {
 				if (inspectorPickData) {
-					canvasCanDrag = false;
+					canvas->canDrag = false;
 
 					Step step = {};
 					step.type = STEP_MODIFY_PROPERTY;
@@ -1954,8 +2132,8 @@ int CanvasMessage(UIElement *element, UIMessage message, int di, void *dp) {
 					}
 
 					ObjectSetSelected(object->id, false /* do not clear selection flag from previous */);
-					canvasDragOffsetX = bounds.l - element->window->cursorX;
-					canvasDragOffsetY = bounds.t - element->window->cursorY;
+					canvas->dragOffsetX = bounds.l - element->window->cursorX;
+					canvas->dragOffsetY = bounds.t - element->window->cursorY;
 				}
 
 				foundObjectToSelect = true;
@@ -1971,66 +2149,69 @@ int CanvasMessage(UIElement *element, UIMessage message, int di, void *dp) {
 			}
 		}
 
-		canvasLeftDownX = element->window->cursorX;
-		canvasLeftDownY = element->window->cursorY;
+		canvas->leftDownX = element->window->cursorX;
+		canvas->leftDownY = element->window->cursorY;
 
-		UIElementRepaint(element, nullptr);
+		UIElementRefresh(element);
 		InspectorPopulate();
 		InspectorPickTargetEnd();
-	} else if (message == UI_MSG_LEFT_UP && canvasDragging) {
+	} else if (message == UI_MSG_LEFT_UP && canvas->dragging) {
 		Object *object = ObjectFind(selectedObjectID);
 		int32_t oldX = PropertyReadInt32(object, "_graphX");
 		int32_t oldY = PropertyReadInt32(object, "_graphY");
 
-		if ((canvasDragDeltaX || canvasDragDeltaY) && object) {
+		if ((canvas->dragDeltaX || canvas->dragDeltaY) && object) {
 			Step step = {};
 			step.type = STEP_MODIFY_PROPERTY;
 			step.property.type = PROP_INT;
 
 			for (uintptr_t i = 0; i < objects.Length(); i++) {
 				Object *object = &objects[i];
+				if (!!(object->flags & OBJECT_IN_PROTOTYPE) != canvas->showPrototype) continue;
 
 				if ((object->flags & OBJECT_IS_SELECTED) && object->id != selectedObjectID) {
 					step.objectID = object->id;
 					strcpy(step.property.cName, "_graphX");
-					step.property.integer = PropertyReadInt32(object, "_graphX") + canvasDragDeltaX;
+					step.property.integer = PropertyReadInt32(object, "_graphX") + canvas->dragDeltaX;
 					DocumentApplyStep(step, STEP_APPLY_GROUPED);
 					strcpy(step.property.cName, "_graphY");
-					step.property.integer = PropertyReadInt32(object, "_graphY") + canvasDragDeltaY;
+					step.property.integer = PropertyReadInt32(object, "_graphY") + canvas->dragDeltaY;
 					DocumentApplyStep(step, STEP_APPLY_GROUPED);
 				}
 			}
 
 			step.objectID = selectedObjectID;
 			strcpy(step.property.cName, "_graphX");
-			step.property.integer = oldX + canvasDragDeltaX;
+			step.property.integer = oldX + canvas->dragDeltaX;
 			DocumentApplyStep(step, STEP_APPLY_GROUPED);
 			strcpy(step.property.cName, "_graphY");
-			step.property.integer = oldY + canvasDragDeltaY;
+			step.property.integer = oldY + canvas->dragDeltaY;
 			DocumentApplyStep(step);
 		}
 
-		canvasDragging = false;
-		UIElementRepaint(element, nullptr);
-	} else if (message == UI_MSG_MOUSE_DRAG && element->window->pressedButton == 1 && selectedObjectID && canvasCanDrag) {
-		int32_t dx = canvasLeftDownX - element->window->cursorX;
-		int32_t dy = canvasLeftDownY - element->window->cursorY;
+		canvas->dragging = false;
+		UIElementRefresh(canvas);
+	} else if (message == UI_MSG_MOUSE_DRAG && element->window->pressedButton == 1 && selectedObjectID && canvas->canDrag) {
+		int32_t dx = canvas->leftDownX - element->window->cursorX;
+		int32_t dy = canvas->leftDownY - element->window->cursorY;
 
-		if (canvasDragging || dx * dx + dy * dy > 200) {
-			int32_t canvasDragNewX = element->window->cursorX + canvasPanX + canvasDragOffsetX - element->bounds.l;
-			int32_t canvasDragNewY = element->window->cursorY + canvasPanY + canvasDragOffsetY - element->bounds.t;
-			canvasDragNewX -= canvasDragNewX % CANVAS_ALIGN, canvasDragNewY -= canvasDragNewY % CANVAS_ALIGN;
-			canvasDragDeltaX = canvasDragNewX - PropertyReadInt32(ObjectFind(selectedObjectID), "_graphX");
-			canvasDragDeltaY = canvasDragNewY - PropertyReadInt32(ObjectFind(selectedObjectID), "_graphY");
-			canvasDragging = true;
-			UIElementRepaint(element, nullptr);
+		if (canvas->dragging || dx * dx + dy * dy > 200) {
+			int32_t canvasDragNewX = element->window->cursorX + canvas->panX + canvas->dragOffsetX - element->bounds.l;
+			int32_t canvasDragNewY = element->window->cursorY + canvas->panY + canvas->dragOffsetY - element->bounds.t;
+			if (!canvas->showPrototype) canvasDragNewX -= canvasDragNewX % CANVAS_ALIGN, canvasDragNewY -= canvasDragNewY % CANVAS_ALIGN;
+			canvas->dragDeltaX = canvasDragNewX - PropertyReadInt32(ObjectFind(selectedObjectID), "_graphX");
+			canvas->dragDeltaY = canvasDragNewY - PropertyReadInt32(ObjectFind(selectedObjectID), "_graphY");
+			canvas->dragging = true;
+			UIElementRefresh(canvas);
 		}
-	} else if (message == UI_MSG_LEFT_UP && canvasSelecting) {
-		UIRectangle selectionBounds = UI_RECT_4(MinimumInteger(canvasLeftDownX, canvasSelectX), MaximumInteger(canvasLeftDownX, canvasSelectX),
-				MinimumInteger(canvasLeftDownY, canvasSelectY), MaximumInteger(canvasLeftDownY, canvasSelectY));
+	} else if (message == UI_MSG_LEFT_UP && canvas->selecting) {
+		UIRectangle selectionBounds = UI_RECT_4(MinimumInteger(canvas->leftDownX, canvas->selectX), MaximumInteger(canvas->leftDownX, canvas->selectX),
+				MinimumInteger(canvas->leftDownY, canvas->selectY), MaximumInteger(canvas->leftDownY, canvas->selectY));
 
 		for (uintptr_t i = 0; i < objects.Length(); i++) {
 			Object *object = &objects[i];
+			if (!!(object->flags & OBJECT_IN_PROTOTYPE) != canvas->showPrototype) continue;
+
 			UIRectangle bounds = CanvasGetObjectBounds(object);
 			UIRectangle selectionIntersection = UIRectangleIntersection(bounds, selectionBounds);
 
@@ -2039,57 +2220,83 @@ int CanvasMessage(UIElement *element, UIMessage message, int di, void *dp) {
 			}
 		}
 
-		canvasSelecting = false;
-		UIElementRepaint(element, nullptr);
+		canvas->selecting = false;
+		UIElementRefresh(canvas);
 	} else if (message == UI_MSG_MOUSE_DRAG && element->window->pressedButton == 1 && !selectedObjectID) {
-		canvasSelectX = element->window->cursorX;
-		canvasSelectY = element->window->cursorY;
-		canvasSelecting = true;
-		UIElementRepaint(element, nullptr);
+		canvas->selectX = element->window->cursorX;
+		canvas->selectY = element->window->cursorY;
+		canvas->selecting = true;
+		UIElementRefresh(canvas);
 	} else if (message == UI_MSG_MIDDLE_DOWN) {
-		canvasLastPanPointX = element->window->cursorX;
-		canvasLastPanPointY = element->window->cursorY;
+		canvas->lastPanPointX = element->window->cursorX;
+		canvas->lastPanPointY = element->window->cursorY;
 		_UIWindowSetCursor(element->window, UI_CURSOR_HAND);
 	} else if (message == UI_MSG_MIDDLE_UP) {
 		_UIWindowSetCursor(element->window, UI_CURSOR_ARROW);
 	} else if (message == UI_MSG_MOUSE_DRAG && element->window->pressedButton == 2) {
-		canvasPanX -= element->window->cursorX - canvasLastPanPointX;
-		canvasPanY -= element->window->cursorY - canvasLastPanPointY;
-		canvasLastPanPointX = element->window->cursorX;
-		canvasLastPanPointY = element->window->cursorY;
-		UIElementRepaint(element, nullptr);
+		canvas->panX -= element->window->cursorX - canvas->lastPanPointX;
+		canvas->panY -= element->window->cursorY - canvas->lastPanPointY;
+		canvas->lastPanPointX = element->window->cursorX;
+		canvas->lastPanPointY = element->window->cursorY;
+		UIElementRefresh(canvas);
 	} else if (message == UI_MSG_LAYOUT) {
-		int width = UIElementMessage(canvasControls, UI_MSG_GET_WIDTH, 0, 0);
-		int height = UIElementMessage(canvasControls, UI_MSG_GET_HEIGHT, 0, 0);
+		UIElement *controls = canvas->showPrototype ? &prototypeControls->e : &graphControls->e;
+		(canvas->showPrototype ? &graphControls->e : &prototypeControls->e)->flags |= UI_ELEMENT_HIDE;
+		controls->flags &= ~UI_ELEMENT_HIDE;
+		int width = UIElementMessage(controls, UI_MSG_GET_WIDTH, 0, 0);
+		int height = UIElementMessage(controls, UI_MSG_GET_HEIGHT, 0, 0);
 		UIRectangle bounds = UI_RECT_4(element->bounds.l + 10, element->bounds.l + 10 + width, element->bounds.b - 10 - height, element->bounds.b - 10);
-		UIElementMove(canvasControls, bounds, false);
+		UIElementMove(controls, bounds, false);
+
+		Object *object = ObjectFind(selectedObjectID);
+
+		if (canvas->showPrototype && selectedObjectID && object && (object->flags & OBJECT_IN_PROTOTYPE)) {
+			UIRectangle bounds = CanvasGetObjectBounds(object);
+			int cx = (bounds.l + bounds.r) / 2, cy = (bounds.t + bounds.b) / 2;
+			const int size = 3;
+			UIElementMove(canvas->resizeHandles[0], UI_RECT_4(bounds.l - size, bounds.l + size + 1, cy - size, cy + size + 1), false);
+			UIElementMove(canvas->resizeHandles[1], UI_RECT_4(bounds.r - size - 1, bounds.r + size, cy - size, cy + size + 1), false);
+			UIElementMove(canvas->resizeHandles[2], UI_RECT_4(cx - size, cx + size + 1, bounds.t - size, bounds.t + size + 1), false);
+			UIElementMove(canvas->resizeHandles[3], UI_RECT_4(cx - size, cx + size + 1, bounds.b - size - 1, bounds.b + size), false);
+			canvas->resizeHandles[0]->flags &= ~UI_ELEMENT_HIDE;
+			canvas->resizeHandles[1]->flags &= ~UI_ELEMENT_HIDE;
+			canvas->resizeHandles[2]->flags &= ~UI_ELEMENT_HIDE;
+			canvas->resizeHandles[3]->flags &= ~UI_ELEMENT_HIDE;
+		} else {
+			canvas->resizeHandles[0]->flags |= UI_ELEMENT_HIDE;
+			canvas->resizeHandles[1]->flags |= UI_ELEMENT_HIDE;
+			canvas->resizeHandles[2]->flags |= UI_ELEMENT_HIDE;
+			canvas->resizeHandles[3]->flags |= UI_ELEMENT_HIDE;
+		}
 	}
 
 	return 0;
 }
 
 void CanvasToggleArrows(void *) {
-	canvasShowArrows = !canvasShowArrows;
+	canvas->showArrows = !canvas->showArrows;
 	UIElementRepaint(canvas, nullptr);
 }
 
+void CanvasSwitchView(void *) {
+	canvas->showPrototype = !canvas->showPrototype;
+	UIElementRefresh(canvas);
+}
+
 //////////////////////////////////////////////////////////////
 
-void ObjectAddCommandInternal(void *cp) {
-	Object object = {};
-	object.type = (ObjectType) (uintptr_t) cp;
-	object.id = ++objectIDAllocator;
-	Property p;
-	int32_t x = canvasPanX + UI_RECT_WIDTH(canvas->bounds) / 2;
-	int32_t y = canvasPanY + UI_RECT_HEIGHT(canvas->bounds) / 2;
-	x -= x % CANVAS_ALIGN, y -= y % CANVAS_ALIGN;
-	int32_t w = object.type == OBJ_COMMENT ? 30 : 80;
-	int32_t h = object.type == OBJ_COMMENT ? 10 : 60;
-	p = { .type = PROP_INT, .integer = x }; strcpy(p.cName, "_graphX"); object.properties.Add(p);
-	p = { .type = PROP_INT, .integer = y }; strcpy(p.cName, "_graphY"); object.properties.Add(p);
-	p = { .type = PROP_INT, .integer = w }; strcpy(p.cName, "_graphW"); object.properties.Add(p);
-	p = { .type = PROP_INT, .integer = h }; strcpy(p.cName, "_graphH"); object.properties.Add(p);
-	
+int PrototypeMessage(UIElement *element, UIMessage message, int di, void *dp) {
+	if (message == UI_MSG_PAINT) {
+		UIPainter *painter = (UIPainter *) dp;
+		UIDrawBlock(painter, element->bounds, 0xFFC0C0C0);
+	}
+
+	return 0;
+}
+
+//////////////////////////////////////////////////////////////
+
+void ObjectAddInternal(Object object) {
 	Step step = {};
 	step.type = STEP_ADD_OBJECT;
 	step.object = object;
@@ -2101,10 +2308,51 @@ void ObjectAddCommandInternal(void *cp) {
 
 	ObjectSetSelected(object.id);
 	InspectorPopulate();
+	UIElementRefresh(canvas);
+}
+
+void ObjectAddCommandInternal(void *cp) {
+	Object object = {};
+	object.type = (ObjectType) (uintptr_t) cp;
+	object.id = ++objectIDAllocator;
+	Property p;
+	int32_t x = canvas->panX + UI_RECT_WIDTH(canvas->bounds) / 2;
+	int32_t y = canvas->panY + UI_RECT_HEIGHT(canvas->bounds) / 2;
+	x -= x % CANVAS_ALIGN, y -= y % CANVAS_ALIGN;
+	int32_t w = object.type == OBJ_COMMENT ? 30 : 80;
+	int32_t h = object.type == OBJ_COMMENT ? 10 : 60;
+	p = { .type = PROP_INT, .integer = x }; strcpy(p.cName, "_graphX"); object.properties.Add(p);
+	p = { .type = PROP_INT, .integer = y }; strcpy(p.cName, "_graphY"); object.properties.Add(p);
+	p = { .type = PROP_INT, .integer = w }; strcpy(p.cName, "_graphW"); object.properties.Add(p);
+	p = { .type = PROP_INT, .integer = h }; strcpy(p.cName, "_graphH"); object.properties.Add(p);
+	ObjectAddInternal(object);
+}
+
+void ObjectAddInstanceCommandInternal(void *cp) {
+	Object *style = (Object *) cp;
+	Property *metricsProperty = PropertyFind(style, "metrics", PROP_OBJECT);
+	Object *metrics = ObjectFind(metricsProperty ? metricsProperty->object : 0);
+	int32_t preferredWidth = PropertyReadInt32(metrics, "preferredWidth");
+	int32_t preferredHeight = PropertyReadInt32(metrics, "preferredHeight");
+	Object object = {};
+	object.type = OBJ_INSTANCE;
+	object.id = ++objectIDAllocator;
+	object.flags |= OBJECT_IN_PROTOTYPE;
+	Property p;
+	int32_t x = canvas->panX + UI_RECT_WIDTH(canvas->bounds) / 2;
+	int32_t y = canvas->panY + UI_RECT_HEIGHT(canvas->bounds) / 2;
+	int32_t w = preferredWidth ?: 100;
+	int32_t h = preferredHeight ?: 100;
+	p = { .type = PROP_INT, .integer = x }; strcpy(p.cName, "_graphX"); object.properties.Add(p);
+	p = { .type = PROP_INT, .integer = y }; strcpy(p.cName, "_graphY"); object.properties.Add(p);
+	p = { .type = PROP_INT, .integer = w }; strcpy(p.cName, "_graphW"); object.properties.Add(p);
+	p = { .type = PROP_INT, .integer = h }; strcpy(p.cName, "_graphH"); object.properties.Add(p);
+	p = { .type = PROP_OBJECT, .object = style->id }; strcpy(p.cName, "style"); object.properties.Add(p);
+	ObjectAddInternal(object);
 }
 
 void ObjectAddCommand(void *) {
-	UIMenu *menu = UIMenuCreate(window->pressed, UI_MENU_NO_SCROLL);
+	UIMenu *menu = UIMenuCreate(window->pressed, UI_MENU_NO_SCROLL | UI_MENU_PLACE_ABOVE);
 	UIMenuAddItem(menu, 0, "Style", -1, ObjectAddCommandInternal, (void *) (uintptr_t) OBJ_STYLE);
 	UIMenuAddItem(menu, 0, "Comment", -1, ObjectAddCommandInternal, (void *) (uintptr_t) OBJ_COMMENT);
 	UIMenuAddItem(menu, 0, "Color variable", -1, ObjectAddCommandInternal, (void *) (uintptr_t) OBJ_VAR_COLOR);
@@ -2123,11 +2371,24 @@ void ObjectAddCommand(void *) {
 	UIMenuShow(menu);
 }
 
+void ObjectAddInstanceCommand(void *) {
+	UIMenu *menu = UIMenuCreate(window->pressed, UI_MENU_NO_SCROLL | UI_MENU_PLACE_ABOVE);
+
+	for (uintptr_t i = 0; i < objects.Length(); i++) {
+		if (objects[i].type == OBJ_STYLE) {
+			UIMenuAddItem(menu, 0, objects[i].cName, -1, ObjectAddInstanceCommandInternal, (void *) (uintptr_t) &objects[i]);
+		}
+	}
+
+	UIMenuShow(menu);
+}
+
 void ObjectDeleteCommand(void *) {
 	Array<uint64_t> list = {};
 
 	for (uintptr_t i = 0; i < objects.Length(); i++) {
 		Object *object = &objects[i];
+		if (!!(object->flags & OBJECT_IN_PROTOTYPE) != canvas->showPrototype) continue;
 
 		if (object->flags & OBJECT_IS_SELECTED) {
 			list.Add(object->id);
@@ -2196,33 +2457,47 @@ int main() {
 	ui.theme = _uiThemeClassic;
 	window = UIWindowCreate(0, UI_ELEMENT_PARENT_PUSH | UI_WINDOW_MAXIMIZE, "Designer", 0, 0);
 	window->e.messageUser = WindowMessage;
-	UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_EXPAND);
 
-	UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL | UI_PANEL_GRAY | UI_PANEL_SMALL_SPACING);
+	UISplitPaneCreate(0, UI_ELEMENT_PARENT_PUSH, 0.75f);
+	canvas = (Canvas *) UIElementCreate(sizeof(Canvas), 0, 0, CanvasMessage, "Canvas");
+	UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_EXPAND);
+	UIPanelCreate(0, UI_ELEMENT_PARENT_PUSH | UI_PANEL_HORIZONTAL | UI_PANEL_GRAY | UI_PANEL_MEDIUM_SPACING);
 #ifdef OS_ESSENCE
 		UIButtonCreate(0, UI_BUTTON_DROP_DOWN, "File", -1)->invoke = DocumentFileMenu;
 #else
 		UIButtonCreate(0, 0, "Save", -1)->invoke = DocumentSave;
 #endif
-		UIButtonCreate(0, 0, "Add object...", -1)->invoke = ObjectAddCommand;
-		UISpacerCreate(0, 0, 15, 0);
-		labelMessage = UILabelCreate(0, UI_ELEMENT_H_FILL, 0, 0);
+		UIButtonCreate(0, 0, "Switch view", -1)->invoke = CanvasSwitchView;
 	UIParentPop();
-
 	UISpacerCreate(0, UI_SPACER_LINE, 0, 1);
+	inspector = &UIPanelCreate(0, UI_ELEMENT_V_FILL | UI_PANEL_GRAY | UI_PANEL_MEDIUM_SPACING | UI_PANEL_SCROLL | UI_PANEL_EXPAND)->e;
 
-	UISplitPaneCreate(0, UI_ELEMENT_PARENT_PUSH | UI_ELEMENT_V_FILL, 0.75f);
-	canvas = UIElementCreate(sizeof(UIElement), 0, 0, CanvasMessage, "Canvas");
-	inspector = &UIPanelCreate(0, UI_PANEL_GRAY | UI_PANEL_MEDIUM_SPACING | UI_PANEL_SCROLL | UI_PANEL_EXPAND)->e;
 	InspectorPopulate();
 
-	canvasControls = &UIPanelCreate(canvas, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH)->e;
-		UIButtonCreate(0, 0, "Toggle arrows", -1)->invoke = CanvasToggleArrows;
+	graphControls = UIPanelCreate(canvas, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
+	graphControls->gap = -1;
+		UIButtonCreate(0, UI_BUTTON_SMALL, "Toggle arrows", -1)->invoke = CanvasToggleArrows;
+		UIButtonCreate(0, UI_BUTTON_SMALL, "Add object \x18", -1)->invoke = ObjectAddCommand;
 	UIParentPop();
 
+	prototypeControls = UIPanelCreate(canvas, UI_PANEL_HORIZONTAL | UI_ELEMENT_PARENT_PUSH);
+	prototypeControls->gap = -1;
+		UIButtonCreate(0, UI_BUTTON_SMALL, "Add instance \x18", -1)->invoke = ObjectAddInstanceCommand;
+	UIParentPop();
+
+	canvas->resizeHandles[0] = UIElementCreate(sizeof(UIElement), canvas, 0, ResizeHandleMessage, "Resize handle");
+	canvas->resizeHandles[1] = UIElementCreate(sizeof(UIElement), canvas, 0, ResizeHandleMessage, "Resize handle");
+	canvas->resizeHandles[2] = UIElementCreate(sizeof(UIElement), canvas, 0, ResizeHandleMessage, "Resize handle");
+	canvas->resizeHandles[3] = UIElementCreate(sizeof(UIElement), canvas, 0, ResizeHandleMessage, "Resize handle");
+	canvas->resizeHandles[0]->cp = (void *) (uintptr_t) 0;
+	canvas->resizeHandles[1]->cp = (void *) (uintptr_t) 1;
+	canvas->resizeHandles[2]->cp = (void *) (uintptr_t) 2;
+	canvas->resizeHandles[3]->cp = (void *) (uintptr_t) 3;
+
 	UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_LETTER('Z'), 1 /* ctrl */, 0, 0, DocumentUndoStep, 0));
 	UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_LETTER('Y'), 1 /* ctrl */, 0, 0, DocumentRedoStep, 0));
 	UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_LETTER('D'), 1 /* ctrl */, 0, 0, ObjectDuplicateCommand, 0));
+	UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_FKEY(2), 0, 0, 0, CanvasSwitchView, 0));
 	UIWindowRegisterShortcut(window, UI_SHORTCUT(UI_KEYCODE_DELETE, 0, 0, 0, ObjectDeleteCommand, 0));
 
 #ifdef OS_ESSENCE