Maelstrom: When a UI area changes it's rectangle all the anchored areas recalculate their rectangles (recursively)

https://github.com/libsdl-org/Maelstrom/commit/64648fa932623e5b326dac6b61694adfc83e40b3

From 64648fa932623e5b326dac6b61694adfc83e40b3 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 28 Oct 2011 23:16:08 -0400
Subject: [PATCH] When a UI area changes it's rectangle all the anchored areas
 recalculate their rectangles (recursively) Areas are the size of their
 parents and anchored to their center by default. The KeyButton uses the
 template and child element functionality to allow customization from XML.

---
 UI/UITemplates.xml             |  26 +++++---
 UI/about_credits.xml           |   9 +--
 UI/about_game.xml              |   9 +--
 UI/about_story.xml             |   9 +--
 UI/loading.xml                 |   5 +-
 UI/main.xml                    |  42 ++++++-------
 UI/splash.xml                  |   4 +-
 UIDialog.cpp                   |  16 ++---
 UIElementKeyButton.cpp         |  37 ++++-------
 UIElementKeyButton.h           |   6 --
 screenlib/UIArea.cpp           | 109 ++++++++++++++++++++++++++-------
 screenlib/UIArea.h             |  51 +++++++--------
 screenlib/UIBaseElement.cpp    |   4 +-
 screenlib/UIElementLabel.cpp   |   7 +--
 screenlib/UIElementLine.cpp    |   2 +-
 screenlib/UIElementRect.cpp    |   4 +-
 screenlib/UIElementTexture.cpp |   7 +--
 screenlib/UIManager.cpp        |   3 +-
 screenlib/UIPanel.cpp          |   2 -
 19 files changed, 196 insertions(+), 156 deletions(-)

diff --git a/UI/UITemplates.xml b/UI/UITemplates.xml
index 9c94aa7a..ddc3801c 100644
--- a/UI/UITemplates.xml
+++ b/UI/UITemplates.xml
@@ -4,37 +4,30 @@
 			<Rectangle>
 				<Color r="0x75" g="0x75" b="0xFF"/>
 				<Size w="514" h="386"/>
-				<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
 			</Rectangle>
 			<Rectangle>
 				<Color r="0x75" g="0x75" b="0xFF"/>
 				<Size w="516" h="388"/>
-				<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
 			</Rectangle>
 			<Rectangle>
 				<Color r="0x9C" g="0x9C" b="0xFF"/>
 				<Size w="518" h="390"/>
-				<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
 			</Rectangle>
 			<Rectangle>
 				<Color r="0x9C" g="0x9C" b="0xFF"/>
 				<Size w="520" h="392"/>
-				<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
 			</Rectangle>
 			<Rectangle>
 				<Color r="0xC3" g="0xC3" b="0xFF"/>
 				<Size w="522" h="394"/>
-				<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
 			</Rectangle>
 			<Rectangle>
 				<Color r="0x9C" g="0x9C" b="0xFF"/>
 				<Size w="524" h="396"/>
-				<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
 			</Rectangle>
 			<Rectangle>
 				<Color r="0x75" g="0x75" b="0xFF"/>
 				<Size w="526" h="398"/>
-				<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
 			</Rectangle>
 		</Elements>
 	</UIPanel>
@@ -43,6 +36,9 @@
 	<Label templateName="SmallWhite" template="Small">
 		<Color r="0xFF" g="0xFF" b="0xFF"/>
 	</Label>
+	<Label templateName="SmallBlack" template="Small">
+		<Color r="0x00" g="0x00" b="0x00"/>
+	</Label>
 	<Label templateName="SmallYellow" template="Small">
 		<Color r="0xFF" g="0xFF" b="0x00"/>
 	</Label>
@@ -74,4 +70,20 @@
 	<Label templateName="LargeYellowUnderline" template="Large" fontStyle="UNDERLINE">
 		<Color r="0xFF" g="0xFF" b="0x00"/>
 	</Label>
+
+	<Button templateName="HotkeyOnly">
+		<Size w="0" h="0"/>
+	</Button>
+	<KeyButton templateName="MenuButton">
+		<Size w="32" h="32"/>
+		<Elements>
+			<Icon id="100"/>
+			<Label name="shadow" template="SmallWhite">
+				<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT" x="14" y="10"/>
+			</Icon>
+			<Label name="label" template="SmallBlack">
+				<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT" x="13" y="9"/>
+			</Label>
+		</Elements>
+	</KeyButton>
 </UITemplates>
diff --git a/UI/about_credits.xml b/UI/about_credits.xml
index c146c34d..aa776c56 100644
--- a/UI/about_credits.xml
+++ b/UI/about_credits.xml
@@ -1,8 +1,6 @@
 <UIPanel template="FramedPanel">
 	<Elements>
-		<Title name="image" id="135">
-			<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
-		</Title>
+		<Title name="image" id="135"/>
 
 		<!-- Porting credits -->
 		<Rectangle fill="true">
@@ -19,9 +17,6 @@
 		</Label>
 
 		<!-- Buttons -->
-		<Button hotkey="any" clickPanel="main" clickSound="131">
-			<SizeParent/>
-			<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
-		</Button>
+		<Button hotkey="any" clickPanel="main" clickSound="131"/>
 	</Elements>
 </UIPanel>
diff --git a/UI/about_game.xml b/UI/about_game.xml
index 249ae89c..194c7b99 100644
--- a/UI/about_game.xml
+++ b/UI/about_game.xml
@@ -1,8 +1,6 @@
 <UIPanel template="FramedPanel" delegate="AboutPanel">
 	<Elements>
-		<Title name="image" id="134">
-			<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
-		</Title>
+		<Title name="image" id="134"/>
 
 		<!-- Shield icon -->
 		<Icon id="137">
@@ -31,9 +29,6 @@
 
 		<!-- Buttons -->
 		<Button hotkey="any" clickPanel="main" clickSound="106"/>
-		<Button hotkey="Return" clickPanel="about_credits" clickSound="102">
-			<SizeParent/>
-			<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
-		</Button>
+		<Button hotkey="Return" clickPanel="about_credits" clickSound="102"/>
 	</Elements>
 </UIPanel>
diff --git a/UI/about_story.xml b/UI/about_story.xml
index e6ef9ea0..a75d52c9 100644
--- a/UI/about_story.xml
+++ b/UI/about_story.xml
@@ -1,14 +1,9 @@
 <UIPanel template="FramedPanel">
 	<Elements>
-		<Title name="image" id="133">
-			<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
-		</Title>
+		<Title name="image" id="133"/>
 
 		<!-- Buttons -->
 		<Button hotkey="any" clickPanel="main" clickSound="106"/>
-		<Button hotkey="Return" clickPanel="about_game" clickSound="102">
-			<SizeParent/>
-			<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
-		</Button>
+		<Button hotkey="Return" clickPanel="about_game" clickSound="102"/>
 	</Elements>
 </UIPanel>
diff --git a/UI/loading.xml b/UI/loading.xml
index 6b829d88..5b6add2a 100644
--- a/UI/loading.xml
+++ b/UI/loading.xml
@@ -1,8 +1,7 @@
 <UIPanel template="FramedPanel" enterSound="111" leaveSound="123">
 	<Elements>
-		<Title name="image" id="130">
-			<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
-		</Title>
+		<Title name="image" id="130"/>
+
 		<Label template="SmallYellow" text="Loading...">
 			<Anchor anchorFrom="CENTER" anchorTo="BOTTOM" anchor="image" y="20"/>
 		</Label>
diff --git a/UI/main.xml b/UI/main.xml
index 0276074f..7032ede9 100644
--- a/UI/main.xml
+++ b/UI/main.xml
@@ -5,7 +5,7 @@
 		</Title>
 
 		<!-- Catch-all button to bonk when a key is pressed -->
-		<Button hotkey="any" clickSound="108"/>
+		<Button template="HotkeyOnly" hotkey="any" clickSound="108"/>
 
 		<!-- dividers -->
 		<Line name="vertical_divider">
@@ -134,43 +134,43 @@
 		</Label>
 
 		<!-- Instructions -->
-		<KeyButton name="PlayButton" hotkey="P" clickSound="114">
+		<KeyButton name="PlayButton" template="MenuButton" hotkey="P" clickSound="114">
 			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPRIGHT" anchor="vertical_divider" x="9" y="10"/>
 		</KeyButton>
 		<Label template="SmallYellow" text=" Start playing Maelstrom">
 			<Anchor anchorFrom="BOTTOMLEFT" anchorTo="TOPRIGHT" anchor="PlayButton" x="3" y="21"/>
 		</Label>
-		<KeyButton name="ControlsButton" hotkey="C" clickSound="119">
+		<KeyButton name="ControlsButton" template="MenuButton" hotkey="C" clickSound="119">
 			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT" anchor="PlayButton" y="34"/>
 		</KeyButton>
 		<Label template="SmallYellow" text=" Configure the game controls">
 			<Anchor anchorFrom="BOTTOMLEFT" anchorTo="TOPRIGHT" anchor="ControlsButton" x="3" y="21"/>
 		</Label>
-		<KeyButton name="ZapButton" hotkey="Z" clickSound="107">
+		<KeyButton name="ZapButton" template="MenuButton" hotkey="Z" clickSound="107">
 			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT" anchor="ControlsButton" y="34"/>
 		</KeyButton>
 		<Label template="SmallYellow" text=" Zap the high scores">
 			<Anchor anchorFrom="BOTTOMLEFT" anchorTo="TOPRIGHT" anchor="ZapButton" x="3" y="21"/>
 		</Label>
-		<KeyButton name="AboutButton" hotkey="A" clickSound="122">
+		<KeyButton name="AboutButton" template="MenuButton" hotkey="A" clickSound="122">
 			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT" anchor="ZapButton" y="34"/>
 		</KeyButton>
 		<Label template="SmallYellow" text=" About Maelstrom...">
 			<Anchor anchorFrom="BOTTOMLEFT" anchorTo="TOPRIGHT" anchor="AboutButton" x="3" y="21"/>
 		</Label>
-		<KeyButton name="QuitButton" hotkey="Q" clickSound="106">
+		<KeyButton name="QuitButton" template="MenuButton" hotkey="Q" clickSound="106">
 			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT" anchor="AboutButton" y="68"/>
 		</KeyButton>
 		<Label template="SmallYellow" text=" Quit Maelstrom">
 			<Anchor anchorFrom="BOTTOMLEFT" anchorTo="TOPRIGHT" anchor="QuitButton" x="3" y="21"/>
 		</Label>
-		<KeyButton name="VolumeDownButton" hotkey="0">
+		<KeyButton name="VolumeDownButton" template="MenuButton" hotkey="0">
 			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT" anchor="QuitButton" y="34"/>
 		</KeyButton>
 		<Label template="SmallYellow" text="-">
 			<Anchor anchorFrom="BOTTOMLEFT" anchorTo="TOPRIGHT" anchor="VolumeDownButton" x="3" y="21"/>
 		</Label>
-		<KeyButton name="VolumeUpButton" hotkey="8">
+		<KeyButton name="VolumeUpButton" template="MenuButton" hotkey="8">
 			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT" anchor="VolumeDownButton" x="40"/>
 		</KeyButton>
 		<Label template="SmallYellow" text=" Set Sound Volume">
@@ -178,19 +178,19 @@
 		</Label>
 
 		<!-- Hidden action buttons -->
-		<Button name="ToggleFullscreen" hotkey="ALT-Return"/>
-		<Button name="Cheat" hotkey="L" clickSound="124"/>
-		<Button name="Special" hotkey="X" clickSound="128"/>
-		<Button name="Screenshot" hotkey="F3"/>
-		<Button name="SetVolume0" hotkey="0"/>
-		<Button name="SetVolume1" hotkey="1"/>
-		<Button name="SetVolume2" hotkey="2"/>
-		<Button name="SetVolume3" hotkey="3"/>
-		<Button name="SetVolume4" hotkey="4"/>
-		<Button name="SetVolume5" hotkey="5"/>
-		<Button name="SetVolume6" hotkey="6"/>
-		<Button name="SetVolume7" hotkey="7"/>
-		<Button name="SetVolume8" hotkey="8"/>
+		<Button name="ToggleFullscreen" template="HotkeyOnly" hotkey="ALT-Return"/>
+		<Button name="Cheat" template="HotkeyOnly" hotkey="L" clickSound="124"/>
+		<Button name="Special" template="HotkeyOnly" hotkey="X" clickSound="128"/>
+		<Button name="Screenshot" template="HotkeyOnly" hotkey="F3"/>
+		<Button name="SetVolume0" template="HotkeyOnly" hotkey="0"/>
+		<Button name="SetVolume1" template="HotkeyOnly" hotkey="1"/>
+		<Button name="SetVolume2" template="HotkeyOnly" hotkey="2"/>
+		<Button name="SetVolume3" template="HotkeyOnly" hotkey="3"/>
+		<Button name="SetVolume4" template="HotkeyOnly" hotkey="4"/>
+		<Button name="SetVolume5" template="HotkeyOnly" hotkey="5"/>
+		<Button name="SetVolume6" template="HotkeyOnly" hotkey="6"/>
+		<Button name="SetVolume7" template="HotkeyOnly" hotkey="7"/>
+		<Button name="SetVolume8" template="HotkeyOnly" hotkey="8"/>
 
 		<!-- Credits -->
 		<Label template="SmallYellow" text="Port to SDL by Sam Lantinga">
diff --git a/UI/splash.xml b/UI/splash.xml
index 3da06b90..8b8e1a3c 100644
--- a/UI/splash.xml
+++ b/UI/splash.xml
@@ -1,7 +1,5 @@
 <UIPanel>
 	<Elements>
-		<Title id="999">
-			<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
-		</Title>
+		<Title id="999"/>
 	</Elements>
 </UIPanel>
diff --git a/UIDialog.cpp b/UIDialog.cpp
index a6b67147..ddeb7e0b 100644
--- a/UIDialog.cpp
+++ b/UIDialog.cpp
@@ -63,15 +63,15 @@ UIDialog::Draw()
 	int maxx, maxy;
 
 	if (m_step < EXPAND_STEPS) {
-		w = (m_rect.w*m_step)/EXPAND_STEPS;
-		h = (m_rect.h*m_step)/EXPAND_STEPS;
-		x = m_rect.x + m_rect.w/2 - (w/2);
-		y = m_rect.y + m_rect.h/2 - (h/2);
+		w = (Width()*m_step)/EXPAND_STEPS;
+		h = (Height()*m_step)/EXPAND_STEPS;
+		x = X() + Width()/2 - (w/2);
+		y = Y() + Height()/2 - (h/2);
 	} else {
-		w = m_rect.w;
-		h = m_rect.h;
-		x = m_rect.x;
-		y = m_rect.y;
+		w = Width();
+		h = Height();
+		x = X();
+		y = Y();
 	}
 
 	/* The border is 4 pixels around the area of the dialog */
diff --git a/UIElementKeyButton.cpp b/UIElementKeyButton.cpp
index 4724135d..41fbb4ba 100644
--- a/UIElementKeyButton.cpp
+++ b/UIElementKeyButton.cpp
@@ -1,49 +1,38 @@
 
 #include "UIElementKeyButton.h"
-#include "Maelstrom_Globals.h"
+#include "screenlib/UIElementLabel.h"
 
 UIElementType UIElementKeyButton::s_elementType;
 
 UIElementKeyButton::UIElementKeyButton(UIBaseElement *parent, const char *name) :
 	UIElementButton(parent, name)
 {
-	m_text = NULL;
-	m_textShadow = NULL;
 }
 
 UIElementKeyButton::~UIElementKeyButton()
 {
-	if (m_text) {
-		fontserv->FreeText(m_text);
-	}
-	if (m_textShadow) {
-		fontserv->FreeText(m_textShadow);
-	}
 }
 
 bool
 UIElementKeyButton::Load(rapidxml::xml_node<> *node, const UITemplates *templates)
 {
-	m_rect.w = m_screen->GetImageWidth(gKeyIcon);
-	m_rect.h = m_screen->GetImageHeight(gKeyIcon);
-
 	if (!UIElementButton::Load(node, templates)) {
 		return false;
 	}
 
 	if (m_hotkey != SDLK_UNKNOWN) {
-		m_text = fontserv->TextImage(SDL_GetKeyName(m_hotkey),
-				fonts[GENEVA_9], STYLE_BOLD, 0x00, 0x00, 0x00);
-		m_textShadow = fontserv->TextImage(SDL_GetKeyName(m_hotkey),
-				fonts[GENEVA_9], STYLE_BOLD, 0xFF, 0xFF, 0xFF);
+		UIElementLabel *label;
+		const char *text;
+
+		text = SDL_GetKeyName(m_hotkey);
+		label = GetElement<UIElementLabel>("label");
+		if (label) {
+			label->SetText(text);
+		}
+		label = GetElement<UIElementLabel>("shadow");
+		if (label) {
+			label->SetText(text);
+		}
 	}
 	return true;
 }
-
-void
-UIElementKeyButton::Draw()
-{
-	m_screen->QueueBlit(m_rect.x, m_rect.y, gKeyIcon, NOCLIP);
-	m_screen->QueueBlit(m_rect.x+14, m_rect.y+10, m_textShadow, NOCLIP);
-	m_screen->QueueBlit(m_rect.x+13, m_rect.y+9, m_text, NOCLIP);
-}
diff --git a/UIElementKeyButton.h b/UIElementKeyButton.h
index 3b867362..2fb41018 100644
--- a/UIElementKeyButton.h
+++ b/UIElementKeyButton.h
@@ -16,12 +16,6 @@ class UIElementKeyButton : public UIElementButton
 
 	virtual bool Load(rapidxml::xml_node<> *node, const UITemplates *templates);
 
-	virtual void Draw();
-
-protected:
-	SDL_Texture *m_text;
-	SDL_Texture *m_textShadow;
-
 protected:
 	static UIElementType s_elementType;
 
diff --git a/screenlib/UIArea.cpp b/screenlib/UIArea.cpp
index 5fe947cc..16d9118c 100644
--- a/screenlib/UIArea.cpp
+++ b/screenlib/UIArea.cpp
@@ -50,19 +50,22 @@ static AnchorLocation ParseAnchorLocation(const char *text)
 
 }
 
-UIArea::UIArea(FrameBuf *screen) : ErrorBase()
+UIArea::UIArea(FrameBuf *screen, UIArea *anchor, int w, int h) : ErrorBase()
 {
 	m_screen = screen;
-	m_anchor.element = NULL;
-	m_anchor.anchorFrom = TOPLEFT;
-	m_anchor.anchorTo = TOPLEFT;
-	m_anchor.offsetX = 0;
-	m_anchor.offsetY = 0;
+	m_shown = true;
 	m_rect.x = 0;
 	m_rect.y = 0;
-	m_rect.w = 0;
-	m_rect.h = 0;
-	m_shown = true;
+	m_rect.w = w;
+	m_rect.h = h;
+	m_anchor.element = anchor;
+	m_anchor.anchorFrom = CENTER;
+	m_anchor.anchorTo = CENTER;
+	m_anchor.offsetX = 0;
+	m_anchor.offsetY = 0;
+	if (anchor) {
+		anchor->AddAnchoredArea(this);
+	}
 }
 
 bool
@@ -70,6 +73,7 @@ UIArea::Load(rapidxml::xml_node<> *node)
 {
 	rapidxml::xml_node<> *child;
 	rapidxml::xml_attribute<> *attr;
+	SDL_Rect rect = m_rect;
 
 	attr = node->first_attribute("show", 0, false);
 	if (attr) {
@@ -93,22 +97,18 @@ UIArea::Load(rapidxml::xml_node<> *node)
 		}
 	}
 
-	child = node->first_node("sizeParent", 0, false);
-	if (child) {
-		UIArea *parent;
-
-		parent = GetAnchorElement(NULL);
-		m_rect.w = parent->Width();
-		m_rect.h = parent->Height();
-	}
-
 	child = node->first_node("anchor", 0, false);
 	if (child) {
 		int x, y;
 
 		attr = child->first_attribute("anchor", 0, false);
+		if (m_anchor.element) {
+			m_anchor.element->DelAnchoredArea(this);
+		}
 		m_anchor.element = GetAnchorElement(attr ? attr->value() : NULL);
-		if (!m_anchor.element) {
+		if (m_anchor.element) {
+			m_anchor.element->AddAnchoredArea(this);
+		} else {
 			SetError("Element 'anchor' couldn't find anchor element %s",
 				attr ? attr->value() : "NULL");
 			return false;
@@ -131,13 +131,60 @@ UIArea::Load(rapidxml::xml_node<> *node)
 		if (attr) {
 			m_anchor.offsetY = SDL_atoi(attr->value());
 		}
+	}
 
-		CalculateAnchor();
+	CalculateAnchor(false);
+	if (m_rect.x != rect.x || m_rect.y != rect.y ||
+	    m_rect.w != rect.w || m_rect.h != rect.h) {
+		OnRectChanged();
 	}
 
 	return true;
 }
 
+void
+UIArea::SetPosition(int x, int y) {
+	/* Setting the position breaks the anchoring */
+	m_anchor.element = NULL;
+
+	if (x != m_rect.x || y != m_rect.y) {
+		m_rect.x = x;
+		m_rect.y = y;
+		OnRectChanged();
+	}
+}
+
+void
+UIArea::SetSize(int w, int h)
+{
+	if (w != m_rect.w || h != m_rect.h) {
+		m_rect.w = w;
+		m_rect.h = h;
+		CalculateAnchor(false);
+		OnRectChanged();
+	}
+}
+
+void
+UIArea::SetWidth(int w)
+{
+	if (w != m_rect.w) {
+		m_rect.w = w;
+		CalculateAnchor(false);
+		OnRectChanged();
+	}
+}
+
+void
+UIArea::SetHeight(int h)
+{
+	if (h != m_rect.h) {
+		m_rect.h = h;
+		CalculateAnchor(false);
+		OnRectChanged();
+	}
+}
+
 void
 UIArea::GetAnchorLocation(AnchorLocation spot, int *x, int *y) const
 {
@@ -170,7 +217,7 @@ UIArea::GetAnchorLocation(AnchorLocation spot, int *x, int *y) const
 }
 
 void
-UIArea::CalculateAnchor()
+UIArea::CalculateAnchor(bool triggerRectChanged)
 {
 	int x, y;
 
@@ -200,6 +247,22 @@ UIArea::CalculateAnchor()
 			break;
 	}
 
-	m_rect.x = x + m_anchor.offsetX;
-	m_rect.y = y + m_anchor.offsetY;
+	x += m_anchor.offsetX;
+	y += m_anchor.offsetY;
+	if (x != m_rect.x || y != m_rect.y) {
+		m_rect.x = x;
+		m_rect.y = y;
+
+		if (triggerRectChanged) {
+			OnRectChanged();
+		}
+	}
+}
+
+void
+UIArea::OnRectChanged()
+{
+	for (unsigned i = 0; i < m_anchoredAreas.length(); ++i) {
+		m_anchoredAreas[i]->CalculateAnchor(true);
+	}
 }
diff --git a/screenlib/UIArea.h b/screenlib/UIArea.h
index 45ca0033..c0c8ec14 100644
--- a/screenlib/UIArea.h
+++ b/screenlib/UIArea.h
@@ -24,6 +24,7 @@
 #define _UIArea_h
 
 #include "SDL_rect.h"
+#include "../utils/array.h"
 #include "../utils/rapidxml.h"
 #include "ErrorBase.h"
 
@@ -54,7 +55,7 @@ class FrameBuf;
 class UIArea : public ErrorBase
 {
 public:
-	UIArea(FrameBuf *screen);
+	UIArea(FrameBuf *screen, UIArea *anchor = NULL, int w = 0, int h = 0);
 
 	bool Load(rapidxml::xml_node<> *node);
 
@@ -63,23 +64,11 @@ class UIArea : public ErrorBase
 		return NULL;
 	}
 
-	void SetPosition(int x, int y) {
-		m_rect.x = x;
-		m_rect.y = y;
-	}
-	void SetSize(int w, int h) {
-		m_rect.w = w;
-		m_rect.h = h;
-		CalculateAnchor();
-	}
-	void SetWidth(int w) {
-		m_rect.w = w;
-		CalculateAnchor();
-	}
-	void SetHeight(int h) {
-		m_rect.h = h;
-		CalculateAnchor();
-	}
+	void SetPosition(int x, int y);
+	void SetSize(int w, int h);
+	void SetWidth(int w);
+	void SetHeight(int h);
+
 	bool ContainsPoint(int x, int y) const {
 		return (x >= m_rect.x && x < m_rect.x+m_rect.w &&
 		        y >= m_rect.y && y < m_rect.y+m_rect.h);
@@ -88,8 +77,8 @@ class UIArea : public ErrorBase
 	FrameBuf *GetScreen() const {
 		return m_screen;
 	}
-	const SDL_Rect *GetRect() const {
-		return &m_rect;
+	const SDL_Rect &GetRect() const {
+		return m_rect;
 	}
 	int X() const {
 		return m_rect.x;
@@ -103,7 +92,6 @@ class UIArea : public ErrorBase
 	int Height() const {
 		return m_rect.h;
 	}
-	void GetAnchorLocation(AnchorLocation spot, int *x, int *y) const;
 
 	virtual void Show() {
 		m_shown = true;
@@ -115,19 +103,34 @@ class UIArea : public ErrorBase
 		return m_shown;
 	}
 
+	void AddAnchoredArea(UIArea *area) {
+		m_anchoredAreas.add(area);
+	}
+	void DelAnchoredArea(UIArea *area) {
+		m_anchoredAreas.remove(area);
+	}
+
 protected:
-	void CalculateAnchor();
+	void GetAnchorLocation(AnchorLocation spot, int *x, int *y) const;
+	void CalculateAnchor(bool triggerRectChanged = true);
+	virtual void OnRectChanged();
 
 protected:
 	FrameBuf *m_screen;
+	bool m_shown;
+
+private:
+	/* This is private so updates can trigger OnRectChanged() */
+	SDL_Rect m_rect;
+
 	struct {
 		UIArea *element;
 		AnchorLocation anchorFrom;
 		AnchorLocation anchorTo;
 		int offsetX, offsetY;
 	} m_anchor;
-	SDL_Rect m_rect;
-	bool m_shown;
+
+	array<UIArea *> m_anchoredAreas;
 };
 
 #endif // _UIArea_h
diff --git a/screenlib/UIBaseElement.cpp b/screenlib/UIBaseElement.cpp
index 363bea0f..40903740 100644
--- a/screenlib/UIBaseElement.cpp
+++ b/screenlib/UIBaseElement.cpp
@@ -32,7 +32,7 @@ UIElementType UIBaseElement::s_elementType;
 
 
 UIBaseElement::UIBaseElement(UIManager *ui, const char *name) :
-	UIArea(ui->GetScreen())
+	UIArea(ui->GetScreen(), ui, ui->Width(), ui->Height())
 {
 	m_ui = ui;
 	m_parent = NULL;
@@ -40,7 +40,7 @@ UIBaseElement::UIBaseElement(UIManager *ui, const char *name) :
 }
 
 UIBaseElement::UIBaseElement(UIBaseElement *parent, const char *name) :
-	UIArea(parent->GetScreen())
+	UIArea(parent->GetScreen(), parent, parent->Width(), parent->Height())
 {
 	m_ui = parent->GetUI();
 	m_parent = parent;
diff --git a/screenlib/UIElementLabel.cpp b/screenlib/UIElementLabel.cpp
index 572943fb..48dd5da9 100644
--- a/screenlib/UIElementLabel.cpp
+++ b/screenlib/UIElementLabel.cpp
@@ -106,9 +106,8 @@ UIElementLabel::SetText(const char *text)
 	m_text = SDL_strdup(text);
 	m_texture = GetUI()->CreateText(m_text, m_fontName, m_fontSize, m_fontStyle, m_color);
 
-	m_rect.w = m_screen->GetImageWidth(m_texture);
-	m_rect.h = m_screen->GetImageHeight(m_texture);
-	CalculateAnchor();
+	SetSize(m_screen->GetImageWidth(m_texture), 
+		m_screen->GetImageHeight(m_texture));
 }
 
 void
@@ -134,6 +133,6 @@ void
 UIElementLabel::Draw()
 {
 	if (m_texture) {
-		m_screen->QueueBlit(m_rect.x, m_rect.y, m_texture, NOCLIP);
+		m_screen->QueueBlit(X(), Y(), m_texture, NOCLIP);
 	}
 }
diff --git a/screenlib/UIElementLine.cpp b/screenlib/UIElementLine.cpp
index 94ef032b..0e1288d5 100644
--- a/screenlib/UIElementLine.cpp
+++ b/screenlib/UIElementLine.cpp
@@ -31,5 +31,5 @@ UIElementLine::Load(rapidxml::xml_node<> *node, const UITemplates *templates)
 void
 UIElementLine::Draw()
 {
-	m_screen->DrawLine(m_rect.x, m_rect.y, m_rect.x+m_rect.w-1, m_rect.y+m_rect.h-1, m_color);
+	m_screen->DrawLine(X(), Y(), X()+Width()-1, Y()+Height()-1, m_color);
 }
diff --git a/screenlib/UIElementRect.cpp b/screenlib/UIElementRect.cpp
index 5ae4b5b0..2ca0636d 100644
--- a/screenlib/UIElementRect.cpp
+++ b/screenlib/UIElementRect.cpp
@@ -43,8 +43,8 @@ void
 UIElementRect::Draw()
 {
 	if (m_fill) {
-		m_screen->FillRect(m_rect.x, m_rect.y, m_rect.w, m_rect.h, m_color);
+		m_screen->FillRect(X(), Y(), Width(), Height(), m_color);
 	} else {
-		m_screen->DrawRect(m_rect.x, m_rect.y, m_rect.w, m_rect.h, m_color);
+		m_screen->DrawRect(X(), Y(), Width(), Height(), m_color);
 	}
 }
diff --git a/screenlib/UIElementTexture.cpp b/screenlib/UIElementTexture.cpp
index ccc944b8..3c56d1d6 100644
--- a/screenlib/UIElementTexture.cpp
+++ b/screenlib/UIElementTexture.cpp
@@ -25,15 +25,14 @@ UIElementTexture::SetTexture(SDL_Texture *texture)
 		m_screen->FreeImage(m_texture);
 	}
 	m_texture = texture;
-	m_rect.w = m_screen->GetImageWidth(texture);
-	m_rect.h = m_screen->GetImageHeight(texture);
-	CalculateAnchor();
+	SetSize(m_screen->GetImageWidth(texture),
+		m_screen->GetImageHeight(texture));
 }
 
 void
 UIElementTexture::Draw()
 {
 	if (m_texture) {
-		m_screen->QueueBlit(m_rect.x, m_rect.y, m_texture, NOCLIP);
+		m_screen->QueueBlit(X(), Y(), m_texture, NOCLIP);
 	}
 }
diff --git a/screenlib/UIManager.cpp b/screenlib/UIManager.cpp
index 6693825a..d4bdd92b 100644
--- a/screenlib/UIManager.cpp
+++ b/screenlib/UIManager.cpp
@@ -30,7 +30,8 @@
 #include "UIElementRect.h"
 
 
-UIManager::UIManager(FrameBuf *screen) : UIArea(screen)
+UIManager::UIManager(FrameBuf *screen) :
+	UIArea(screen, NULL, screen->Width(), screen->Height())
 {
 	m_loadPath = new char[2];
 	strcpy(m_loadPath, ".");
diff --git a/screenlib/UIPanel.cpp b/screenlib/UIPanel.cpp
index 48bd01b9..800d000a 100644
--- a/screenlib/UIPanel.cpp
+++ b/screenlib/UIPanel.cpp
@@ -30,8 +30,6 @@
 UIPanel::UIPanel(UIManager *ui, const char *name) :
 	UIBaseElement(ui, name)
 {
-	m_rect.w = m_screen->Width();
-	m_rect.h = m_screen->Height();
 	m_shown = false;
 	m_fullscreen = true;
 	m_cursorVisible = true;