Maelstrom: Added support for image buttons

https://github.com/libsdl-org/Maelstrom/commit/e6e2ae725428e6eb02dfe92b8491eccef87b7976

From e6e2ae725428e6eb02dfe92b8491eccef87b7976 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 6 Nov 2012 00:43:14 -0800
Subject: [PATCH] Added support for image buttons

---
 screenlib/UIDrawEngine.cpp    | 10 ++--
 screenlib/UIElement.cpp       | 24 ++++++----
 screenlib/UIElement.h         |  8 +++-
 screenlib/UIElementButton.cpp | 86 ++++++++++++++++++++++++++++++++++-
 screenlib/UIElementButton.h   | 13 ++++++
 screenlib/UITexture.cpp       |  1 +
 screenlib/UITexture.h         |  9 ++++
 7 files changed, 135 insertions(+), 16 deletions(-)

diff --git a/screenlib/UIDrawEngine.cpp b/screenlib/UIDrawEngine.cpp
index cd7dad94..ce2beaa6 100644
--- a/screenlib/UIDrawEngine.cpp
+++ b/screenlib/UIDrawEngine.cpp
@@ -132,19 +132,21 @@ UIDrawEngine::OnDraw()
 
 	if (m_textImage) {
 		UIArea *area = m_element->GetTextArea();
-		int x, y;
-		if (m_element->GetTextShadowOffset(&x, &y)) {
+		int x, y, shadowX, shadowY;
+		m_element->GetTextOffset(&x, &y);
+
+		if (m_element->GetTextShadowOffset(&shadowX, &shadowY)) {
 			Uint8 r, g, b;
 
 			m_screen->GetRGB(m_element->GetTextShadowColor(), &r, &g, &b);
 			SDL_SetTextureColorMod(m_textImage->Texture(), r, g, b);
 
-			m_screen->QueueBlit(m_textImage->Texture(), area->X()+x, area->Y()+y, area->Width(), area->Height(), NOCLIP);
+			m_screen->QueueBlit(m_textImage->Texture(), area->X()+x+shadowX, area->Y()+y+shadowY, area->Width(), area->Height(), NOCLIP);
 
 			m_screen->GetRGB(m_element->GetCurrentColor(), &r, &g, &b);
 			SDL_SetTextureColorMod(m_textImage->Texture(), r, g, b);
 		}
-		m_screen->QueueBlit(m_textImage->Texture(), area->X(), area->Y(), area->Width(), area->Height(), NOCLIP);
+		m_screen->QueueBlit(m_textImage->Texture(), area->X()+x, area->Y()+y, area->Width(), area->Height(), NOCLIP);
 	}
 }
 
diff --git a/screenlib/UIElement.cpp b/screenlib/UIElement.cpp
index b4b73109..3dce4db3 100644
--- a/screenlib/UIElement.cpp
+++ b/screenlib/UIElement.cpp
@@ -42,8 +42,10 @@ UIElement::UIElement(UIBaseElement *parent, const char *name, UIDrawEngine *draw
 	m_fontStyle = UIFONT_STYLE_NORMAL;
 	m_text = NULL;
 	m_textBinding = NULL;
-	m_textShadowOffsetX = 0;
-	m_textShadowOffsetY = 0;
+	m_textOffset.x = 0;
+	m_textOffset.y = 0;
+	m_textShadowOffset.x = 0;
+	m_textShadowOffset.y = 0;
 	m_textShadowColor = m_screen->MapRGB(0x00, 0x00, 0x00);
 	m_image = NULL;
 	m_mouseEnabled = false;
@@ -71,7 +73,7 @@ UIElement::~UIElement()
 	if (m_textBinding) {
 		SDL_free(m_textBinding);
 	}
-	if (m_image) {
+	if (m_image && !m_image->IsLocked()) {
 		m_ui->FreeImage(m_image);
 	}
 	if (m_clickCallback) {
@@ -123,6 +125,10 @@ UIElement::Load(rapidxml::xml_node<> *node, const UITemplates *templates)
 		SetColor(color);
 	}
 
+	if (LoadColor(node, "disabledColor", color)) {
+		SetDisabledColor(color);
+	}
+
 	attr = node->first_attribute("font", 0, false);
 	if (attr) {
 		if (!ParseFont(attr->value())) {
@@ -445,16 +451,16 @@ UIElement::SetText(const char *text)
 void
 UIElement::SetTextShadowOffset(int x, int y)
 {
-	m_textShadowOffsetX = x;
-	m_textShadowOffsetY = y;
+	m_textShadowOffset.x = x;
+	m_textShadowOffset.y = y;
 }
 
 bool
 UIElement::GetTextShadowOffset(int *x, int *y) const
 {
-	if (m_textShadowOffsetX || m_textShadowOffsetY) {
-		*x = m_textShadowOffsetX;
-		*y = m_textShadowOffsetY;
+	if (m_textShadowOffset.x || m_textShadowOffset.y) {
+		*x = m_textShadowOffset.y;
+		*y = m_textShadowOffset.y;
 		return true;
 	} else {
 		*x = 0;
@@ -493,7 +499,7 @@ UIElement::SetImage(const char *file)
 void
 UIElement::SetImage(UITexture *image)
 {
-	if (m_image) {
+	if (m_image && !m_image->IsLocked()) {
 		m_ui->FreeImage(m_image);
 	}
 	m_image = image;
diff --git a/screenlib/UIElement.h b/screenlib/UIElement.h
index 7e3f39cc..ab0125d4 100644
--- a/screenlib/UIElement.h
+++ b/screenlib/UIElement.h
@@ -149,6 +149,10 @@ DECLARE_TYPESAFE_CLASS(UIBaseElement)
 	UIArea *GetTextArea() {
 		return &m_textArea;
 	}
+	void GetTextOffset(int *x, int *y) const {
+		*x = m_textOffset.x;
+		*y = m_textOffset.y;
+	}
 	void SetTextShadowOffset(int x, int y);
 	bool GetTextShadowOffset(int *x, int *y) const;
 	void SetTextShadowColor(Uint8 R, Uint8 G, Uint8 B);
@@ -204,8 +208,8 @@ DECLARE_TYPESAFE_CLASS(UIBaseElement)
 	char *m_text;
 	char *m_textBinding;
 	UIArea m_textArea;
-	int m_textShadowOffsetX;
-	int m_textShadowOffsetY;
+	SDL_Point m_textOffset;
+	SDL_Point m_textShadowOffset;
 	Uint32 m_textShadowColor;
 	UITexture *m_image;
 	UIArea m_imageArea;
diff --git a/screenlib/UIElementButton.cpp b/screenlib/UIElementButton.cpp
index 43c30a20..787c963f 100644
--- a/screenlib/UIElementButton.cpp
+++ b/screenlib/UIElementButton.cpp
@@ -36,6 +36,8 @@ UIElementButton::UIElementButton(UIBaseElement *parent, const char *name, UIDraw
 	m_hotkeyMod = KMOD_NONE;
 	m_clickSound = 0;
 	m_clickPanel = NULL;
+	m_buttonState = NUM_BUTTON_STATES;
+	SDL_zero(m_stateImages);
 }
 
 UIElementButton::~UIElementButton()
@@ -43,6 +45,14 @@ UIElementButton::~UIElementButton()
 	if (m_clickPanel) {
 		SDL_free(m_clickPanel);
 	}
+
+	for (int i = 0; i < SDL_arraysize(m_stateImages); ++i) {
+		UITexture *image = m_stateImages[i];
+		if (image) {
+			image->SetLocked(false);
+			m_ui->FreeImage(image);
+		}
+	}
 }
 
 bool
@@ -87,7 +97,31 @@ UIElementButton::Load(rapidxml::xml_node<> *node, const UITemplates *templates)
 		}
 	}
 
-	
+	// Load the button state images, if any
+	static const char *stateImageNames[] = {
+		"normal_image",
+		"pressed_image",
+		"disabled_image",
+	};
+	SDL_COMPILE_TIME_ASSERT(stateImageNames, SDL_arraysize(stateImageNames) == NUM_BUTTON_STATES);
+
+	for (int i = 0; i < NUM_BUTTON_STATES; ++i) {
+		attr = node->first_attribute(stateImageNames[i], 0, false);
+		if (!attr) {
+			continue;
+		}
+
+		UITexture *image = m_ui->CreateImage(attr->value());
+		if (image) {
+			image->SetLocked(true);
+			m_stateImages[i] = image;
+		} else {
+			fprintf(stderr, "Warning: Couldn't load image '%s'\n", attr->value());
+			return false;
+		}
+	}
+	SetButtonState(BUTTON_STATE_NORMAL);
+
 	LoadNumber(node, "clickSound", m_clickSound);
 	LoadString(node, "clickPanel", m_clickPanel);
 
@@ -146,6 +180,56 @@ UIElementButton::HandleEvent(const SDL_Event &event)
 	return UIElement::HandleEvent(event);
 }
 
+void
+UIElementButton::OnMouseDown()
+{
+	UIElement::OnMouseDown();
+
+	SetButtonState(BUTTON_STATE_PRESSED);
+}
+
+void
+UIElementButton::OnMouseUp()
+{
+	UIElement::OnMouseUp();
+
+	SetButtonState(BUTTON_STATE_NORMAL);
+}
+
+void
+UIElementButton::UpdateDisabledState()
+{
+	UIElement::UpdateDisabledState();
+
+	if (IsDisabled()) {
+		SetButtonState(BUTTON_STATE_DISABLED);
+	} else {
+		SetButtonState(BUTTON_STATE_NORMAL);
+	}
+}
+
+void
+UIElementButton::SetButtonState(BUTTON_STATE state)
+{
+	if (state == m_buttonState) {
+		return;
+	}
+
+	if (state == BUTTON_STATE_PRESSED) {
+		m_textOffset.x = 1;
+		m_textOffset.y = 1;
+	} else {
+		m_textOffset.x = 0;
+		m_textOffset.y = 0;
+	}
+
+	if (m_stateImages[state]) {
+		SetImage(m_stateImages[state]);
+	}
+
+	m_buttonState = state;
+}
+
 void
 UIElementButton::OnClick()
 {
diff --git a/screenlib/UIElementButton.h b/screenlib/UIElementButton.h
index 62d65ac5..6f578342 100644
--- a/screenlib/UIElementButton.h
+++ b/screenlib/UIElementButton.h
@@ -35,8 +35,19 @@ DECLARE_TYPESAFE_CLASS(UIElement)
 	override bool Load(rapidxml::xml_node<> *node, const UITemplates *templates);
 
 	override bool HandleEvent(const SDL_Event &event);
+	override void OnMouseDown();
+	override void OnMouseUp();
 
 protected:
+	enum BUTTON_STATE {
+		BUTTON_STATE_NORMAL,
+		BUTTON_STATE_PRESSED,
+		BUTTON_STATE_DISABLED,
+		NUM_BUTTON_STATES
+	};
+	void SetButtonState(BUTTON_STATE state);
+
+	override void UpdateDisabledState();
 	override void OnClick();
 
 	bool ShouldHandleKey(SDL_Keycode key);
@@ -46,6 +57,8 @@ DECLARE_TYPESAFE_CLASS(UIElement)
 	int m_hotkeyMod;
 	int m_clickSound;
 	char *m_clickPanel;
+	BUTTON_STATE m_buttonState;
+	UITexture *m_stateImages[NUM_BUTTON_STATES];
 };
 
 #endif // _UIElementButton_h
diff --git a/screenlib/UITexture.cpp b/screenlib/UITexture.cpp
index d11d2b84..2c3a1d20 100644
--- a/screenlib/UITexture.cpp
+++ b/screenlib/UITexture.cpp
@@ -29,4 +29,5 @@ UITexture::UITexture(SDL_Texture *texture, float scale)
 	m_texture = texture;
 	m_scale = scale;
 	SDL_QueryTexture(texture, NULL, NULL, &m_textureWidth, &m_textureHeight);
+	m_locked = false;
 }
diff --git a/screenlib/UITexture.h b/screenlib/UITexture.h
index c23aba9e..f37adfdd 100644
--- a/screenlib/UITexture.h
+++ b/screenlib/UITexture.h
@@ -57,11 +57,20 @@ class UITexture
 		m_scale = scale;
 	}
 
+	// When a texture is locked it shouldn't be freed
+	void SetLocked(bool locked) {
+		m_locked = locked;
+	}
+	bool IsLocked() const {
+		return m_locked;
+	}
+
 protected:
 	SDL_Texture *m_texture;
 	int m_textureWidth;
 	int m_textureHeight;
 	float m_scale;
+	bool m_locked;
 };
 
 #endif // _UITexture_h