Maelstrom: Draw the touch controls with 50% alpha

From 78c5a8ac0ebfc1e55cb070c1b7f53e357479bd91 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 2 Apr 2026 23:04:37 -0700
Subject: [PATCH] Draw the touch controls with 50% alpha

---
 Data/UI/game.xml           | 72 +++++++++++++++++++-------------------
 screenlib/SDL_FrameBuf.h   | 24 ++++++-------
 screenlib/UIDrawEngine.cpp | 10 +++---
 screenlib/UIElement.cpp    | 12 +++++++
 screenlib/UIElement.h      |  3 ++
 screenlib/UITexture.cpp    |  4 ++-
 screenlib/UITexture.h      |  2 +-
 7 files changed, 73 insertions(+), 54 deletions(-)

diff --git a/Data/UI/game.xml b/Data/UI/game.xml
index 5f574aa5..38d54aeb 100644
--- a/Data/UI/game.xml
+++ b/Data/UI/game.xml
@@ -102,89 +102,89 @@
 		<Area condition="!PHONE" name="touch_controls" show="false">
 			<Elements>
 				<!-- Touch controls -->
-				<Button name="abort" action="CONTROL_ABORT" image="circle">
+				<Button name="abort" action="CONTROL_ABORT" image="circle" alpha="128">
 					<Size w="30" h="30"/>
 					<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT" x="20" y="8"/>
 					<Elements>
-						<Image image="abort">
+						<Image image="abort" alpha="128">
 							<Size w="24" h="24"/>
 							<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
 						</Image>
 					</Elements>
 				</Button>
-				<Button name="pause" action="CONTROL_PAUSE" image="circle">
+				<Button name="pause" action="CONTROL_PAUSE" image="circle" alpha="128">
 					<Size w="30" h="30"/>
 					<Anchor anchorFrom="TOPRIGHT" anchorTo="TOPRIGHT" x="-20" y="8"/>
 					<Elements>
-						<Image image="pause">
+						<Image image="pause" alpha="128">
 							<Size w="24" h="24"/>
 							<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
 						</Image>
 					</Elements>
 				</Button>
-				<Button name="zoom" action="CONTROL_ZOOM" image="circle">
+				<Button name="zoom" action="CONTROL_ZOOM" image="circle" alpha="128">
 					<Size w="30" h="30"/>
 					<Anchor anchorFrom="TOP" anchorTo="BOTTOM" anchor="pause" y="8"/>
 					<Elements>
-						<Image name="zoom_in" image="zoom-in">
+						<Image name="zoom_in" image="zoom-in" alpha="128">
 							<Size w="24" h="24"/>
 							<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
 						</Image>
-						<Image name="zoom_out" image="zoom-out">
+						<Image name="zoom_out" image="zoom-out" alpha="128">
 							<Size w="24" h="24"/>
 							<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
 						</Image>
 					</Elements>
 				</Button>
 
-				<Thumbstick image="split-circle">
+				<Thumbstick image="split-circle" alpha="128">
 					<Size w="85" h="85"/>
 					<Anchor anchorFrom="BOTTOMLEFT" anchorTo="BOTTOMLEFT" x="20" y="-20"/>
 					<Action angle="90" arc="180" active_radius="0" action_enter="CONTROL_DOWN_RIGHT" action_leave="CONTROL_UP_RIGHT"/>
 					<Action angle="270" arc="180" active_radius="0" action_enter="CONTROL_DOWN_LEFT" action_leave="CONTROL_UP_LEFT"/>
 					<Elements>
-						<Image image="rotate-left">
+						<Image image="rotate-left" alpha="128">
 							<Size w="30" h="30"/>
 							<Anchor anchorFrom="LEFT" anchorTo="LEFT" x="6"/>
 						</Image>
-						<Image image="rotate-right">
+						<Image image="rotate-right" alpha="128">
 							<Size w="30" h="30"/>
 							<Anchor anchorFrom="RIGHT" anchorTo="RIGHT" x="-6"/>
 						</Image>
 					</Elements>
 				</Image>
 
-				<Thumbstick name="fire" image="circle">
+				<Thumbstick name="fire" image="circle" alpha="128">
 					<Size w="40" h="40"/>
 					<Anchor anchorFrom="BOTTOMRIGHT" anchorTo="BOTTOMRIGHT" x="-60" y="-30"/>
 					<Action action_enter="CONTROL_DOWN_FIRE" action_leave="CONTROL_UP_FIRE"/>
 					<Elements>
-						<Icon id="136">
+						<Icon id="136" alpha="128">
 							<Size w="16" h="16"/>
 							<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
 						</Icon>
 					</Elements>
 				</Thumbstick>
-				<Thumbstick name="shield" image="circle">
+				<Thumbstick name="shield" image="circle" alpha="128">
 					<Size w="40" h="40"/>
 					<Anchor anchorFrom="BOTTOMRIGHT" anchorTo="BOTTOMRIGHT" x="-30" y="-60"/>
 					<Action action_enter="CONTROL_DOWN_SHIELD" action_leave="CONTROL_UP_SHIELD"/>
 					<Elements>
-						<Image image="shield">
+						<Image image="shield" alpha="128">
 							<Size w="16" h="16"/>
 							<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
-						</Icon>
+						</Image>
 					</Elements>
 				</Thumbstick>
-				<Thumbstick name="thrust" image="circle">
+				<Thumbstick name="thrust" image="circle" alpha="128">
 					<Size w="40" h="40"/>
 					<Anchor anchorFrom="BOTTOMRIGHT" anchorTo="BOTTOMRIGHT" x="-90" y="-60"/>
 					<Action action_enter="CONTROL_DOWN_THRUST" action_leave="CONTROL_UP_THRUST"/>
 					<Elements>
-						<Image image="thrust">
+						<Image image="thrust" alpha="128">
 							<Size w="16" h="16"/>
 							<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
-						</Icon>
+						</Image>
 					</Elements>
 				</Thumbstick>
 			</Elements>
@@ -193,89 +193,89 @@
 		<Area condition="PHONE" name="touch_controls" show="false">
 			<Elements>
 				<!-- Touch controls -->
-				<Button name="abort" action="CONTROL_ABORT" image="circle">
+				<Button name="abort" action="CONTROL_ABORT" image="circle" alpha="128">
 					<Size w="45" h="45"/>
 					<Anchor anchorFrom="TOPLEFT" anchorTo="TOPLEFT" x="30" y="12"/>
 					<Elements>
-						<Image image="abort">
+						<Image image="abort" alpha="128">
 							<Size w="36" h="36"/>
 							<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
 						</Image>
 					</Elements>
 				</Button>
-				<Button name="pause" action="CONTROL_PAUSE" image="circle">
+				<Button name="pause" action="CONTROL_PAUSE" image="circle" alpha="128">
 					<Size w="45" h="45"/>
 					<Anchor anchorFrom="TOPRIGHT" anchorTo="TOPRIGHT" x="-30" y="12"/>
 					<Elements>
-						<Image image="pause">
+						<Image image="pause" alpha="128">
 							<Size w="36" h="36"/>
 							<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
 						</Image>
 					</Elements>
 				</Button>
-				<Button name="zoom" action="CONTROL_ZOOM" image="circle">
+				<Button name="zoom" action="CONTROL_ZOOM" image="circle" alpha="128">
 					<Size w="45" h="45"/>
 					<Anchor anchorFrom="TOP" anchorTo="BOTTOM" anchor="pause" y="12"/>
 					<Elements>
-						<Image name="zoom_in" image="zoom-in">
+						<Image name="zoom_in" image="zoom-in" alpha="128">
 							<Size w="36" h="36"/>
 							<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
 						</Image>
-						<Image name="zoom_out" image="zoom-out">
+						<Image name="zoom_out" image="zoom-out" alpha="128">
 							<Size w="36" h="36"/>
 							<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
 						</Image>
 					</Elements>
 				</Button>
 
-				<Thumbstick image="split-circle">
+				<Thumbstick image="split-circle" alpha="128">
 					<Size w="128" h="128"/>
 					<Anchor anchorFrom="BOTTOMLEFT" anchorTo="BOTTOMLEFT" x="30" y="-30"/>
 					<Action angle="90" arc="180" active_radius="0" action_enter="CONTROL_DOWN_RIGHT" action_leave="CONTROL_UP_RIGHT"/>
 					<Action angle="270" arc="180" active_radius="0" action_enter="CONTROL_DOWN_LEFT" action_leave="CONTROL_UP_LEFT"/>
 					<Elements>
-						<Image image="rotate-left">
+						<Image image="rotate-left" alpha="128">
 							<Size w="45" h="45"/>
 							<Anchor anchorFrom="LEFT" anchorTo="LEFT" x="9"/>
 						</Image>
-						<Image image="rotate-right">
+						<Image image="rotate-right" alpha="128">
 							<Size w="45" h="45"/>
 							<Anchor anchorFrom="RIGHT" anchorTo="RIGHT" x="-9"/>
 						</Image>
 					</Elements>
 				</Image>
 
-				<Thumbstick name="fire" image="circle">
+				<Thumbstick name="fire" image="circle" alpha="128">
 					<Size w="60" h="60"/>
 					<Anchor anchorFrom="BOTTOMRIGHT" anchorTo="BOTTOMRIGHT" x="-90" y="-45"/>
 					<Action action_enter="CONTROL_DOWN_FIRE" action_leave="CONTROL_UP_FIRE"/>
 					<Elements>
-						<Icon id="136">
+						<Icon id="136" alpha="128">
 							<Size w="24" h="24"/>
 							<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
 						</Icon>
 					</Elements>
 				</Thumbstick>
-				<Thumbstick name="shield" image="circle">
+				<Thumbstick name="shield" image="circle" alpha="128">
 					<Size w="60" h="60"/>
 					<Anchor anchorFrom="BOTTOMRIGHT" anchorTo="BOTTOMRIGHT" x="-45" y="-90"/>
 					<Action action_enter="CONTROL_DOWN_SHIELD" action_leave="CONTROL_UP_SHIELD"/>
 					<Elements>
-						<Image image="shield">
+						<Image image="shield" alpha="128">
 							<Size w="24" h="24"/>
 							<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
-						</Icon>
+						</Image>
 					</Elements>
 				</Thumbstick>
-				<Thumbstick name="thrust" image="circle">
+				<Thumbstick name="thrust" image="circle" alpha="128">
 					<Size w="60" h="60"/>
 					<Anchor anchorFrom="BOTTOMRIGHT" anchorTo="BOTTOMRIGHT" x="-135" y="-90"/>
 					<Action action_enter="CONTROL_DOWN_THRUST" action_leave="CONTROL_UP_THRUST"/>
 					<Elements>
-						<Image image="thrust">
+						<Image image="thrust" alpha="128">
 							<Size w="24" h="24"/>
 							<Anchor anchorFrom="CENTER" anchorTo="CENTER"/>
-						</Icon>
+						</Image>
 					</Elements>
 				</Thumbstick>
 			</Elements>
diff --git a/screenlib/SDL_FrameBuf.h b/screenlib/SDL_FrameBuf.h
index b33a0f0f..fa114604 100644
--- a/screenlib/SDL_FrameBuf.h
+++ b/screenlib/SDL_FrameBuf.h
@@ -138,22 +138,22 @@ class FrameBuf : public ErrorBase {
 
 	/* Drawing routines */
 	void Clear(int x, int y, int w, int h) {
-		FillRect(x, y, w, h, 0);
+		FillRect(x, y, w, h, 0, SDL_ALPHA_OPAQUE);
 	}
 	void Clear(Uint32 color = 0) {
-		UpdateDrawColor(color);
+		UpdateDrawColor(color, SDL_ALPHA_OPAQUE);
 		SDL_RenderClear(m_renderer);
 	}
-	void DrawPoint(int x, int y, Uint32 color) {
-		UpdateDrawColor(color);
+	void DrawPoint(int x, int y, Uint32 color, Uint8 alpha = SDL_ALPHA_OPAQUE) {
+		UpdateDrawColor(color, alpha);
 		SDL_RenderPoint(m_renderer, (float)x, (float)y);
 	}
-	void DrawLine(int x1, int y1, int x2, int y2, Uint32 color) {
-		UpdateDrawColor(color);
+	void DrawLine(int x1, int y1, int x2, int y2, Uint32 color, Uint8 alpha = SDL_ALPHA_OPAQUE) {
+		UpdateDrawColor(color, alpha);
 		SDL_RenderLine(m_renderer, (float)x1, (float)y1, (float)x2, (float)y2);
 	}
-	void DrawRect(int x1, int y1, int w, int h, Uint32 color) {
-		UpdateDrawColor(color);
+	void DrawRect(int x1, int y1, int w, int h, Uint32 color, Uint8 alpha = SDL_ALPHA_OPAQUE) {
+		UpdateDrawColor(color, alpha);
 
 		SDL_FRect rect;
 		rect.x = (float)x1;
@@ -162,8 +162,8 @@ class FrameBuf : public ErrorBase {
 		rect.h = (float)h;
 		SDL_RenderRect(m_renderer, &rect);
 	}
-	void FillRect(int x1, int y1, int w, int h, Uint32 color) {
-		UpdateDrawColor(color);
+	void FillRect(int x1, int y1, int w, int h, Uint32 color, Uint8 alpha = SDL_ALPHA_OPAQUE) {
+		UpdateDrawColor(color, alpha);
 
 		SDL_FRect rect;
 		rect.x = (float)x1;
@@ -216,12 +216,12 @@ class FrameBuf : public ErrorBase {
 	bool m_gamepadMouseDown = false;
 	array<SDL_Gamepad *> m_gamepads;
 
-	void UpdateDrawColor(Uint32 color) {
+	void UpdateDrawColor(Uint32 color, Uint8 alpha) {
 		Uint8 r, g, b;
 		r = (color >> 16) & 0xFF;
 		g = (color >>  8) & 0xFF;
 		b = (color >>  0) & 0xFF;
-		SDL_SetRenderDrawColor(m_renderer, r, g, b, SDL_ALPHA_OPAQUE);
+		SDL_SetRenderDrawColor(m_renderer, r, g, b, alpha);
 	}
 
 	void OpenGamepad(SDL_JoystickID id);
diff --git a/screenlib/UIDrawEngine.cpp b/screenlib/UIDrawEngine.cpp
index 074a703c..b8395df8 100644
--- a/screenlib/UIDrawEngine.cpp
+++ b/screenlib/UIDrawEngine.cpp
@@ -118,24 +118,24 @@ UIDrawEngine::OnDraw()
 	if (m_element->HasFill()) {
 		m_screen->FillRect(m_element->X(), m_element->Y(),
 				m_element->Width(), m_element->Height(),
-				m_element->GetFillColor());
+				m_element->GetFillColor(), m_element->GetAlpha());
 	}
 
 	UITexture *background = m_element->GetBackground();
 	if (background) {
-		background->Draw(m_screen, m_element->X(), m_element->Y(), m_element->Width(), m_element->Height());
+		background->Draw(m_screen, m_element->X(), m_element->Y(), m_element->Width(), m_element->Height(), m_element->GetAlpha());
 	}
 
 	if (m_element->HasBorder()) {
 		m_screen->DrawRect(m_element->X(), m_element->Y(),
 				m_element->Width(), m_element->Height(),
-				m_element->GetCurrentColor());
+				m_element->GetCurrentColor(), m_element->GetAlpha());
 	}
 
 	UITexture *image = m_element->GetImage();
 	if (image) {
 		UIArea *area = m_element->GetImageArea();
-		image->Draw(m_screen, area->X(), area->Y(), area->Width(), area->Height());
+		image->Draw(m_screen, area->X(), area->Y(), area->Width(), area->Height(), m_element->GetAlpha());
 	}
 
 	if (m_textImage) {
@@ -143,6 +143,8 @@ UIDrawEngine::OnDraw()
 		int x, y, shadowX, shadowY;
 		m_element->GetTextOffset(&x, &y);
 
+		SDL_SetTextureAlphaMod(m_textImage->Texture(), m_element->GetAlpha());
+
 		if (m_element->GetTextShadowOffset(&shadowX, &shadowY)) {
 			Uint8 r, g, b;
 
diff --git a/screenlib/UIElement.cpp b/screenlib/UIElement.cpp
index beb2df27..393e309a 100644
--- a/screenlib/UIElement.cpp
+++ b/screenlib/UIElement.cpp
@@ -37,6 +37,7 @@ UIElement::UIElement(UIBaseElement *parent, const char *name, UIDrawEngine *draw
 	m_fillColor = m_screen->MapRGB(0x00, 0x00, 0x00);
 	m_color = m_screen->MapRGB(0xFF, 0xFF, 0xFF);
 	m_disabledColor = m_screen->MapRGB(0x80, 0x80, 0x80);
+	m_alpha = SDL_ALPHA_OPAQUE;
 	m_fontName = NULL;
 	m_fontSize = 0;
 	m_fontStyle = UIFONT_STYLE_NORMAL;
@@ -134,6 +135,11 @@ UIElement::Load(rapidxml::xml_node<> *node, const UITemplates *templates)
 		SetDisabledColor(color);
 	}
 
+	int alpha;
+	if (LoadNumber(node, "alpha", alpha)) {
+		SetAlpha((Uint8)alpha);
+	}
+
 	attr = node->first_attribute("font", 0, false);
 	if (attr) {
 		if (!ParseFont(attr->value())) {
@@ -405,6 +411,12 @@ UIElement::SetDisabledColor(Uint32 color)
 	}
 }
 
+void
+UIElement::SetAlpha(Uint8 alpha)
+{
+	m_alpha = alpha;
+}
+
 void
 UIElement::UpdateDisabledState()
 {
diff --git a/screenlib/UIElement.h b/screenlib/UIElement.h
index cbb52da8..1efb9710 100644
--- a/screenlib/UIElement.h
+++ b/screenlib/UIElement.h
@@ -177,6 +177,8 @@ DECLARE_TYPESAFE_CLASS(UIBaseElement)
 	Uint32 GetCurrentColor() const {
 		return IsDisabled() ? GetDisabledColor() : GetColor();
 	}
+	void SetAlpha(Uint8 alpha);
+	Uint8 GetAlpha() const { return m_alpha; }
 
 	// Text information
 	void SetFont(const char *fontName, int fontSize, UIFontStyle fontStyle);
@@ -272,6 +274,7 @@ DECLARE_TYPESAFE_CLASS(UIBaseElement)
 	Uint32 m_fillColor;
 	Uint32 m_color;
 	Uint32 m_disabledColor;
+	Uint8 m_alpha;
 	char *m_fontName;
 	int m_fontSize;
 	UIFontStyle m_fontStyle;
diff --git a/screenlib/UITexture.cpp b/screenlib/UITexture.cpp
index bc5ce5b7..d7352d44 100644
--- a/screenlib/UITexture.cpp
+++ b/screenlib/UITexture.cpp
@@ -103,8 +103,10 @@ UITexture::SetStretchGrid(int cornerSize)
 }
 
 void
-UITexture::Draw(FrameBuf *screen, int x, int y, int w, int h)
+UITexture::Draw(FrameBuf *screen, int x, int y, int w, int h, Uint8 alpha)
 {
+	SDL_SetTextureAlphaMod(m_texture, alpha);
+
 	if (m_stretch) {
 		SDL_Rect dstAreas[NUM_STRETCH_AREAS];
 
diff --git a/screenlib/UITexture.h b/screenlib/UITexture.h
index 59901161..916b07f6 100644
--- a/screenlib/UITexture.h
+++ b/screenlib/UITexture.h
@@ -60,7 +60,7 @@ class UITexture
 	void SetStretchGrid(int cornerSize);
 	bool IsStretching() const { return m_stretch; }
 
-	void Draw(FrameBuf *screen, int x, int y, int w, int h);
+	void Draw(FrameBuf *screen, int x, int y, int w, int h, Uint8 alpha);
 
 	// When a texture is locked it shouldn't be freed
 	void SetLocked(bool locked) {