Maelstrom: Added a simple editbox to the UI system

https://github.com/libsdl-org/Maelstrom/commit/36b77cc558834450f067009e793680bfb6ae3f55

From 36b77cc558834450f067009e793680bfb6ae3f55 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 30 Oct 2011 09:25:10 -0400
Subject: [PATCH] Added a simple editbox to the UI system Converted the cheat
 dialog over to the new UI system.

---
 MacDialogButton.cpp            |  14 ++-
 MacDialogEditbox.cpp           |  69 +++++++++++
 MacDialogEditbox.h             |  25 ++++
 MaelstromUI.cpp                |   7 +-
 Maelstrom_Globals.h            |   1 +
 Makefile.am                    |   2 +
 UI/cheat.xml                   |  41 +++++++
 UI/zap.xml                     |   4 +-
 main.cpp                       |  44 ++++++-
 scores.cpp                     |  96 ---------------
 scores.h                       |   1 -
 screenlib/Makefile.am          |   2 +
 screenlib/UIBaseElement.h      |  40 +++++++
 screenlib/UIElementButton.h    |   4 +-
 screenlib/UIElementCheckbox.h  |   5 +-
 screenlib/UIElementEditbox.cpp | 211 +++++++++++++++++++++++++++++++++
 screenlib/UIElementEditbox.h   |  84 +++++++++++++
 screenlib/UIElementLabel.cpp   |  11 +-
 screenlib/UIElementRadio.cpp   |  12 +-
 19 files changed, 549 insertions(+), 124 deletions(-)
 create mode 100644 MacDialogEditbox.cpp
 create mode 100644 MacDialogEditbox.h
 create mode 100644 UI/cheat.xml
 create mode 100644 screenlib/UIElementEditbox.cpp
 create mode 100644 screenlib/UIElementEditbox.h

diff --git a/MacDialogButton.cpp b/MacDialogButton.cpp
index cdb5f5eb..074303e8 100644
--- a/MacDialogButton.cpp
+++ b/MacDialogButton.cpp
@@ -23,7 +23,11 @@ MacDialogButton::MacDialogButton(UIBaseElement *parent, const char *name) :
 UIElementLabel *
 MacDialogButton::CreateLabel()
 {
-	return new MacDialogLabel(this, "label");
+	MacDialogLabel *label;
+
+	label = new MacDialogLabel(this, "label");
+	label->SetTextColor(m_colors[1]);
+	return label;
 }
 
 void
@@ -123,12 +127,12 @@ MacDialogButton::OnMouseUp()
 void
 MacDialogButton::SetElementColor(Uint32 color)
 {
+	array<UIElementLabel*> labels;
 	Uint8 R, G, B;
 
 	m_screen->GetRGB(color, &R, &G, &B);
-	for (unsigned i = 0; i < m_elements.length(); ++i) {
-		if (m_elements[i]->IsA(UIElementLabel::GetType())) {
-			static_cast<UIElementLabel*>(m_elements[i])->SetTextColor(R, G, B);
-		}
+	FindElements<UIElementLabel>(labels);
+	for (unsigned i = 0; i < labels.length(); ++i) {
+		labels[i]->SetTextColor(R, G, B);
 	}
 }
diff --git a/MacDialogEditbox.cpp b/MacDialogEditbox.cpp
new file mode 100644
index 00000000..1cfde099
--- /dev/null
+++ b/MacDialogEditbox.cpp
@@ -0,0 +1,69 @@
+
+#include "screenlib/SDL_FrameBuf.h"
+#include "MacDialogEditbox.h"
+#include "MacDialogLabel.h"
+
+UIElementType MacDialogEditbox::s_elementType;
+
+
+MacDialogEditbox::MacDialogEditbox(UIBaseElement *parent, const char *name) :
+	UIElementEditbox(parent, name)
+{
+	m_colors[0] = m_screen->MapRGB(0xFF, 0xFF, 0xFF);
+	m_colors[1] = m_screen->MapRGB(0x00, 0x00, 0x00);
+}
+
+UIElementLabel *
+MacDialogEditbox::CreateLabel()
+{
+	MacDialogLabel *label;
+
+	label = new MacDialogLabel(this, "label");
+	label->SetTextColor(m_colors[1]);
+	label->SetAnchor(LEFT, LEFT, this, 3, 0);
+	return label;
+}
+
+void
+MacDialogEditbox::OnHighlightChanged()
+{
+	SetElementColor(m_colors[!m_highlight]);
+}
+
+void
+MacDialogEditbox::Draw()
+{
+	Uint32 bg, fg;
+
+	/* The colors are inverted when the editbox is highlighted */
+	bg = m_colors[m_highlight];
+	fg = m_colors[!m_highlight];
+
+	// Draw the outline, always in the real foreground color
+	m_screen->DrawRect(X(), Y(), Width(), Height(), m_colors[1]);
+
+	if (m_highlight) {
+		// Draw the highlight
+		m_screen->FillRect(X()+3, Y()+3, Width()-6, Height()-6, bg);
+	} else if (m_focus) {
+		// Draw the cursor
+		int x = m_label->X() + m_label->Width();
+
+		m_screen->DrawLine(x, Y()+3, x, Y()+3+Height()-6-1, fg);
+	}
+
+	UIElementEditbox::Draw();
+}
+
+void
+MacDialogEditbox::SetElementColor(Uint32 color)
+{
+	array<UIElementLabel*> labels;
+	Uint8 R, G, B;
+
+	m_screen->GetRGB(color, &R, &G, &B);
+	FindElements<UIElementLabel>(labels);
+	for (unsigned i = 0; i < labels.length(); ++i) {
+		labels[i]->SetTextColor(R, G, B);
+	}
+}
diff --git a/MacDialogEditbox.h b/MacDialogEditbox.h
new file mode 100644
index 00000000..e1a9c185
--- /dev/null
+++ b/MacDialogEditbox.h
@@ -0,0 +1,25 @@
+#ifndef _MacDialogEditbox_h
+#define _MacDialogEditbox_h
+
+#include "screenlib/UIElementEditbox.h"
+
+
+class MacDialogEditbox : public UIElementEditbox
+{
+DECLARE_TYPESAFE_CLASS(UIElementEditbox)
+public:
+	MacDialogEditbox(UIBaseElement *parent, const char *name = "");
+
+	override void Draw();
+
+protected:
+	override UIElementLabel *CreateLabel();
+	override void OnHighlightChanged();
+
+	void SetElementColor(Uint32 color);
+
+protected:
+	Uint32 m_colors[2];
+};
+
+#endif // _MacDialogEditbox_h
diff --git a/MaelstromUI.cpp b/MaelstromUI.cpp
index 880a1d8d..f6ec15e8 100644
--- a/MaelstromUI.cpp
+++ b/MaelstromUI.cpp
@@ -8,6 +8,7 @@
 #include "MacDialog.h"
 #include "MacDialogButton.h"
 #include "MacDialogCheckbox.h"
+#include "MacDialogEditbox.h"
 #include "MacDialogLabel.h"
 #include "MacDialogRadioButton.h"
 #include "UIElementIcon.h"
@@ -15,7 +16,7 @@
 #include "UIElementSprite.h"
 #include "UIElementTitle.h"
 #include "screenlib/UIElementButton.h"
-#include "screenlib/UIElementCheckbox.h"
+#include "screenlib/UIElementLabel.h"
 #include "screenlib/UIElementLine.h"
 #include "screenlib/UIElementRadio.h"
 #include "screenlib/UIElementRect.h"
@@ -187,6 +188,8 @@ MaelstromUI::CreateElement(UIBaseElement *parent, const char *type, const char *
 		return new UIElementRadioGroup(parent, name);
 	} else if (strcasecmp(type, "DialogRadioButton") == 0) {
 		return new MacDialogRadioButton(parent, name);
+	} else if (strcasecmp(type, "DialogEditbox") == 0) {
+		return new MacDialogEditbox(parent, name);
 	} else if (strcasecmp(type, "KeyButton") == 0) {
 		return new UIElementKeyButton(parent, name);
 	} else if (strcasecmp(type, "Icon") == 0) {
@@ -196,5 +199,5 @@ MaelstromUI::CreateElement(UIBaseElement *parent, const char *type, const char *
 	} else if (strcasecmp(type, "Title") == 0) {
 		return new UIElementTitle(parent, name);
 	}
-	return UIManager::CreateElement(parent, name, type);;
+	return UIManager::CreateElement(parent, name, type);
 }
diff --git a/Maelstrom_Globals.h b/Maelstrom_Globals.h
index 92c50ab5..b71ca8ab 100644
--- a/Maelstrom_Globals.h
+++ b/Maelstrom_Globals.h
@@ -111,6 +111,7 @@ extern Bool	gNetScores;
 #define DIALOG_CONTROLS	"controls"
 #define DIALOG_ZAP	"zap"
 #define DIALOG_DAWN	"dawn"
+#define DIALOG_CHEAT	"cheat"
 
 // Sound resource definitions...
 #define gShotSound	100
diff --git a/Makefile.am b/Makefile.am
index c8b09a02..b04b199b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -38,6 +38,8 @@ Maelstrom_SOURCES =		\
 	MacDialogButton.h	\
 	MacDialogCheckbox.cpp	\
 	MacDialogCheckbox.h	\
+	MacDialogEditbox.cpp	\
+	MacDialogEditbox.h	\
 	MacDialogLabel.cpp	\
 	MacDialogLabel.h	\
 	MacDialogRadioButton.cpp\
diff --git a/UI/cheat.xml b/UI/cheat.xml
new file mode 100644
index 00000000..970e9fd7
--- /dev/null
+++ b/UI/cheat.xml
@@ -0,0 +1,41 @@
+<Dialog>
+	<Size w="338" h="128"/>
+	<Elements>
+		<Icon name="image" id="103">
+			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT" x="14" y="14"/>
+		</Icon>
+		<DialogLabel name="line1" text="Enter the level to start from (1-40).  This">
+			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPRIGHT" anchor="image" x="14"/>
+		</DialogLabel>
+		<DialogLabel name="line2" text="disqualifies you from a high score...">
+			<Anchor anchorFrom="TOPLEFT" anchorTo="BOTTOMLEFT" anchor="line1" y="2"/>
+		</DialogLabel>
+
+		<DialogEditbox name="level" numeric="true" maxlen="2" text="10">
+			<Size w="30" h="21"/>
+			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT" x="78" y="60"/>
+		</DialogEditbox>
+		<DialogLabel text="Level:">
+			<Anchor anchorFrom="RIGHT" anchorTo="LEFT" anchor="level" x="-2"/>
+		</DialogLabel>
+		<DialogEditbox name="lives" numeric="true" maxlen="2" text="3">
+			<Size w="30" h="21"/>
+			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT" x="78" y="86"/>
+		</DialogEditbox>
+		<DialogLabel text="Lives:">
+			<Anchor anchorFrom="RIGHT" anchorTo="LEFT" anchor="lives" x="-2"/>
+		</DialogLabel>
+
+		<DialogCheckbox name="turbofunk" text="Turbofunk On">
+			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT" x="136" y="64"/>
+		</DialogCheckbox>
+
+		<DialogButton name="cancelButton" text="Cancel">
+			<Size w="73"/>
+			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT" x="166" y="96"/>
+		</DialogButton>
+		<DialogButton text="Do it!" default="true" id="1">
+			<Anchor anchorFrom="LEFT" anchorTo="RIGHT" anchor="cancelButton" x="14"/>
+		</DialogButton>
+	</Elements>
+</Dialog>
diff --git a/UI/zap.xml b/UI/zap.xml
index 6079c6ba..c0c87844 100644
--- a/UI/zap.xml
+++ b/UI/zap.xml
@@ -4,12 +4,12 @@
 	<Elements>
 		<Title id="102">
 			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT" x="4" y="4"/>
-		</Icon>
+		</Title>
 		<DialogButton name="clearButton" text="Clear" id="1">
 			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT" x="99" y="74"/>
 		</DialogButton>
 		<DialogButton text="Cancel" default="true">
-			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPRIGHT" anchor="clearButton"x="13"/>
+			<Anchor anchorFrom="TOPLEFT" anchorTo="TOPRIGHT" anchor="clearButton" x="13"/>
 		</DialogButton>
 	</Elements>
 </Dialog>
diff --git a/main.cpp b/main.cpp
index e29d239b..1d5d9b43 100644
--- a/main.cpp
+++ b/main.cpp
@@ -17,8 +17,10 @@
 #include "netlogic/about.h"
 #include "main.h"
 
-#include "screenlib/UIElementLabel.h"
 #include "screenlib/UIDialog.h"
+#include "screenlib/UIElementCheckbox.h"
+#include "screenlib/UIElementEditbox.h"
+#include "screenlib/UIElementLabel.h"
 #include "UIElementKeyButton.h"
 
 /* External functions used in this file */
@@ -43,8 +45,8 @@ static void RunDoAbout(void)
 }
 static void RunPlayGame(void)
 {
-	gStartLives = 3;
 	gStartLevel = 1;
+	gStartLives = 3;
 	gNoDelay = 0;
 	NewGame();
 }
@@ -90,10 +92,40 @@ static void RunToggleFullscreen(void)
 {
 	screen->ToggleFullScreen();
 }
-static void RunCheat(void)
+static void CheatDialogInit(UIDialog *dialog)
+{
+	UIElementEditbox *editbox;
+
+	editbox = dialog->GetElement<UIElementEditbox>("level");
+	if (editbox) {
+		editbox->SetFocus(true);
+	}
+}
+static void CheatDialogDone(UIDialog *dialog, int status)
 {
-	gStartLevel = GetStartLevel();
-	if ( gStartLevel > 0 ) {
+	UIElementEditbox *editbox;
+	UIElementCheckbox *checkbox;
+
+	if (status > 0) {
+		editbox = dialog->GetElement<UIElementEditbox>("level");
+		if (editbox) {
+			gStartLevel = editbox->GetNumber();
+			if (gStartLevel < 1 || gStartLevel > 40) {
+				return;
+			}
+		}
+
+		editbox = dialog->GetElement<UIElementEditbox>("lives");
+		if (editbox) {
+			gStartLives = editbox->GetNumber();
+			if (gStartLives < 1 || gStartLives > 40) {
+				gStartLives = 3;
+			}
+		}
+
+		checkbox = dialog->GetElement<UIElementCheckbox>("turbofunk");
+		gNoDelay = checkbox->IsChecked();
+
 		Delay(SOUND_DELAY);
 		sound->PlaySound(gNewLife, 5);
 		Delay(SOUND_DELAY);
@@ -382,7 +414,7 @@ MainPanelDelegate::OnLoad()
 	}
 	button = m_panel->GetElement<UIElementButton>("Cheat");
 	if (button) {
-		button->SetClickCallback(RunCheat);
+		button->SetButtonDelegate(new UIDialogLauncher(ui, DIALOG_CHEAT, CheatDialogInit, CheatDialogDone));
 	}
 	button = m_panel->GetElement<UIElementButton>("Special");
 	if (button) {
diff --git a/scores.cpp b/scores.cpp
index 5c4c6a0b..e36d39a3 100644
--- a/scores.cpp
+++ b/scores.cpp
@@ -114,99 +114,3 @@ void ZapHighScores(UIDialog *dialog, int status)
 	}
 }
 
-
-#define LVL_DIALOG_WIDTH	346
-#define LVL_DIALOG_HEIGHT	136
-
-static int     do_level;
-
-static int Level_callback(void) {
-	do_level = 1;
-	return(1);
-}
-static int Cancel2_callback(void) {
-	do_level = 0;
-	return(1);
-}
-
-int GetStartLevel(void)
-{
-	static const char    *Ltext1 = 
-			"Enter the level to start from (1-40).  This";
-	static const char    *Ltext2 = 
-			"disqualifies you from a high score...";
-	static const char    *Ltext3 = "Level:";
-	static const char    *Ltext4 = "Lives:";
-	MFont *chicago;
-	Maclike_Dialog *dialog;
-	SDL_Texture *splash;
-	SDL_Texture *text1, *text2, *text3, *text4;
-	static const char *turbotext = "Turbofunk On";
-	int x, y, X, Y;
-	Mac_Button *doit;
-	Mac_Button *cancel;
-	Mac_NumericEntry *numeric_entry;
-	Mac_CheckBox *checkbox;
-	int startlevel=10, startlives=5, turbofunk=0;
-
-	/* Set up all the components of the dialog box */
-	chicago = fonts[CHICAGO_12];
-	if ( (splash = GetCIcon(screen, 103)) == NULL ) {
-		error("Can't load alien level splash!\n");
-		return(0);
-	}
-	X=(SCREEN_WIDTH-LVL_DIALOG_WIDTH)/2;
-	Y=(SCREEN_HEIGHT-LVL_DIALOG_HEIGHT)/2;
-	dialog = new Maclike_Dialog(X, Y, LVL_DIALOG_WIDTH, LVL_DIALOG_HEIGHT,
-								screen);
-	x = y = 14;
-	dialog->Add_Image(splash, x, y);
-	x += (screen->GetImageWidth(splash)+14);
-	text1 = fontserv->TextImage(Ltext1,chicago,STYLE_NORM,0x00,0x00,0x00);
-	dialog->Add_Image(text1, x, y);
-	y += (screen->GetImageHeight(text1)+2);
-	text2 = fontserv->TextImage(Ltext2, chicago, STYLE_NORM,
-							0x00, 0x00, 0x00);
-	dialog->Add_Image(text2, x, y);
-	do_level = 0;
-	cancel = new Mac_Button(166, 96, 73, BUTTON_HEIGHT,
-				"Cancel", chicago, fontserv, Cancel2_callback);
-	dialog->Add_Dialog(cancel);
-	doit = new Mac_DefaultButton(166+73+14, 96, BUTTON_WIDTH, BUTTON_HEIGHT,
-				"Do it!", chicago, fontserv, Level_callback);
-	dialog->Add_Dialog(doit);
-	numeric_entry = new Mac_NumericEntry(X, Y, chicago, fontserv);
-	numeric_entry->Add_Entry(78, 60, 3, 1, &startlevel);
-	text3 = fontserv->TextImage(Ltext3,chicago,STYLE_NORM,0x00,0x00,0x00);
-	dialog->Add_Image(text3, 78-screen->GetImageWidth(text3)-2, 60+3);
-	numeric_entry->Add_Entry(78, 86, 3, 0, &startlives);
-	text4 = fontserv->TextImage(Ltext4,chicago,STYLE_NORM,0x00,0x00,0x00);
-	dialog->Add_Image(text4, 78-screen->GetImageWidth(text3)-2, 86+3);
-	dialog->Add_Dialog(numeric_entry);
-	checkbox = new Mac_CheckBox(&turbofunk, 136, 64, turbotext,
-						chicago, fontserv);
-	dialog->Add_Dialog(checkbox);
-
-	/* Run the dialog box */
-	dialog->Run(EXPAND_STEPS);
-
-	/* Clean up and return */
-	screen->FreeImage(splash);
-	fontserv->FreeText(text1);
-	fontserv->FreeText(text2);
-	fontserv->FreeText(text3);
-	fontserv->FreeText(text4);
-	delete dialog;
-	if ( do_level ) {
-		if ( ! startlives || (startlives > 40) )
-			startlives = 3;
-		gStartLives = startlives;
-		if ( startlevel > 40 )
-			startlevel = 0;
-		gStartLevel = startlevel;
-		gNoDelay = turbofunk;
-		return(gStartLevel);
-	}
-	return(0);
-}
-
diff --git a/scores.h b/scores.h
index ef4a9e91..04c5b19a 100644
--- a/scores.h
+++ b/scores.h
@@ -5,7 +5,6 @@ class UIDialog;
 extern void	LoadScores(void);
 extern void	SaveScores(void);
 extern void	ZapHighScores(UIDialog *dialog, int status);
-extern int	GetStartLevel(void);
 extern void	PrintHighScores(void);
 
 /* The high scores structure */
diff --git a/screenlib/Makefile.am b/screenlib/Makefile.am
index cd66cabe..1a21e5f8 100644
--- a/screenlib/Makefile.am
+++ b/screenlib/Makefile.am
@@ -19,6 +19,8 @@ libSDLscreen_a_SOURCES =	\
 	UIElementButton.h	\
 	UIElementCheckbox.cpp	\
 	UIElementCheckbox.h	\
+	UIElementEditbox.cpp	\
+	UIElementEditbox.h	\
 	UIElementLabel.cpp	\
 	UIElementLabel.h	\
 	UIElementLine.cpp	\
diff --git a/screenlib/UIBaseElement.h b/screenlib/UIBaseElement.h
index 2abfe918..045795be 100644
--- a/screenlib/UIBaseElement.h
+++ b/screenlib/UIBaseElement.h
@@ -74,6 +74,46 @@ class UIBaseElement : public UIArea
 		}
 		return NULL;
 	}
+	template <typename T>
+	T *FindElement(UIBaseElement *start = NULL) {
+		unsigned i, j;
+		if (start) {
+			// Find the starting element
+			for (i = 0; i < m_elements.length(); ++i) {
+				if (m_elements[i] == start) {
+					break;
+				}
+			}
+			if (i == m_elements.length()) {
+				return NULL;
+			}
+			// Find the next element of that type
+			j = (i+1)%m_elements.length();
+			for ( ; j != i; j = (j+1)%m_elements.length()) {
+				UIBaseElement *element = m_elements[j];
+				if (element->IsA(T::GetType())) {
+					return (T*)element;
+				}
+			}
+		} else {
+			for (i = 0; i < m_elements.length(); ++i) {
+				UIBaseElement *element = m_elements[i];
+				if (element->IsA(T::GetType())) {
+					return (T*)element;
+				}
+			}
+		}
+		return NULL;
+	}
+	template <typename T>
+	void FindElements(array<T*> &elements) {
+		for (unsigned i = 0; i < m_elements.length(); ++i) {
+			UIBaseElement *element = m_elements[i];
+			if (element->IsA(T::GetType())) {
+				elements.add((T*)element);
+			}
+		}
+	}
 	void RemoveElement(UIBaseElement *element) {
 		m_elements.remove(element);
 	}
diff --git a/screenlib/UIElementButton.h b/screenlib/UIElementButton.h
index ae8aba56..ad75d416 100644
--- a/screenlib/UIElementButton.h
+++ b/screenlib/UIElementButton.h
@@ -44,14 +44,14 @@ DECLARE_TYPESAFE_CLASS(UIElement)
 
 	override bool HandleEvent(const SDL_Event &event);
 
-	void SetText(const char *text);
+	virtual void SetText(const char *text);
 
 	// Setting a click callback sets a simplified delegate
 	void SetClickCallback(void (*callback)(void));
 	void SetButtonDelegate(UIButtonDelegate *delegate, bool autodelete = true);
 
 protected:
-	// These can be overridden by inherited classes
+	// These can be overridden by inheriting classes
 	virtual void OnMouseEnter() { }
 	virtual void OnMouseLeave() { }
 	virtual void OnMouseDown() { }
diff --git a/screenlib/UIElementCheckbox.h b/screenlib/UIElementCheckbox.h
index 0f833ae2..bbd28e4b 100644
--- a/screenlib/UIElementCheckbox.h
+++ b/screenlib/UIElementCheckbox.h
@@ -21,10 +21,13 @@ DECLARE_TYPESAFE_CLASS(UIElementButton)
 	bool IsChecked() const {
 		return m_checked;
 	}
-	virtual void OnChecked(bool checked) { }
 
 	override void OnClick();
 
+protected:
+	// This can be overridden by inheriting classes
+	virtual void OnChecked(bool checked) { }
+
 protected:
 	bool m_checked;
 };
diff --git a/screenlib/UIElementEditbox.cpp b/screenlib/UIElementEditbox.cpp
new file mode 100644
index 00000000..e61d6136
--- /dev/null
+++ b/screenlib/UIElementEditbox.cpp
@@ -0,0 +1,211 @@
+/*
+    SCREENLIB:  A framebuffer library based on the SDL library
+    Copyright (C) 1997  Sam Lantinga
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+    Sam Lantinga
+    slouken@libsdl.org
+*/
+
+#include "UIElementEditbox.h"
+#include "UIElementLabel.h"
+
+UIElementType UIElementEditbox::s_elementType;
+
+
+UIElementEditbox::UIElementEditbox(UIBaseElement *parent, const char *name) :
+	UIElementButton(parent, name)
+{
+	m_focus = false;
+	m_highlight = false;
+	m_numeric = false;
+
+	// This is a reasonable default for a non-wrapping editbox
+	m_textMax = 128;
+	m_textLen = 0;
+	m_text = new char[m_textMax];
+	m_text[0] = '\0';
+}
+
+UIElementEditbox::~UIElementEditbox()
+{
+	delete[] m_text;
+}
+
+bool
+UIElementEditbox::Load(rapidxml::xml_node<> *node, const UITemplates *templates)
+{
+	if (!UIElementButton::Load(node, templates)) {
+		return false;
+	}
+
+	LoadBool(node, "numeric", m_numeric);
+
+	int maxLen;
+	if (LoadNumber(node, "maxLen", maxLen)) {
+		SetTextMax(maxLen);
+	}
+
+	return true;
+}
+
+bool
+UIElementEditbox::FinishLoading()
+{
+	if (!m_label) {
+		m_label = CreateLabel();
+		if (!m_label) {
+			fprintf(stderr, "Warning: Couldn't create editbox label\n");
+			return false;
+		}
+		AddElement(m_label);
+	}
+}
+			
+bool
+UIElementEditbox::HandleEvent(const SDL_Event &event)
+{
+	if (!m_focus) {
+		return UIElementButton::HandleEvent(event);
+	}
+
+	if (event.type == SDL_KEYUP) {
+		switch (event.key.keysym.sym) {
+			case SDLK_ESCAPE:
+				SetFocus(false);
+				return true;
+			case SDLK_TAB:
+				SetFocusNext();
+				return true;
+			case SDLK_DELETE:
+			case SDLK_BACKSPACE:
+				if (m_textLen > 0) {
+					if (m_highlight) {
+						m_textLen = 0;
+						m_text[0] = '\0';
+					} else {
+						--m_textLen;
+						m_text[m_textLen] = '\0';
+					}
+				}
+				SetHighlight(false);
+				OnTextChanged();
+				return true;
+			default:
+				break;
+		}
+	}
+
+	if (event.type == SDL_TEXTINPUT) {
+		// Note, this doesn't support non-ASCII characters at the moment
+		// To do that we would have to separate m_textMax and the size
+		// of the text buffer and it gets complicated for in-line editing.
+		char ch = event.text.text[0];
+		bool valid;
+		if (m_numeric) {
+			valid = (ch >= '0' && ch <= '9');
+		} else {
+			valid = (ch >= ' ' && ch <= '~');
+		}
+		if (valid && (m_highlight || (m_textLen < m_textMax))) {
+			if (m_highlight) {
+				m_textLen = 0;
+			}
+			m_text[m_textLen++] = ch;
+			m_text[m_textLen] = '\0';
+			SetHighlight(false);
+			OnTextChanged();
+		}
+		return true;
+	}
+
+	return UIElementButton::HandleEvent(event);
+}
+
+void
+UIElementEditbox::SetFocus(bool focus)
+{
+	m_focus = focus;
+
+	if (m_focus) {
+		array<UIElementEditbox*> editboxes;
+
+		SetHighlight(true);
+
+		// Take focus away from other editboxes
+		m_parent->FindElements<UIElementEditbox>(editboxes);
+		for (unsigned i = 0; i < editboxes.length(); ++i) {
+			if (editboxes[i] != this) {
+				editboxes[i]->SetFocus(false);
+			}
+		}
+	} else {
+		SetHighlight(false);
+	}
+}
+
+void
+UIElementEditbox::SetHighlight(bool highlight)
+{
+	if (highlight != m_highlight) {
+		m_highlight = highlight;
+		OnHighlightChanged();
+	}
+}
+
+void
+UIElementEditbox::SetFocusNext()
+{
+	UIElementEditbox *editbox;
+	unsigned i, j;
+
+	// We always lose focus even if we don't find another editbox
+	SetFocus(false);
+
+	editbox = m_parent->FindElement<UIElementEditbox>(this);
+	if (editbox) {
+		editbox->SetFocus(true);
+	}
+}
+
+void
+UIElementEditbox::SetTextMax(int maxLen)
+{
+	char *oldText = m_text;
+
+	m_textMax = maxLen;
+	m_text = new char[m_textMax+1];
+	if (m_textLen <= m_textMax) {
+		SDL_strlcpy(m_text, oldText, m_textMax+1);
+	} else {
+		SetText(oldText);
+	}
+	delete[] oldText;
+}
+
+void
+UIElementEditbox::SetText(const char *text)
+{
+	SDL_strlcpy(m_text, text, m_textMax+1);
+	m_textLen = SDL_strlen(m_text);
+	OnTextChanged();
+}
+
+void
+UIElementEditbox::OnTextChanged()
+{
+	UIElementButton::SetText(m_text);
+}
diff --git a/screenlib/UIElementEditbox.h b/screenlib/UIElementEditbox.h
new file mode 100644
index 00000000..67d3cf04
--- /dev/null
+++ b/screenlib/UIElementEditbox.h
@@ -0,0 +1,84 @@
+/*
+    SCREENLIB:  A framebuffer library based on the SDL library
+    Copyright (C) 1997  Sam Lantinga
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+    Sam Lantinga
+    slouken@libsdl.org
+*/
+
+#ifndef _UIElementEditbox_h
+#define _UIElementEditbox_h
+
+// This is a simple editbox class
+// It currently doesn't support multiline or editing within the line,
+// though the latter could be supported fairly easily.
+
+#include "UIElementButton.h"
+
+
+class UIElementEditbox : public UIElementButton
+{
+DECLARE_TYPESAFE_CLASS(UIElement)
+public:
+	UIElementEditbox(UIBaseElement *parent, const char *name = "");
+	virtual ~UIElementEditbox();
+
+	override bool Load(rapidxml::xml_node<> *node, const UITemplates *templates);
+	override bool FinishLoading();
+
+	override bool HandleEvent(const SDL_Event &event);
+
+	override void OnClick() {
+		SetFocus(true);
+	}
+
+	void SetFocus(bool focus);
+	void SetFocusNext();
+
+	void SetTextMax(int maxLen);
+
+	override void SetText(const char *text);
+	const char *GetText() const {
+		return m_text;
+	}
+
+	void SetNumber(int value) {
+		char buffer[32];
+		sprintf(buffer, "%d", value);
+		SetText(buffer);
+	}
+	int GetNumber() const {
+		return SDL_atoi(m_text);
+	}
+
+protected:
+	// These can be overridden by inheriting classes
+	virtual void OnHighlightChanged() { }
+	virtual void OnTextChanged();
+
+	void SetHighlight(bool highlight);
+
+protected:
+	bool m_focus;
+	bool m_highlight;
+	bool m_numeric;
+	int m_textMax;
+	int m_textLen;
+	char *m_text;
+};
+
+#endif // _UIElementEditbox_h
diff --git a/screenlib/UIElementLabel.cpp b/screenlib/UIElementLabel.cpp
index 7bdcad4f..eb403501 100644
--- a/screenlib/UIElementLabel.cpp
+++ b/screenlib/UIElementLabel.cpp
@@ -110,13 +110,18 @@ UIElementLabel::SetText(const char *text)
 	}
 	if (m_texture) {
 		GetUI()->FreeText(m_texture);
+		m_texture = NULL;
 	}
 
 	m_text = SDL_strdup(text);
-	m_texture = GetUI()->CreateText(m_text, m_fontName, m_fontSize, m_fontStyle, m_color);
+	if (*m_text) {
+		m_texture = GetUI()->CreateText(m_text, m_fontName, m_fontSize, m_fontStyle, m_color);
 
-	SetSize(m_screen->GetImageWidth(m_texture), 
-		m_screen->GetImageHeight(m_texture));
+		SetSize(m_screen->GetImageWidth(m_texture), 
+			m_screen->GetImageHeight(m_texture));
+	} else {
+		SetWidth(0);
+	}
 }
 
 void
diff --git a/screenlib/UIElementRadio.cpp b/screenlib/UIElementRadio.cpp
index e354b122..6267f2f0 100644
--- a/screenlib/UIElementRadio.cpp
+++ b/screenlib/UIElementRadio.cpp
@@ -32,12 +32,12 @@ UIElementRadioGroup::GetRadioButton(int id)
 void
 UIElementRadioGroup::RadioButtonChecked(UIElementRadioButton *button)
 {
-	for (unsigned i = 0; i < m_elements.length(); ++i) {
-		if (!m_elements[i]->IsA(UIElementRadioButton::GetType())) {
-			continue;
-		}
-		if (m_elements[i] != button) {
-			static_cast<UIElementRadioButton*>(m_elements[i])->SetChecked(false);
+	array<UIElementRadioButton*> buttons;
+
+	FindElements<UIElementRadioButton>(buttons);
+	for (unsigned i = 0; i < buttons.length(); ++i) {
+		if (buttons[i] != button) {
+			buttons[i]->SetChecked(false);
 		}
 	}
 	m_value = button->GetID();