Maelstrom: Updated touch control visibility rules

From 2b7858f9b7aaed9a5b12ffde80a2d0ffb1574a10 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 3 Apr 2026 12:57:01 -0700
Subject: [PATCH] Updated touch control visibility rules

Show the touch controls when starting on a mobile device without a controller plugged in.
Show the touch controls out if touch input is provided.
Fade the touch controls out if there has been no touch input for 10 seconds.
---
 game/game.cpp | 104 +++++++++++++++++++++++++++++++++++++++++++-------
 game/game.h   |  12 +++++-
 2 files changed, 101 insertions(+), 15 deletions(-)

diff --git a/game/game.cpp b/game/game.cpp
index f4392fde..1fc314e6 100644
--- a/game/game.cpp
+++ b/game/game.cpp
@@ -196,6 +196,16 @@ GamePanelDelegate::OnShow()
 {
 	int i;
 
+	SDL_AddEventWatch(EventWatch, this);
+
+	m_lastTouch = SDL_GetTicks();
+
+	if ((IsPhone() || IsTablet()) && GetNumGamepads() == 0) {
+		ShowTouchControls();
+	} else {
+		HideTouchControls();
+	}
+
 	UpdateZoom();
 
 	SetSteamTimelineMode(STEAM_TIMELINE_PLAYING);
@@ -261,6 +271,79 @@ GamePanelDelegate::OnHide()
 		delete gSprites[gNumSprites-1];
 
 	sound->HaltSound();
+
+	SDL_RemoveEventWatch(EventWatch, this);
+}
+
+bool SDLCALL
+GamePanelDelegate::EventWatch(void *userdata, SDL_Event *event)
+{
+	GamePanelDelegate *delegate = static_cast<GamePanelDelegate*>(userdata);
+	delegate->ObserveEvent(event);
+	return true;
+}
+
+void
+GamePanelDelegate::ObserveEvent(const SDL_Event *event)
+{
+	if (event->type == SDL_EVENT_FINGER_DOWN) {
+		ShowTouchControls();
+	} else if (event->type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED ||
+	           event->type == SDL_EVENT_WINDOW_SAFE_AREA_CHANGED) {
+		UpdateZoom();
+	}
+}
+
+void
+GamePanelDelegate::ShowTouchControls()
+{
+	if (!m_touchControls) {
+		return;
+	}
+
+	m_lastTouch = SDL_GetTicks();
+
+	if (!m_touchControls->IsShown()) {
+		m_touchFading = false;
+		m_touchControls->SetAlpha(128);
+		m_touchControls->Show();
+	}
+}
+
+void
+GamePanelDelegate::HideTouchControls()
+{
+	if (!m_touchControls) {
+		return;
+	}
+
+	m_touchControls->Hide();
+}
+
+void
+GamePanelDelegate::HandleTouchFading()
+{
+	if (!m_touchControls || !m_touchControls->IsShown()) {
+		return;
+	}
+
+	if (m_touchFading) {
+		const int max = 16;
+		int v = max - m_fadeStep;
+		Uint8 value = (Uint8)(128 * v / max);
+
+		m_touchControls->SetAlpha(value);
+		++m_fadeStep;
+		if (m_fadeStep > max) {
+			HideTouchControls();
+		}
+	} else {
+		const Uint64 TOUCH_FADE_TIMEOUT = 10 * 1000;
+		if ((SDL_GetTicks() - m_lastTouch) >= TOUCH_FADE_TIMEOUT) {
+			m_touchFading = true;
+			m_fadeStep = 1;
+		}
+	}
 }
 
 void
@@ -269,6 +352,13 @@ GamePanelDelegate::OnTick()
 	int i, j;
 	SYNC_RESULT syncResult;
 
+	if (m_state == STATE_PLAYING) {
+		HandleTouchFading();
+	} else {
+		// Don't time out touch while the bonus is showing
+		m_lastTouch = SDL_GetTicks();
+	}
+
 	switch (m_state) {
 	case STATE_SHOW_BONUS:
 		ShowBonus();
@@ -524,20 +614,6 @@ GamePanelDelegate::OnDraw(DRAWLEVEL drawLevel)
 	StopZoomedDrawing();
 }
 
-bool
-GamePanelDelegate::HandleEvent(const SDL_Event &event)
-{
-	if (event.type == SDL_EVENT_FINGER_DOWN) {
-		if (m_touchControls) {
-			m_touchControls->Show();
-		}
-	} else if (event.type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED ||
-	           event.type == SDL_EVENT_WINDOW_SAFE_AREA_CHANGED) {
-		UpdateZoom();
-	}
-	return false;
-}
-
 bool
 GamePanelDelegate::OnAction(UIBaseElement *sender, const char *action)
 {
diff --git a/game/game.h b/game/game.h
index 29004910..eebc70db 100644
--- a/game/game.h
+++ b/game/game.h
@@ -37,10 +37,16 @@ class GamePanelDelegate : public UIPanelDelegate
 	virtual void OnHide();
 	virtual void OnTick();
 	virtual void OnDraw(DRAWLEVEL drawLevel);
-	virtual bool HandleEvent(const SDL_Event &event);
 	virtual bool OnAction(UIBaseElement *sender, const char *action);
 
+	void ObserveEvent(const SDL_Event *event);
+
 protected:
+	static bool SDLCALL EventWatch(void *userdata, SDL_Event *event);
+
+	void ShowTouchControls();
+	void HideTouchControls();
+	void HandleTouchFading();
 	void UpdateZoom();
 	void StartZoomUI(const SDL_Rect &rect);
 	void StopZoomUI();
@@ -109,6 +115,10 @@ class GamePanelDelegate : public UIPanelDelegate
 
 	SDL_Texture *m_texture = nullptr;
 	SDL_Rect m_savedClip;
+
+	Uint64 m_lastTouch;
+	bool m_touchFading;
+	int m_fadeStep;
 };
 
 /* ----------------------------------------------------------------- */