Maelstrom: Added stackable containers for easy UI layout

https://github.com/libsdl-org/Maelstrom/commit/984eb09383c00eac76d6cab3033954559aa097aa

From 984eb09383c00eac76d6cab3033954559aa097aa Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 23 Nov 2011 14:22:40 -0500
Subject: [PATCH] Added stackable containers for easy UI layout

---
 game/MaelstromUI.cpp      |   3 +
 screenlib/Makefile.am     |   2 +
 screenlib/Makefile.in     |  15 ++--
 screenlib/UIBaseElement.h |  17 +++++
 screenlib/UIContainer.cpp | 141 ++++++++++++++++++++++++++++++++++++++
 screenlib/UIContainer.h   |  66 ++++++++++++++++++
 6 files changed, 238 insertions(+), 6 deletions(-)
 create mode 100644 screenlib/UIContainer.cpp
 create mode 100644 screenlib/UIContainer.h

diff --git a/game/MaelstromUI.cpp b/game/MaelstromUI.cpp
index daae29f8..050a34ad 100644
--- a/game/MaelstromUI.cpp
+++ b/game/MaelstromUI.cpp
@@ -30,6 +30,7 @@
 #include "player.h"
 #include "lobby.h"
 #include "MacDialog.h"
+#include "../screenlib/UIContainer.h"
 #include "../screenlib/UIElementButton.h"
 #include "../screenlib/UIElementCheckbox.h"
 #include "../screenlib/UIElementEditbox.h"
@@ -199,6 +200,8 @@ MaelstromUI::CreateElement(UIBaseElement *parent, const char *type, const char *
 		element = new UIElement(parent, name, new UIDrawEngine());
 	} else if (strcasecmp(type, "Image") == 0) {
 		element = new UIElement(parent, name, new UIDrawEngine());
+	} else if (strcasecmp(type, "Container") == 0) {
+		element = new UIContainer(parent, name, new UIDrawEngine());
 	} else if (strcasecmp(type, "Button") == 0) {
 		element = new UIElementButton(parent, name, new UIDrawEngine());
 	} else if (strcasecmp(type, "Icon") == 0) {
diff --git a/screenlib/Makefile.am b/screenlib/Makefile.am
index 813791ce..2e3bee5c 100644
--- a/screenlib/Makefile.am
+++ b/screenlib/Makefile.am
@@ -9,6 +9,8 @@ libSDLscreen_a_SOURCES =	\
 	UIArea.h		\
 	UIBaseElement.cpp	\
 	UIBaseElement.h		\
+	UIContainer.cpp		\
+	UIContainer.h		\
 	UIDialog.cpp		\
 	UIDialog.h		\
 	UIDialogButton.cpp	\
diff --git a/screenlib/Makefile.in b/screenlib/Makefile.in
index b5d00d72..d791de47 100644
--- a/screenlib/Makefile.in
+++ b/screenlib/Makefile.in
@@ -50,12 +50,12 @@ ARFLAGS = cru
 libSDLscreen_a_AR = $(AR) $(ARFLAGS)
 libSDLscreen_a_LIBADD =
 am_libSDLscreen_a_OBJECTS = SDL_FrameBuf.$(OBJEXT) UIArea.$(OBJEXT) \
-	UIBaseElement.$(OBJEXT) UIDialog.$(OBJEXT) \
-	UIDialogButton.$(OBJEXT) UIDrawEngine.$(OBJEXT) \
-	UIElement.$(OBJEXT) UIElementButton.$(OBJEXT) \
-	UIElementCheckbox.$(OBJEXT) UIElementEditbox.$(OBJEXT) \
-	UIElementRadio.$(OBJEXT) UIManager.$(OBJEXT) UIPanel.$(OBJEXT) \
-	UITemplates.$(OBJEXT)
+	UIBaseElement.$(OBJEXT) UIContainer.$(OBJEXT) \
+	UIDialog.$(OBJEXT) UIDialogButton.$(OBJEXT) \
+	UIDrawEngine.$(OBJEXT) UIElement.$(OBJEXT) \
+	UIElementButton.$(OBJEXT) UIElementCheckbox.$(OBJEXT) \
+	UIElementEditbox.$(OBJEXT) UIElementRadio.$(OBJEXT) \
+	UIManager.$(OBJEXT) UIPanel.$(OBJEXT) UITemplates.$(OBJEXT)
 libSDLscreen_a_OBJECTS = $(am_libSDLscreen_a_OBJECTS)
 DEFAULT_INCLUDES = -I.@am__isrc@
 depcomp = $(SHELL) $(top_srcdir)/build-scripts/depcomp
@@ -192,6 +192,8 @@ libSDLscreen_a_SOURCES = \
 	UIArea.h		\
 	UIBaseElement.cpp	\
 	UIBaseElement.h		\
+	UIContainer.cpp		\
+	UIContainer.h		\
 	UIDialog.cpp		\
 	UIDialog.h		\
 	UIDialogButton.cpp	\
@@ -267,6 +269,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/SDL_FrameBuf.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/UIArea.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/UIBaseElement.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/UIContainer.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/UIDialog.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/UIDialogButton.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/UIDrawEngine.Po@am__quote@
diff --git a/screenlib/UIBaseElement.h b/screenlib/UIBaseElement.h
index 9f7ccf5c..2be78f7b 100644
--- a/screenlib/UIBaseElement.h
+++ b/screenlib/UIBaseElement.h
@@ -155,13 +155,26 @@ class UIBaseElement : public UIArea
 
 	virtual void Show() {
 		m_shown = true;
+		if (m_parent) {
+			m_parent->OnChildShown(this);
+		}
 	}
 	virtual void Hide() {
 		m_shown = false;
+		if (m_parent) {
+			m_parent->OnChildHidden(this);
+		}
 	}
 	bool IsShown() const {
 		return m_shown;
 	}
+	virtual void OnRectChanged() {
+		UIArea::OnRectChanged();
+
+		if (m_parent) {
+			m_parent->OnChildRectChanged(this);
+		}
+	}
 
 	void SetDisabled(bool disabled);
 	void SetParentDisabled(bool disabled);
@@ -172,6 +185,10 @@ class UIBaseElement : public UIArea
 	virtual void Draw();
 	virtual bool HandleEvent(const SDL_Event &event);
 
+	virtual void OnChildShown(UIBaseElement *child) { }
+	virtual void OnChildHidden(UIBaseElement *child) { }
+	virtual void OnChildRectChanged(UIBaseElement *child) { }
+
 protected:
 	FrameBuf *m_screen;
 	UIManager *m_ui;
diff --git a/screenlib/UIContainer.cpp b/screenlib/UIContainer.cpp
new file mode 100644
index 00000000..04ff16fa
--- /dev/null
+++ b/screenlib/UIContainer.cpp
@@ -0,0 +1,141 @@
+/*
+  screenlib:  A simple window and UI library based on the SDL library
+  Copyright (C) 1997-2011 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+#include "UIContainer.h"
+
+UIElementType UIContainer::s_elementType;
+
+
+UIContainer::UIContainer(UIBaseElement *parent, const char *name, UIDrawEngine *drawEngine) :
+	UIElement(parent, name, drawEngine)
+{
+	m_layoutType = LAYOUT_VERTICAL;
+	m_spacing = 0;
+	m_layoutInProgress = false;
+}
+
+
+bool
+UIContainer::Load(rapidxml::xml_node<> *node, const UITemplates *templates)
+{
+	rapidxml::xml_attribute<> *attr;
+
+	if (!UIElement::Load(node, templates)) {
+		return false;
+	}
+
+	attr = node->first_attribute("layout", 0, false);
+	if (attr) {
+		if (!ParseLayoutType(attr->value())) {
+			fprintf(stderr, "Warning: Unknown layout type '%s'\n", attr->value());
+			return false;
+		}
+	}
+
+	LoadNumber(node, "spacing", m_spacing);
+
+	return true;
+}
+
+void
+UIContainer::LayoutChildren()
+{
+	UIBaseElement *anchor;
+	AnchorLocation anchorLocation;
+	int offsetX = 0;
+	int offsetY = 0;
+	int i;
+
+	if (m_layoutInProgress) {
+		return;
+	}
+	m_layoutInProgress = true;
+
+	// Anchor the first visible element
+	anchor = NULL;
+	for (i = 0; i < m_elements.length(); ++i) {
+		if (m_elements[i]->IsShown()) {
+			m_elements[i]->SetAnchor(TOPLEFT, TOPLEFT, this);
+			anchor = m_elements[i];
+			break;
+		}
+	}
+
+	// Anchor the rest of the elements
+	if (m_layoutType == LAYOUT_HORIZONTAL) {
+		anchorLocation = TOPRIGHT;
+		offsetX = m_spacing;
+	} else {
+		anchorLocation = BOTTOMLEFT;
+		offsetY = m_spacing;
+	}
+	for (++i; i < m_elements.length(); ++i) {
+		if (m_elements[i]->IsShown()) {
+			m_elements[i]->SetAnchor(TOPLEFT, anchorLocation, anchor, offsetX, offsetY);
+			anchor = m_elements[i];
+		}
+	}
+
+	// Adjust our width and height to match
+	int w = 0, h = 0;
+	if (m_layoutType == LAYOUT_HORIZONTAL) {
+		// Width is the sum of children, height is the max of children
+		for (i = 0; i < m_elements.length(); ++i) {
+			if (m_elements[i]->IsShown()) {
+				if (m_elements[i]->Height() > h) {
+					h = m_elements[i]->Height();
+				}
+			}
+		}
+		if (anchor) {
+			w = (anchor->X() - X()) + anchor->Width();
+		}
+	} else {
+		// Width is the max of children, height is the sum of children
+		for (i = 0; i < m_elements.length(); ++i) {
+			if (m_elements[i]->IsShown()) {
+				if (m_elements[i]->Width() > w) {
+					w = m_elements[i]->Width();
+				}
+			}
+		}
+		if (anchor) {
+			h = (anchor->Y() - Y()) + anchor->Height();
+		}
+	}
+	SetSize(w, h, true);
+
+	m_layoutInProgress = false;
+}
+
+bool
+UIContainer::ParseLayoutType(const char *text)
+{
+	if (SDL_strcasecmp(text, "HORIZONTAL") == 0) {
+		m_layoutType = LAYOUT_HORIZONTAL;
+		return true;
+	}
+	if (SDL_strcasecmp(text, "VERTICAL") == 0) {
+		m_layoutType = LAYOUT_VERTICAL;
+		return true;
+	}
+	return false;
+}
diff --git a/screenlib/UIContainer.h b/screenlib/UIContainer.h
new file mode 100644
index 00000000..cbaf0907
--- /dev/null
+++ b/screenlib/UIContainer.h
@@ -0,0 +1,66 @@
+/*
+  screenlib:  A simple window and UI library based on the SDL library
+  Copyright (C) 1997-2011 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+
+#ifndef _UIContainer_h
+#define _UIContainer_h
+
+#include "UIElement.h"
+
+
+enum LAYOUT_TYPE {
+	LAYOUT_HORIZONTAL,
+	LAYOUT_VERTICAL,
+};
+
+class UIContainer : public UIElement
+{
+DECLARE_TYPESAFE_CLASS(UIElement)
+public:
+	UIContainer(UIBaseElement *parent, const char *name, UIDrawEngine *drawEngine);
+
+	override bool Load(rapidxml::xml_node<> *node, const UITemplates *templates);
+	override bool FinishLoading() {
+		LayoutChildren();
+		return true;
+	}
+
+	override void OnChildShown(UIBaseElement *child) {
+		LayoutChildren();
+	}
+	override void OnChildHidden(UIBaseElement *child) {
+		LayoutChildren();
+	}
+	override void OnChildRectChanged(UIBaseElement *child) {
+		LayoutChildren();
+	}
+
+protected:
+	LAYOUT_TYPE m_layoutType;
+	int m_spacing;
+	bool m_layoutInProgress;
+
+protected:
+	bool ParseLayoutType(const char *text);
+
+	virtual void LayoutChildren();
+};
+
+#endif // _UIContainer_h