[Scummvm-git-logs] scummvm master -> 50184ef27df59e562824c99c0b1abf7a35c700d1
sev-
noreply at scummvm.org
Mon Jul 13 11:35:44 UTC 2026
This automated email contains information about 12 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
fa93cdd093 COMMON: Add toggle-hotspots event and standard action
61db0bf14d BACKENDS: Handle toggle-hotspots keymapper action
d0f6830e64 GRAPHICS: Add hotspot overlay renderer
e2532bdc15 ENGINES: Add hotspot display infrastructure
c8c6bcf85d GUI: Add hotspot options to Game Options
496a9a65f5 GUI: Add hotspot theme layout and regenerate themes
62a9809ead TEST: Add hotspot unit tests
0ac8afb5b5 DOCS: Document hotspot display feature
258a839452 TOUCHE: Add hotspot display support
0379edd771 GRAPHICS: Scale hotspot overlay and support 32-bit backends
a1bba7b319 ENGINES: Hide mouse cursor while hotspots overlay is shown
50184ef27d COMMON: Drive hotspot display with a start/end event pair
Commit: fa93cdd093cf594f429b1dd817fe4d4962eccfca
https://github.com/scummvm/scummvm/commit/fa93cdd093cf594f429b1dd817fe4d4962eccfca
Author: Mariusz Górski (takashivip at gmail.com)
Date: 2026-07-13T13:35:35+02:00
Commit Message:
COMMON: Add toggle-hotspots event and standard action
Assisted-by: Claude:claude-opus-4.8
Changed paths:
common/events.h
diff --git a/common/events.h b/common/events.h
index 60b9d03c774..6e69117d176 100644
--- a/common/events.h
+++ b/common/events.h
@@ -117,6 +117,9 @@ enum EventType {
EVENT_FOCUS_GAINED = 36,
EVENT_FOCUS_LOST = 37,
+ /** Toggle hotspot display. */
+ EVENT_TOGGLE_HOTSPOTS = 38,
+
/**
* We reserve some event ids for custom events.
*
@@ -129,6 +132,15 @@ enum EventType {
EVENT_USER_LAST_AVAILABLE = 9999
};
+/**
+ * Custom engine action types for EVENT_CUSTOM_ENGINE_ACTION_START/END.
+ * These are used by cross-engine features. Values start at 20000 to avoid
+ * clashing with engine-specific custom action ids, which start from 0.
+ */
+enum EngineAction {
+ kEngineActionHotspotToggle = 20000
+};
+
const int16 JOYAXIS_MIN = -32768;
const int16 JOYAXIS_MAX = 32767;
Commit: 61db0bf14dc0b521b58bf457264f81de8c287e4d
https://github.com/scummvm/scummvm/commit/61db0bf14dc0b521b58bf457264f81de8c287e4d
Author: Mariusz Górski (takashivip at gmail.com)
Date: 2026-07-13T13:35:35+02:00
Commit Message:
BACKENDS: Handle toggle-hotspots keymapper action
Assisted-by: Claude:claude-opus-4.8
Changed paths:
backends/events/default/default-events.cpp
backends/keymapper/standard-actions.cpp
backends/keymapper/standard-actions.h
diff --git a/backends/events/default/default-events.cpp b/backends/events/default/default-events.cpp
index 4a02bb270c2..056aef9124a 100644
--- a/backends/events/default/default-events.cpp
+++ b/backends/events/default/default-events.cpp
@@ -120,6 +120,22 @@ bool DefaultEventManager::pollEvent(Common::Event &event) {
_modifierState = event.kbd.flags;
break;
+ case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
+ if (event.customType == Common::kEngineActionHotspotToggle && g_engine) {
+ if (ConfMan.getBool("enable_hotspots")) {
+ g_engine->showHotspots(true);
+ }
+ }
+ break;
+
+ case Common::EVENT_CUSTOM_ENGINE_ACTION_END:
+ if (event.customType == Common::kEngineActionHotspotToggle && g_engine) {
+ if (ConfMan.getBool("enable_hotspots")) {
+ g_engine->showHotspots(false);
+ }
+ }
+ break;
+
case Common::EVENT_MOUSEMOVE:
_mousePos = event.mouse;
break;
diff --git a/backends/keymapper/standard-actions.cpp b/backends/keymapper/standard-actions.cpp
index 138a7794288..ca92b3e6109 100644
--- a/backends/keymapper/standard-actions.cpp
+++ b/backends/keymapper/standard-actions.cpp
@@ -41,5 +41,6 @@ const char *const kStandardActionEE = "EEKY";
const char *const kStandardActionCut = "CUT";
const char *const kStandardActionCopy = "COPY";
const char *const kStandardActionPaste = "PASTE";
+const char *const kStandardActionToggleHotspots = "HOTS";
} //namespace Common
diff --git a/backends/keymapper/standard-actions.h b/backends/keymapper/standard-actions.h
index 23555d29e57..e7dd7ca1b1b 100644
--- a/backends/keymapper/standard-actions.h
+++ b/backends/keymapper/standard-actions.h
@@ -55,6 +55,7 @@ extern const char *const kStandardActionEE;
extern const char *const kStandardActionCut;
extern const char *const kStandardActionCopy;
extern const char *const kStandardActionPaste;
+extern const char *const kStandardActionToggleHotspots;
} //namespace Common
Commit: d0f6830e64d9f96e3a7c0de15b72bc40f287f754
https://github.com/scummvm/scummvm/commit/d0f6830e64d9f96e3a7c0de15b72bc40f287f754
Author: Mariusz Górski (takashivip at gmail.com)
Date: 2026-07-13T13:35:35+02:00
Commit Message:
GRAPHICS: Add hotspot overlay renderer
Assisted-by: Claude:claude-opus-4.8
Changed paths:
A graphics/hotspot_renderer.cpp
A graphics/hotspot_renderer.h
graphics/module.mk
diff --git a/graphics/hotspot_renderer.cpp b/graphics/hotspot_renderer.cpp
new file mode 100644
index 00000000000..b8d42a16cb5
--- /dev/null
+++ b/graphics/hotspot_renderer.cpp
@@ -0,0 +1,259 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "graphics/hotspot_renderer.h"
+#include "common/util.h"
+#include "graphics/font.h"
+#include "graphics/fontman.h"
+#include "graphics/pixelformat.h"
+#include "graphics/surface.h"
+
+namespace Graphics {
+
+HotspotRenderer::HotspotRenderer() {
+}
+
+HotspotRenderer::~HotspotRenderer() {
+}
+
+void HotspotRenderer::render(Surface *surface,
+ const Common::Array<HotspotInfo> &hotspots,
+ int gameWidth, int gameHeight,
+ int overlayWidth, int overlayHeight,
+ const PixelFormat &format,
+ MarkerShape markerShape,
+ bool showText) {
+ if (!surface || hotspots.empty())
+ return;
+
+ const Font *font = nullptr;
+ if (showText) {
+ font = FontMan.getFontByUsage(FontManager::kGUIFont);
+ }
+
+ float scaleX = (float)overlayWidth / (float)gameWidth;
+ float scaleY = (float)overlayHeight / (float)gameHeight;
+
+ for (uint i = 0; i < hotspots.size(); i++) {
+ int gameX = hotspots[i].position.x;
+ int gameY = hotspots[i].position.y;
+
+ int overlayX = (int)(gameX * scaleX);
+ int overlayY = (int)(gameY * scaleY);
+
+ drawMarker(surface, overlayX, overlayY, format, markerShape);
+
+ if (font && !hotspots[i].name.empty()) {
+ drawHotspotLabel(surface, overlayX, overlayY, hotspots[i].name,
+ overlayWidth, overlayHeight, format, font);
+ }
+ }
+}
+
+void HotspotRenderer::drawMarker(Surface *surface, int x, int y, const PixelFormat &format, MarkerShape markerShape) {
+ int width = surface->w;
+ int height = surface->h;
+
+ switch (markerShape) {
+ case kMarkerCrosshair:
+ drawCrosshairMarker(surface, x, y, width, height, format);
+ break;
+ case kMarkerSquare:
+ drawSquareMarker(surface, x, y, width, height, format);
+ break;
+ case kMarkerPoint:
+ drawPointMarker(surface, x, y, width, height, format);
+ break;
+ }
+}
+
+void HotspotRenderer::drawHotspotLabel(Surface *surface, int overlayX, int overlayY,
+ const Common::U32String &label, int overlayWidth, int overlayHeight,
+ const PixelFormat &format, const Font *font) {
+ int textX = overlayX + kMarkerSize / 2 + 8;
+ int textY = overlayY - font->getFontHeight() / 2;
+
+ int textWidth = font->getStringWidth(label);
+ int textHeight = font->getFontHeight();
+
+ int bgX = textX - 4;
+ int bgY = textY - 2;
+ int bgW = textWidth + 8;
+ int bgH = textHeight + 4;
+
+ drawLabelBox(surface, bgX, bgY, bgW, bgH,
+ overlayWidth, overlayHeight, format);
+
+ uint32 textColor = format.RGBToColor(255, 255, 255);
+ font->drawString(surface, label, textX, textY,
+ overlayWidth - textX, textColor, kTextAlignLeft);
+}
+
+void HotspotRenderer::drawCrosshairMarker(Surface *surface, int x, int y,
+ int width, int height, const PixelFormat &format) {
+ if (format.bytesPerPixel != 2)
+ return;
+
+ const int lineLength = (kMarkerSize / 2) - 1;
+
+ drawLineWithGlow(surface, x - lineLength, y, x + lineLength, y, width, height, format, kLineThickness);
+ drawLineWithGlow(surface, x, y - lineLength, x, y + lineLength, width, height, format, kLineThickness);
+}
+
+void HotspotRenderer::drawSquareMarker(Surface *surface, int x, int y,
+ int width, int height, const PixelFormat &format) {
+ if (format.bytesPerPixel != 2)
+ return;
+
+ int size = kMarkerSize;
+ int rectX = x - kMarkerSize / 2;
+ int rectY = y - kMarkerSize / 2;
+
+ drawRectWithGlow(surface, rectX, rectY, size, size, width, height, format);
+}
+
+void HotspotRenderer::drawPointMarker(Surface *surface, int x, int y,
+ int width, int height, const PixelFormat &format) {
+ if (format.bytesPerPixel != 2)
+ return;
+
+ int maxRadius = kPointRadius + kGlowSize;
+
+ for (int dy = -maxRadius; dy <= maxRadius; dy++) {
+ for (int dx = -maxRadius; dx <= maxRadius; dx++) {
+ int px = x + dx;
+ int py = y + dy;
+
+ if (px < 0 || px >= width || py < 0 || py >= height)
+ continue;
+
+ int distSq = dx * dx + dy * dy;
+ int dist = (int)(sqrtf((float)distSq) + 0.5f);
+
+ blendPixelWithGlow(surface, px, py, format, dist, kPointRadius);
+ }
+ }
+}
+
+void HotspotRenderer::blendPixelWithGlow(Surface *surface, int px, int py,
+ const PixelFormat &format, int distance, int solidSize) {
+ uint16 *destPixel = (uint16 *)surface->getBasePtr(px, py);
+ byte bgR, bgG, bgB, bgA;
+ format.colorToARGB(*destPixel, bgA, bgR, bgG, bgB);
+
+ // Convert solidSize to maximum solid distance (solidSize=1 means center pixel only, distance=0)
+ int maxSolidDistance = solidSize - 1;
+
+ if (distance <= maxSolidDistance) {
+ const int solidBeta = 180;
+ const int solidAlpha = 255 - solidBeta;
+ byte r = ((255 * solidBeta) + (bgR * solidAlpha)) / 255;
+ byte g = ((255 * solidBeta) + (bgG * solidAlpha)) / 255;
+ byte b = ((255 * solidBeta) + (bgB * solidAlpha)) / 255;
+ *destPixel = format.RGBToColor(r, g, b);
+ } else if (distance <= maxSolidDistance + kGlowSize) {
+ int glowDist = distance - maxSolidDistance;
+ int alpha = ((kGlowSize - glowDist) * 80) / kGlowSize;
+ byte r = ((255 * alpha) + (bgR * (255 - alpha))) / 255;
+ byte g = ((255 * alpha) + (bgG * (255 - alpha))) / 255;
+ byte b = ((255 * alpha) + (bgB * (255 - alpha))) / 255;
+ *destPixel = format.RGBToColor(r, g, b);
+ }
+}
+
+void HotspotRenderer::drawLineWithGlow(Surface *surface, int x1, int y1, int x2, int y2,
+ int width, int height, const PixelFormat &format, int lineThickness) {
+ if (format.bytesPerPixel != 2)
+ return;
+
+ bool isHorizontal = (y1 == y2);
+ bool isVertical = (x1 == x2);
+
+ if (!isHorizontal && !isVertical)
+ return;
+
+ int alongMin, alongMax, acrossFixed;
+
+ if (isHorizontal) {
+ alongMin = MIN(x1, x2);
+ alongMax = MAX(x1, x2);
+ acrossFixed = y1;
+ } else {
+ alongMin = MIN(y1, y2);
+ alongMax = MAX(y1, y2);
+ acrossFixed = x1;
+ }
+
+ for (int along = alongMin; along <= alongMax; along++) {
+ for (int thickness = -lineThickness - kGlowSize; thickness <= lineThickness + kGlowSize; thickness++) {
+ int px, py;
+ if (isHorizontal) {
+ px = along;
+ py = acrossFixed + thickness;
+ } else {
+ px = acrossFixed + thickness;
+ py = along;
+ }
+
+ if (px < 0 || px >= width || py < 0 || py >= height)
+ continue;
+
+ int distFromCenter = ABS(thickness);
+ blendPixelWithGlow(surface, px, py, format, distFromCenter, lineThickness);
+ }
+ }
+}
+
+void HotspotRenderer::drawRectWithGlow(Surface *surface, int x, int y, int w, int h,
+ int overlayWidth, int overlayHeight, const PixelFormat &format) {
+ drawLineWithGlow(surface, x, y, x + w - 1, y, overlayWidth, overlayHeight, format, kLineThickness);
+ drawLineWithGlow(surface, x, y + h - 1, x + w - 1, y + h - 1,
+ overlayWidth, overlayHeight, format, kLineThickness);
+ drawLineWithGlow(surface, x, y + 1, x, y + h - 2, overlayWidth, overlayHeight, format, kLineThickness);
+ drawLineWithGlow(surface, x + w - 1, y + 1, x + w - 1, y + h - 2,
+ overlayWidth, overlayHeight, format, kLineThickness);
+}
+
+void HotspotRenderer::drawLabelBox(Surface *surface, int x, int y, int w, int h,
+ int overlayWidth, int overlayHeight, const PixelFormat &format) {
+ if (format.bytesPerPixel != 2)
+ return;
+
+ for (int py = y + 1; py < y + h - 1; py++) {
+ for (int px = x + 1; px < x + w - 1; px++) {
+ if (px < 0 || px >= overlayWidth || py < 0 || py >= overlayHeight)
+ continue;
+
+ uint16 *destPixel = (uint16 *)surface->getBasePtr(px, py);
+ byte bgR, bgG, bgB, bgA;
+ format.colorToARGB(*destPixel, bgA, bgR, bgG, bgB);
+
+ byte r = (bgR * 40) / 100;
+ byte g = (bgG * 40) / 100;
+ byte b = (bgB * 40) / 100;
+ *destPixel = format.RGBToColor(r, g, b);
+ }
+ }
+
+ drawRectWithGlow(surface, x, y, w, h, overlayWidth, overlayHeight, format);
+}
+
+} // End of namespace Graphics
diff --git a/graphics/hotspot_renderer.h b/graphics/hotspot_renderer.h
new file mode 100644
index 00000000000..adb5b038b20
--- /dev/null
+++ b/graphics/hotspot_renderer.h
@@ -0,0 +1,125 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef GRAPHICS_HOTSPOT_RENDERER_H
+#define GRAPHICS_HOTSPOT_RENDERER_H
+
+#include "common/array.h"
+#include "common/rect.h"
+#include "common/ustr.h"
+
+namespace Graphics {
+
+class Font;
+class Surface;
+struct PixelFormat;
+
+/**
+ * Classification of a hotspot by its role in the game world.
+ */
+enum HotspotType {
+ kHotspotDefault, ///< No specific type (generic / unclassified)
+ kHotspotObject, ///< Interactable object
+ kHotspotExit, ///< Exit or door leading to another area
+ kHotspotNPC ///< Non-player character
+};
+
+/**
+ * Information about a single hotspot to display.
+ */
+struct HotspotInfo {
+ Common::Point position; ///< Position in game coordinates
+ Common::U32String name; ///< Display name
+ HotspotType type; ///< Classification of the hotspot
+
+ HotspotInfo() : type(kHotspotDefault) {}
+ HotspotInfo(const Common::Point &pos, const Common::U32String &n,
+ HotspotType t = kHotspotDefault)
+ : position(pos), name(n), type(t) {}
+};
+
+/**
+ * Marker shape types.
+ */
+enum MarkerShape {
+ kMarkerCrosshair,
+ kMarkerSquare,
+ kMarkerPoint
+};
+
+/**
+ * Renderer for drawing hotspot markers on overlay surface.
+ */
+class HotspotRenderer {
+public:
+ HotspotRenderer();
+ ~HotspotRenderer();
+
+ /**
+ * Render hotspot markers on an overlay surface.
+ *
+ * @param surface The overlay surface to draw on
+ * @param hotspots Array of hotspot information
+ * @param gameWidth Width of game screen
+ * @param gameHeight Height of game screen
+ * @param overlayWidth Width of overlay
+ * @param overlayHeight Height of overlay
+ * @param format Pixel format of overlay
+ * @param markerShape Type of marker to display
+ * @param showText Whether to display text labels
+ */
+ void render(Surface *surface,
+ const Common::Array<HotspotInfo> &hotspots,
+ int gameWidth, int gameHeight,
+ int overlayWidth, int overlayHeight,
+ const PixelFormat &format,
+ MarkerShape markerShape,
+ bool showText);
+
+private:
+ enum {
+ kMarkerSize = 10, ///< Size of square and crosshair markers
+ kGlowSize = 3, ///< Size of glow effect around markers and text
+ kPointRadius = 3, ///< Radius of point marker
+ kLineThickness = 1 ///< Thickness of lines for markers and text boxes
+ };
+
+ void drawMarker(Surface *surface, int x, int y, const PixelFormat &format, MarkerShape markerShape);
+ void drawCrosshairMarker(Surface *surface, int x, int y, int width, int height, const PixelFormat &format);
+ void drawSquareMarker(Surface *surface, int x, int y, int width, int height, const PixelFormat &format);
+ void drawPointMarker(Surface *surface, int x, int y, int width, int height, const PixelFormat &format);
+ void drawHotspotLabel(Surface *surface, int overlayX, int overlayY, const Common::U32String &label,
+ int overlayWidth, int overlayHeight, const PixelFormat &format, const Font *font);
+ void drawLabelBox(Surface *surface, int x, int y, int w, int h,
+ int overlayWidth, int overlayHeight, const PixelFormat &format);
+ void drawRectWithGlow(Surface *surface, int x, int y, int w, int h,
+ int overlayWidth, int overlayHeight, const PixelFormat &format);
+
+ void drawLineWithGlow(Surface *surface, int x1, int y1, int x2, int y2,
+ int width, int height, const PixelFormat &format, int lineThickness);
+
+ void blendPixelWithGlow(Surface *surface, int px, int py, const PixelFormat &format,
+ int distance, int solidSize);
+};
+
+} // End of namespace Graphics
+
+#endif // GRAPHICS_HOTSPOT_RENDERER_H
diff --git a/graphics/module.mk b/graphics/module.mk
index 04725fcaaa3..d96f720c89c 100644
--- a/graphics/module.mk
+++ b/graphics/module.mk
@@ -24,6 +24,7 @@ MODULE_OBJS := \
fonts/ttf.o \
fonts/winfont.o \
framelimiter.o \
+ hotspot_renderer.o \
image-archive.o \
korfont.o \
larryScale.o \
Commit: e2532bdc150563869e38caf3625ac8b0a2d11b53
https://github.com/scummvm/scummvm/commit/e2532bdc150563869e38caf3625ac8b0a2d11b53
Author: Mariusz Górski (takashivip at gmail.com)
Date: 2026-07-13T13:35:35+02:00
Commit Message:
ENGINES: Add hotspot display infrastructure
Assisted-by: Claude:claude-opus-4.8
Changed paths:
engines/engine.cpp
engines/engine.h
engines/metaengine.cpp
diff --git a/engines/engine.cpp b/engines/engine.cpp
index 5ab92309d10..aaa7a7870e6 100644
--- a/engines/engine.cpp
+++ b/engines/engine.cpp
@@ -60,6 +60,8 @@
#include "graphics/fontman.h"
#include "graphics/paletteman.h"
#include "graphics/pixelformat.h"
+#include "graphics/font.h"
+#include "graphics/hotspot_renderer.h"
#include "image/bmp.h"
#include "common/text-to-speech.h"
@@ -154,7 +156,9 @@ Engine::Engine(OSystem *syst)
_mainMenuDialog(NULL),
_debugger(NULL),
_autosaveInterval(ConfMan.getInt("autosave_period")),
- _lastAutosaveTime(_system->getMillis()) {
+ _lastAutosaveTime(_system->getMillis()),
+ _showHotspots(false),
+ _hotspotForceRedraw(false) {
g_engine = this;
_quitRequested = false;
@@ -742,6 +746,66 @@ void Engine::pauseEngineIntern(bool pause) {
_mixer->pauseAll(pause);
}
+void Engine::showHotspots(bool show) {
+ _showHotspots = show;
+
+ if (show)
+ _hotspotForceRedraw = true;
+ else if (g_system->isOverlayVisible())
+ g_system->hideOverlay();
+}
+
+void Engine::getHotspotPositions(Common::Array<Graphics::HotspotInfo> &hotspots) {
+}
+
+bool Engine::hotspotDirty() const {
+ return true;
+}
+
+void Engine::drawHotspots() {
+ if (!_showHotspots)
+ return;
+
+ if (!hotspotDirty() && !_hotspotForceRedraw)
+ return;
+ _hotspotForceRedraw = false;
+
+ Common::Array<Graphics::HotspotInfo> hotspots;
+ getHotspotPositions(hotspots);
+
+ if (hotspots.empty())
+ return;
+
+ int16 gameWidth = g_system->getWidth();
+ int16 gameHeight = g_system->getHeight();
+ int16 overlayWidth = g_system->getOverlayWidth();
+ int16 overlayHeight = g_system->getOverlayHeight();
+ Graphics::PixelFormat overlayFormat = g_system->getOverlayFormat();
+
+ if (!g_system->isOverlayVisible())
+ g_system->showOverlay(false);
+
+ g_system->clearOverlay();
+
+ Graphics::Surface overlayBuffer;
+ overlayBuffer.create(overlayWidth, overlayHeight, overlayFormat);
+ g_system->grabOverlay(overlayBuffer);
+
+ bool showText = ConfMan.getBool("show_hotspot_text");
+ if (!ConfMan.hasKey("show_hotspot_text"))
+ showText = true;
+
+ int markerType = ConfMan.getInt("hotspot_marker");
+
+ Graphics::HotspotRenderer renderer;
+ renderer.render(&overlayBuffer, hotspots, gameWidth, gameHeight,
+ overlayWidth, overlayHeight, overlayFormat,
+ (Graphics::MarkerShape)markerType, showText);
+
+ g_system->copyRectToOverlay(overlayBuffer.getPixels(), overlayBuffer.pitch, 0, 0, overlayWidth, overlayHeight);
+ overlayBuffer.free();
+}
+
void Engine::openMainMenuDialog() {
if (!_mainMenuDialog)
_mainMenuDialog = new MainMenuDialog(this);
diff --git a/engines/engine.h b/engines/engine.h
index fccc2efff5d..73508b474d7 100644
--- a/engines/engine.h
+++ b/engines/engine.h
@@ -22,7 +22,9 @@
#ifndef ENGINES_ENGINE_H
#define ENGINES_ENGINE_H
+#include "common/array.h"
#include "common/error.h"
+#include "common/rect.h"
#include "common/scummsys.h"
#include "common/str.h"
#include "common/language.h"
@@ -52,6 +54,9 @@ namespace GUI {
class Debugger;
class Dialog;
}
+namespace Graphics {
+struct HotspotInfo;
+}
/**
* @defgroup engines_engine Engine
@@ -182,6 +187,18 @@ protected:
int32 _activeEnhancements = kEnhGameBreakingBugFixes;
+ /**
+ * Flag for whether hotspots should be displayed
+ */
+ bool _showHotspots;
+
+ /**
+ * Flag to force one redraw after hotspots are shown (e.g. after button press
+ * re-enables the overlay), so engines that cache dirty state don't skip the
+ * first render after the overlay was hidden.
+ */
+ bool _hotspotForceRedraw;
+
private:
/**
* The associated metaengine
@@ -509,11 +526,48 @@ protected:
*/
virtual void pauseEngineIntern(bool pause);
+ /**
+ * Get information about all hotspots that should be displayed.
+ *
+ * Engines should override this method to populate the hotspots array
+ * with hotspot information in game screen coordinates.
+ *
+ * @param hotspots Array to be filled with hotspot information
+ */
+ virtual void getHotspotPositions(Common::Array<Graphics::HotspotInfo> &hotspots);
+
+ /**
+ * Draw hotspot markers on the screen using the overlay.
+ *
+ * Default implementation calls getHotspotPositions() and draws semi-transparent
+ * circles, automatically converting from game coordinates to overlay coordinates.
+ */
+ virtual void drawHotspots();
+
+ /**
+ * Returns whether hotspot markers need to be re-rendered this frame.
+ *
+ * The default implementation always returns true. Engines may override
+ * this to return false when hotspot positions have not changed (e.g. no
+ * object state change, no screen pan), avoiding an unnecessary overlay
+ * redraw.
+ *
+ * @return true if hotspots should be re-rendered, false to skip
+ */
+ virtual bool hotspotDirty() const;
+
/** @} */
public:
+ /**
+ * Set whether hotspots should be displayed.
+ *
+ * @param show true to show hotspots, false to hide them
+ */
+ virtual void showHotspots(bool show);
+
/**
* Request the engine to quit.
*
diff --git a/engines/metaengine.cpp b/engines/metaengine.cpp
index 5a2bfb0f09b..f98ae29bdcb 100644
--- a/engines/metaengine.cpp
+++ b/engines/metaengine.cpp
@@ -29,6 +29,7 @@
#include "common/savefile.h"
#include "common/system.h"
#include "common/translation.h"
+#include "common/events.h"
#include "engines/dialogs.h"
@@ -158,6 +159,11 @@ Common::KeymapArray MetaEngine::initKeymaps(const char *target) const {
act->allowKbdRepeats();
engineKeyMap->addAction(act);
+ act = new Action(kStandardActionToggleHotspots, _("Show hotspots"));
+ act->setCustomEngineActionEvent(Common::kEngineActionHotspotToggle);
+ act->addDefaultInputMapping("h");
+ engineKeyMap->addAction(act);
+
return Keymap::arrayOf(engineKeyMap);
}
Commit: c8c6bcf85da1a13a52a8e12f0006b72b46307d5c
https://github.com/scummvm/scummvm/commit/c8c6bcf85da1a13a52a8e12f0006b72b46307d5c
Author: Mariusz Górski (takashivip at gmail.com)
Date: 2026-07-13T13:35:35+02:00
Commit Message:
GUI: Add hotspot options to Game Options
Assisted-by: Claude:claude-opus-4.8
Changed paths:
gui/editgamedialog.cpp
gui/editgamedialog.h
diff --git a/gui/editgamedialog.cpp b/gui/editgamedialog.cpp
index 79bb1d30a35..d21ccc7e184 100644
--- a/gui/editgamedialog.cpp
+++ b/gui/editgamedialog.cpp
@@ -22,6 +22,7 @@
#include "gui/editgamedialog.h"
#include "backends/keymapper/keymapper.h"
+#include "graphics/hotspot_renderer.h"
#include "common/config-manager.h"
#include "common/gui_options.h"
@@ -79,7 +80,8 @@ enum {
kCmdSavePathClear = 'PSAC',
kCmdCheckIntegrity = 'PCHI',
- kGraphicsTabContainerReflowCmd = 'gtcr'
+ kGraphicsTabContainerReflowCmd = 'gtcr',
+ kEnableHotspotsCmd = 'enhs',
};
class DomainEditTextWidget : public EditTextWidget {
@@ -317,7 +319,46 @@ EditGameDialog::EditGameDialog(const Common::String &domain)
_savePathClearButton = addClearButton(tab, "GameOptions_Paths.SavePathClearButton", kCmdSavePathClear);
//
- // 9) The Achievements & The Statistics tabs
+ // 9) The Misc tab
+ //
+ tab->addTab(g_gui.useLowResGUI() ? _c("Misc", "lowres") : _("Misc"), "GameOptions_Misc");
+
+ ScrollContainerWidget *miscContainer = new ScrollContainerWidget(tab,
+ "GameOptions_Misc.Container", "GameOptions_Misc_Container");
+ miscContainer->setBackgroundType(ThemeEngine::kWidgetBackgroundNo);
+ miscContainer->setTarget(this);
+
+ bool hotspotsEnabled = ConfMan.getBool("enable_hotspots", _domain);
+ _enableHotspotsCheckbox = new CheckboxWidget(miscContainer, "GameOptions_Misc_Container.EnableHotspots",
+ _("Enable hotspot display"),
+ _("Enable visual markers and labels for interactive objects"),
+ kEnableHotspotsCmd
+ );
+
+ _hotspotMarkerPopUpDesc = new StaticTextWidget(miscContainer,
+ "GameOptions_Misc_Container.HotspotMarkerPopupDesc", _("Hotspot marker:"));
+ _hotspotMarkerPopUp = new PopUpWidget(miscContainer, "GameOptions_Misc_Container.HotspotMarkerPopup");
+ _hotspotMarkerPopUp->appendEntry(_("Point"), Graphics::kMarkerPoint);
+ _hotspotMarkerPopUp->appendEntry(_("Square"), Graphics::kMarkerSquare);
+ _hotspotMarkerPopUp->appendEntry(_("Crosshair"), Graphics::kMarkerCrosshair);
+ _hotspotMarkerPopUp->setSelectedTag(ConfMan.getInt("hotspot_marker", _domain));
+ _hotspotMarkerPopUp->setEnabled(hotspotsEnabled);
+ _hotspotMarkerPopUpDesc->setEnabled(hotspotsEnabled);
+
+ bool showHotspotText = ConfMan.getBool("show_hotspot_text", _domain);
+ if (!ConfMan.hasKey("show_hotspot_text", _domain))
+ showHotspotText = true;
+ _showHotspotTextCheckbox = new CheckboxWidget(miscContainer, "GameOptions_Misc_Container.ShowHotspotText",
+ _("Show hotspot labels"),
+ _("Display text labels next to hotspot markers")
+ );
+ _showHotspotTextCheckbox->setState(showHotspotText);
+ _showHotspotTextCheckbox->setEnabled(hotspotsEnabled);
+
+ _enableHotspotsCheckbox->setState(hotspotsEnabled);
+
+ //
+ // 10) The Achievements & The Statistics tabs
//
if (enginePlugin) {
const MetaEngine &metaEngine = enginePlugin->get<MetaEngine>();
@@ -529,11 +570,23 @@ void EditGameDialog::apply() {
_engineOptions->save();
}
+ ConfMan.setBool("enable_hotspots", _enableHotspotsCheckbox->getState(), _domain);
+ ConfMan.setInt("hotspot_marker", _hotspotMarkerPopUp->getSelectedTag(), _domain);
+ ConfMan.setBool("show_hotspot_text", _showHotspotTextCheckbox->getState(), _domain);
+
OptionsDialog::apply();
}
void EditGameDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
switch (cmd) {
+ case kEnableHotspotsCmd: {
+ bool enabled = _enableHotspotsCheckbox->getState();
+ _hotspotMarkerPopUpDesc->setEnabled(enabled);
+ _hotspotMarkerPopUp->setEnabled(enabled);
+ _showHotspotTextCheckbox->setEnabled(enabled);
+ g_gui.scheduleTopDialogRedraw();
+ break;
+ }
case kCmdGlobalGraphicsOverride:
setGraphicSettingsState(data != 0);
g_gui.scheduleTopDialogRedraw();
diff --git a/gui/editgamedialog.h b/gui/editgamedialog.h
index 0dc95c8a838..aec6ba4ddd0 100644
--- a/gui/editgamedialog.h
+++ b/gui/editgamedialog.h
@@ -91,6 +91,11 @@ protected:
CheckboxWidget *_globalMT32Override;
CheckboxWidget *_globalVolumeOverride;
+ CheckboxWidget *_enableHotspotsCheckbox;
+ StaticTextWidget *_hotspotMarkerPopUpDesc;
+ PopUpWidget *_hotspotMarkerPopUp;
+ CheckboxWidget *_showHotspotTextCheckbox;
+
ScrollContainerWidget *_gameContainer;
OptionsContainerWidget *_engineOptions;
};
Commit: 496a9a65f5c896736044f900538b736d9696242c
https://github.com/scummvm/scummvm/commit/496a9a65f5c896736044f900538b736d9696242c
Author: Mariusz Górski (takashivip at gmail.com)
Date: 2026-07-13T13:35:35+02:00
Commit Message:
GUI: Add hotspot theme layout and regenerate themes
Assisted-by: Claude:claude-opus-4.8
Changed paths:
gui/ThemeEngine.h
gui/themes/common/highres_layout.stx
gui/themes/common/lowres_layout.stx
gui/themes/default.inc
gui/themes/residualvm.zip
gui/themes/residualvm/THEMERC
gui/themes/scummclassic.zip
gui/themes/scummclassic/THEMERC
gui/themes/scummclassic/classic_layout.stx
gui/themes/scummclassic/classic_layout_lowres.stx
gui/themes/scummmodern.zip
gui/themes/scummmodern/THEMERC
gui/themes/scummremastered.zip
gui/themes/scummremastered/THEMERC
diff --git a/gui/ThemeEngine.h b/gui/ThemeEngine.h
index 48d47c5d176..75a61fd1f4a 100644
--- a/gui/ThemeEngine.h
+++ b/gui/ThemeEngine.h
@@ -36,7 +36,7 @@
#include "graphics/pixelformat.h"
-#define SCUMMVM_THEME_VERSION_STR "SCUMMVM_STX0.9.23"
+#define SCUMMVM_THEME_VERSION_STR "SCUMMVM_STX0.9.24"
class OSystem;
diff --git a/gui/themes/common/highres_layout.stx b/gui/themes/common/highres_layout.stx
index c8f8d872fd2..96623302e63 100644
--- a/gui/themes/common/highres_layout.stx
+++ b/gui/themes/common/highres_layout.stx
@@ -1876,6 +1876,37 @@
</layout>
</dialog>
+ <dialog name = 'GameOptions_Misc' overlays = 'Dialog.GameOptions.TabWidget'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'
+ type = 'ScrollContainerWidget'
+ />
+ </layout>
+ </dialog>
+
+ <dialog name = 'GameOptions_Misc_Container' overlays = 'GameOptions_Misc.Container' shading = 'dim'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' align = 'center'>
+ <widget name = 'EnableHotspots'
+ type = 'Checkbox'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' align = 'center'>
+ <widget name = 'HotspotMarkerPopupDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'HotspotMarkerPopup'
+ type = 'PopUp'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' align = 'center'>
+ <widget name = 'ShowHotspotText'
+ type = 'Checkbox'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name = 'GlobalMenu' overlays = 'screen_center'>
<layout type = 'vertical' padding = '16, 16, 16, 16' align = 'center'>
<widget name = 'Logo'
diff --git a/gui/themes/common/lowres_layout.stx b/gui/themes/common/lowres_layout.stx
index ae6311cebe3..1ec690bcca1 100644
--- a/gui/themes/common/lowres_layout.stx
+++ b/gui/themes/common/lowres_layout.stx
@@ -1703,6 +1703,37 @@
</layout>
</dialog>
+ <dialog name = 'GameOptions_Misc' overlays = 'Dialog.GameOptions.TabWidget'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'
+ type = 'ScrollContainerWidget'
+ />
+ </layout>
+ </dialog>
+
+ <dialog name = 'GameOptions_Misc_Container' overlays = 'GameOptions_Misc.Container' shading = 'dim'>
+ <layout type = 'vertical' padding = '8, 8, 8, 8' spacing = '6'>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' align = 'center'>
+ <widget name = 'EnableHotspots'
+ type = 'Checkbox'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' align = 'center'>
+ <widget name = 'HotspotMarkerPopupDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'HotspotMarkerPopup'
+ type = 'PopUp'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' align = 'center'>
+ <widget name = 'ShowHotspotText'
+ type = 'Checkbox'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name = 'GlobalMenu' overlays = 'screen_center'>
<layout type = 'vertical' padding = '4, 4, 4, 4' align = 'center' spacing='2'>
<widget name = 'Title'
diff --git a/gui/themes/default.inc b/gui/themes/default.inc
index 49ef03e6c50..2cbdbe96dd8 100644
--- a/gui/themes/default.inc
+++ b/gui/themes/default.inc
@@ -2842,6 +2842,35 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"/>"
"</layout>"
"</dialog>"
+"<dialog name='GameOptions_Misc' overlays='Dialog.GameOptions.TabWidget'>"
+"<layout type='vertical' padding='0,0,0,0'>"
+"<widget name='Container' "
+"type='ScrollContainerWidget' "
+"/>"
+"</layout>"
+"</dialog>"
+"<dialog name='GameOptions_Misc_Container' overlays='GameOptions_Misc.Container' shading='dim'>"
+"<layout type='vertical' padding='16,16,16,16' spacing='8'>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' align='center'>"
+"<widget name='EnableHotspots' "
+"type='Checkbox' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' align='center'>"
+"<widget name='HotspotMarkerPopupDesc' "
+"type='OptionsLabel' "
+"/>"
+"<widget name='HotspotMarkerPopup' "
+"type='PopUp' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' align='center'>"
+"<widget name='ShowHotspotText' "
+"type='Checkbox' "
+"/>"
+"</layout>"
+"</layout>"
+"</dialog>"
"<dialog name='GlobalMenu' overlays='screen_center'>"
"<layout type='vertical' padding='16,16,16,16' align='center'>"
"<widget name='Title' "
@@ -5321,6 +5350,35 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"/>"
"</layout>"
"</dialog>"
+"<dialog name='GameOptions_Misc' overlays='Dialog.GameOptions.TabWidget'>"
+"<layout type='vertical' padding='0,0,0,0'>"
+"<widget name='Container' "
+"type='ScrollContainerWidget' "
+"/>"
+"</layout>"
+"</dialog>"
+"<dialog name='GameOptions_Misc_Container' overlays='GameOptions_Misc.Container' shading='dim'>"
+"<layout type='vertical' padding='8,8,8,8' spacing='6'>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' align='center'>"
+"<widget name='EnableHotspots' "
+"type='Checkbox' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' align='center'>"
+"<widget name='HotspotMarkerPopupDesc' "
+"type='OptionsLabel' "
+"/>"
+"<widget name='HotspotMarkerPopup' "
+"type='PopUp' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' align='center'>"
+"<widget name='ShowHotspotText' "
+"type='Checkbox' "
+"/>"
+"</layout>"
+"</layout>"
+"</dialog>"
"<dialog name='GlobalMenu' overlays='screen_center'>"
"<layout type='vertical' padding='2,2,2,6' align='center' spacing='0'>"
"<widget name='Title' "
diff --git a/gui/themes/residualvm.zip b/gui/themes/residualvm.zip
index 0d57f6e477a..40cb2b5bb42 100644
Binary files a/gui/themes/residualvm.zip and b/gui/themes/residualvm.zip differ
diff --git a/gui/themes/residualvm/THEMERC b/gui/themes/residualvm/THEMERC
index 442f37e083e..e3792fee684 100644
--- a/gui/themes/residualvm/THEMERC
+++ b/gui/themes/residualvm/THEMERC
@@ -1,3 +1,3 @@
-[SCUMMVM_STX0.9.23:ResidualVM Modern Theme Remastered:No Author]
+[SCUMMVM_STX0.9.24:ResidualVM Modern Theme Remastered:No Author]
%using ../common
%using ../common-svg
diff --git a/gui/themes/scummclassic.zip b/gui/themes/scummclassic.zip
index ca2607b382f..a539796554c 100644
Binary files a/gui/themes/scummclassic.zip and b/gui/themes/scummclassic.zip differ
diff --git a/gui/themes/scummclassic/THEMERC b/gui/themes/scummclassic/THEMERC
index d3a8ae3af49..09accb5ad96 100644
--- a/gui/themes/scummclassic/THEMERC
+++ b/gui/themes/scummclassic/THEMERC
@@ -1 +1 @@
-[SCUMMVM_STX0.9.23:ScummVM Classic Theme:No Author]
+[SCUMMVM_STX0.9.24:ScummVM Classic Theme:No Author]
diff --git a/gui/themes/scummclassic/classic_layout.stx b/gui/themes/scummclassic/classic_layout.stx
index ceafccb9459..984c467ae4e 100644
--- a/gui/themes/scummclassic/classic_layout.stx
+++ b/gui/themes/scummclassic/classic_layout.stx
@@ -1511,6 +1511,37 @@
</layout>
</dialog>
+ <dialog name = 'GameOptions_Misc' overlays = 'Dialog.GameOptions.TabWidget'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'
+ type = 'ScrollContainerWidget'
+ />
+ </layout>
+ </dialog>
+
+ <dialog name = 'GameOptions_Misc_Container' overlays = 'GameOptions_Misc.Container' shading = 'dim'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' align = 'center'>
+ <widget name = 'EnableHotspots'
+ type = 'Checkbox'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' align = 'center'>
+ <widget name = 'HotspotMarkerPopupDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'HotspotMarkerPopup'
+ type = 'PopUp'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' align = 'center'>
+ <widget name = 'ShowHotspotText'
+ type = 'Checkbox'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name = 'GlobalMenu' overlays = 'screen_center'>
<layout type = 'vertical' padding = '16, 16, 16, 16' align = 'center'>
<widget name = 'Title'
diff --git a/gui/themes/scummclassic/classic_layout_lowres.stx b/gui/themes/scummclassic/classic_layout_lowres.stx
index 0fe3d149386..4f7b03026c4 100644
--- a/gui/themes/scummclassic/classic_layout_lowres.stx
+++ b/gui/themes/scummclassic/classic_layout_lowres.stx
@@ -1532,6 +1532,37 @@
</layout>
</dialog>
+ <dialog name = 'GameOptions_Misc' overlays = 'Dialog.GameOptions.TabWidget'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'
+ type = 'ScrollContainerWidget'
+ />
+ </layout>
+ </dialog>
+
+ <dialog name = 'GameOptions_Misc_Container' overlays = 'GameOptions_Misc.Container' shading = 'dim'>
+ <layout type = 'vertical' padding = '8, 8, 8, 8' spacing = '6'>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' align = 'center'>
+ <widget name = 'EnableHotspots'
+ type = 'Checkbox'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' align = 'center'>
+ <widget name = 'HotspotMarkerPopupDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'HotspotMarkerPopup'
+ type = 'PopUp'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' align = 'center'>
+ <widget name = 'ShowHotspotText'
+ type = 'Checkbox'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name = 'GlobalMenu' overlays = 'screen_center'>
<layout type = 'vertical' padding = '2, 2, 2, 6' align = 'center' spacing='0'>
<widget name = 'Title'
diff --git a/gui/themes/scummmodern.zip b/gui/themes/scummmodern.zip
index 756addce923..ec6dcb3cc71 100644
Binary files a/gui/themes/scummmodern.zip and b/gui/themes/scummmodern.zip differ
diff --git a/gui/themes/scummmodern/THEMERC b/gui/themes/scummmodern/THEMERC
index f56e57a3da9..514707afb8d 100644
--- a/gui/themes/scummmodern/THEMERC
+++ b/gui/themes/scummmodern/THEMERC
@@ -1,2 +1,2 @@
-[SCUMMVM_STX0.9.23:ScummVM Modern Theme:No Author]
+[SCUMMVM_STX0.9.24:ScummVM Modern Theme:No Author]
%using ../common
diff --git a/gui/themes/scummremastered.zip b/gui/themes/scummremastered.zip
index 6abf823e5d5..e7352e4e615 100644
Binary files a/gui/themes/scummremastered.zip and b/gui/themes/scummremastered.zip differ
diff --git a/gui/themes/scummremastered/THEMERC b/gui/themes/scummremastered/THEMERC
index 2fffe8f54ca..6ce0aa09246 100644
--- a/gui/themes/scummremastered/THEMERC
+++ b/gui/themes/scummremastered/THEMERC
@@ -1,3 +1,3 @@
-[SCUMMVM_STX0.9.23:ScummVM Modern Theme Remastered:No Author]
+[SCUMMVM_STX0.9.24:ScummVM Modern Theme Remastered:No Author]
%using ../common
%using ../common-svg
Commit: 62a9809eadc27d97cdf86f047e3a792fa433a8da
https://github.com/scummvm/scummvm/commit/62a9809eadc27d97cdf86f047e3a792fa433a8da
Author: Mariusz Górski (takashivip at gmail.com)
Date: 2026-07-13T13:35:35+02:00
Commit Message:
TEST: Add hotspot unit tests
Assisted-by: Claude:claude-opus-4.8
Changed paths:
A test/common/events.h
A test/graphics/hotspot_info.h
diff --git a/test/common/events.h b/test/common/events.h
new file mode 100644
index 00000000000..183ecf186e4
--- /dev/null
+++ b/test/common/events.h
@@ -0,0 +1,48 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <cxxtest/TestSuite.h>
+#include "common/events.h"
+
+class EventsTestSuite : public CxxTest::TestSuite {
+public:
+ void test_engine_action_hotspot_toggle() {
+ TS_ASSERT_EQUALS(Common::kEngineActionHotspotToggle, 20000);
+ }
+
+ void test_custom_engine_action_event() {
+ Common::Event event;
+ event.type = Common::EVENT_CUSTOM_ENGINE_ACTION_START;
+ event.customType = Common::kEngineActionHotspotToggle;
+
+ TS_ASSERT_EQUALS(event.type, Common::EVENT_CUSTOM_ENGINE_ACTION_START);
+ TS_ASSERT_EQUALS(event.customType, Common::kEngineActionHotspotToggle);
+ }
+
+ void test_custom_engine_action_end_event() {
+ Common::Event event;
+ event.type = Common::EVENT_CUSTOM_ENGINE_ACTION_END;
+ event.customType = Common::kEngineActionHotspotToggle;
+
+ TS_ASSERT_EQUALS(event.type, Common::EVENT_CUSTOM_ENGINE_ACTION_END);
+ TS_ASSERT_EQUALS(event.customType, Common::kEngineActionHotspotToggle);
+ }
+};
diff --git a/test/graphics/hotspot_info.h b/test/graphics/hotspot_info.h
new file mode 100644
index 00000000000..d71734244e2
--- /dev/null
+++ b/test/graphics/hotspot_info.h
@@ -0,0 +1,67 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <cxxtest/TestSuite.h>
+#include "graphics/hotspot_renderer.h"
+
+class HotspotInfoTestSuite : public CxxTest::TestSuite {
+public:
+ void test_constructor_with_values() {
+ Graphics::HotspotInfo info(Common::Point(10, 20), "Test");
+ TS_ASSERT_EQUALS(info.position.x, 10);
+ TS_ASSERT_EQUALS(info.position.y, 20);
+ TS_ASSERT_EQUALS(info.name, "Test");
+ }
+
+ void test_default_constructor() {
+ Graphics::HotspotInfo info;
+ TS_ASSERT_EQUALS(info.position.x, 0);
+ TS_ASSERT_EQUALS(info.position.y, 0);
+ TS_ASSERT(info.name.empty());
+ }
+
+ void test_empty_name() {
+ Graphics::HotspotInfo info(Common::Point(100, 200), "");
+ TS_ASSERT_EQUALS(info.position.x, 100);
+ TS_ASSERT_EQUALS(info.position.y, 200);
+ TS_ASSERT(info.name.empty());
+ }
+
+ void test_negative_coordinates() {
+ Graphics::HotspotInfo info(Common::Point(-5, -10), "Negative");
+ TS_ASSERT_EQUALS(info.position.x, -5);
+ TS_ASSERT_EQUALS(info.position.y, -10);
+ TS_ASSERT_EQUALS(info.name, "Negative");
+ }
+
+ void test_default_type() {
+ Graphics::HotspotInfo info(Common::Point(0, 0), "");
+ TS_ASSERT_EQUALS(info.type, Graphics::kHotspotDefault);
+ }
+
+ void test_explicit_type() {
+ Graphics::HotspotInfo npc(Common::Point(10, 20), "Guard", Graphics::kHotspotNPC);
+ TS_ASSERT_EQUALS(npc.type, Graphics::kHotspotNPC);
+
+ Graphics::HotspotInfo obj(Common::Point(30, 40), "Door", Graphics::kHotspotExit);
+ TS_ASSERT_EQUALS(obj.type, Graphics::kHotspotExit);
+ }
+};
Commit: 0ac8afb5b57d58010914c3f8c03b3bb9780024d2
https://github.com/scummvm/scummvm/commit/0ac8afb5b57d58010914c3f8c03b3bb9780024d2
Author: Mariusz Górski (takashivip at gmail.com)
Date: 2026-07-13T13:35:35+02:00
Commit Message:
DOCS: Document hotspot display feature
Assisted-by: Claude:claude-opus-4.8
Changed paths:
doc/docportal/settings/keymaps.rst
doc/docportal/settings/misc.rst
diff --git a/doc/docportal/settings/keymaps.rst b/doc/docportal/settings/keymaps.rst
index f116e376e47..625b5f633d6 100644
--- a/doc/docportal/settings/keymaps.rst
+++ b/doc/docportal/settings/keymaps.rst
@@ -248,3 +248,8 @@ Left
Right
*keymap_engine-default_RIGHT*
+
+.. _hotspots:
+
+Show hotspots
+ *keymap_engine-default_HOTS*
diff --git a/doc/docportal/settings/misc.rst b/doc/docportal/settings/misc.rst
index c3a6b88956f..f6d67f72a50 100644
--- a/doc/docportal/settings/misc.rst
+++ b/doc/docportal/settings/misc.rst
@@ -24,10 +24,31 @@ Autosave
.. _seed:
Random seed
- Every time you start ScummVM, a random seed is generated. Use this option to set a fixed seed, which ensures that random events in games play out the same way every time.
+ Every time you start ScummVM, a random seed is generated. Use this option to set a fixed seed, which ensures that random events in games play out the same way every time.
*random_seed*
+.. _enablehotspots:
+
+Enable hotspot display
+ Enables visual markers and labels for interactive objects in supported games. When enabled, pressing the hotspot key (default 'h') displays markers at clickable locations.
+
+ *enable_hotspots*
+
+.. _hotspotmarker:
+
+Hotspot marker
+ Selects the visual style for hotspot markers. Options include Point (circular marker), Square (rectangular outline), or Crosshair (crosshair symbol).
+
+ *hotspot_marker*
+
+.. _showhotspottext:
+
+Show hotspot labels
+ When enabled, displays text labels next to hotspot markers showing the name of the interactive object.
+
+ *show_hotspot_text*
+
.. _discord:
Enable Discord Integration
Commit: 258a839452458900375cfc636f811f62d3966362
https://github.com/scummvm/scummvm/commit/258a839452458900375cfc636f811f62d3966362
Author: Mariusz Górski (takashivip at gmail.com)
Date: 2026-07-13T13:35:35+02:00
Commit Message:
TOUCHE: Add hotspot display support
Assisted-by: Claude:claude-opus-4.8
Changed paths:
engines/touche/metaengine.cpp
engines/touche/touche.cpp
engines/touche/touche.h
diff --git a/engines/touche/metaengine.cpp b/engines/touche/metaengine.cpp
index 9cb6c836863..0c0b0099db3 100644
--- a/engines/touche/metaengine.cpp
+++ b/engines/touche/metaengine.cpp
@@ -24,6 +24,7 @@
#include "common/savefile.h"
#include "common/system.h"
#include "common/translation.h"
+#include "common/events.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymapper.h"
@@ -131,6 +132,11 @@ Common::KeymapArray ToucheMetaEngine::initKeymaps(const char *target) const {
act->addDefaultInputMapping("JOY_B");
engineKeyMap->addAction(act);
+ act = new Action(kStandardActionToggleHotspots, _("Show hotspots"));
+ act->setCustomEngineActionEvent(Common::kEngineActionHotspotToggle);
+ act->addDefaultInputMapping("h");
+ gameKeyMap->addAction(act);
+
{
act = new Action("SKIPORQUIT", _("Skip sequence / open quit dialog"));
act->setCustomEngineActionEvent(kToucheActionSkipOrQuit);
diff --git a/engines/touche/touche.cpp b/engines/touche/touche.cpp
index 31f1f7b9835..62ee0b42d0a 100644
--- a/engines/touche/touche.cpp
+++ b/engines/touche/touche.cpp
@@ -36,6 +36,7 @@
#include "engines/util.h"
#include "graphics/cursorman.h"
+#include "graphics/hotspot_renderer.h"
#include "graphics/paletteman.h"
#include "gui/debugger.h"
@@ -180,6 +181,7 @@ ToucheEngine::ToucheEngine(OSystem *system, Common::Language language)
_inventoryVar2 = nullptr;
_currentCursorObject = 0;
_talkTextMode = 0;
+ _hotspotSnapshot.currentEpisodeNum = -1;
}
ToucheEngine::~ToucheEngine() {
@@ -517,6 +519,7 @@ void ToucheEngine::runCycle() {
processAnimationTable();
updateKeyCharTalk(0);
updateDirtyScreenAreas();
+ drawHotspots();
++_flagsTable[295];
++_flagsTable[296];
++_flagsTable[297];
@@ -1505,6 +1508,145 @@ void ToucheEngine::drawHitBoxes() {
}
}
+void ToucheEngine::getHotspotPositions(Common::Array< ::Graphics::HotspotInfo> &hotspots) {
+ if (_flagsTable[618] != 0)
+ return;
+
+ int scrollX = _flagsTable[614];
+ int scrollY = _flagsTable[615];
+
+ for (uint i = 0; i < _programHitBoxTable.size(); ++i) {
+ const ProgramHitBoxData &hitBox = _programHitBoxTable[i];
+
+ if (hitBox.item & 0x1000)
+ break;
+
+ if (hitBox.item & 0x2000)
+ continue;
+
+ if (hitBox.hitBoxes[0].top & 0x4000)
+ continue;
+
+ int16 str = hitBox.str;
+ int screenX, screenY;
+ ::Graphics::HotspotType hotspotType;
+
+ if (hitBox.item & 0x4000) {
+ const KeyChar *keyChar = &_keyCharsTable[hitBox.item & ~0x4000];
+ if (keyChar->num == 0 || (keyChar->flags & 0x4000))
+ continue;
+ if (keyChar->strNum != 0)
+ str = hitBox.defaultStr;
+
+ const Common::Rect &charRect = keyChar->prevBoundingRect;
+ if (!charRect.isValidRect())
+ continue;
+
+ screenX = (charRect.left + charRect.right) / 2;
+ screenY = (charRect.top + charRect.bottom) / 2;
+ hotspotType = ::Graphics::kHotspotNPC;
+ } else {
+ const Common::Rect &objRect = hitBox.hitBoxes[0];
+ if (!objRect.isValidRect())
+ continue;
+
+ int centerX = (objRect.left + objRect.right) / 2;
+ int centerY = (objRect.top + objRect.bottom) / 2;
+
+ screenX = centerX - scrollX;
+ screenY = centerY - scrollY;
+ hotspotType = ::Graphics::kHotspotObject;
+ }
+
+ if (screenX < 0 || screenX >= kScreenWidth || screenY < 0 || screenY >= kRoomHeight)
+ continue;
+
+ Common::String name;
+ if (str > 0) {
+ const char *strData = getString(str);
+ if (strData)
+ name = Common::String(strData);
+ }
+
+ hotspots.push_back(::Graphics::HotspotInfo(Common::Point(screenX, screenY), name, hotspotType));
+ }
+}
+
+void ToucheEngine::rebuildHotspotSnapshot() const {
+ _hotspotSnapshot.flag618 = _flagsTable[618];
+ _hotspotSnapshot.scrollX = _flagsTable[614];
+ _hotspotSnapshot.scrollY = _flagsTable[615];
+ _hotspotSnapshot.currentEpisodeNum = _currentEpisodeNum;
+ _hotspotSnapshot.hitBoxes.clear();
+ for (uint i = 0; i < _programHitBoxTable.size(); ++i) {
+ const ProgramHitBoxData &hb = _programHitBoxTable[i];
+ HotspotSnapshot::HitBoxEntry e;
+ e.item = hb.item;
+ e.lockedTop = hb.hitBoxes[0].top;
+ e.str = hb.str;
+ e.defaultStr = hb.defaultStr;
+ e.hitBox0 = hb.hitBoxes[0];
+ _hotspotSnapshot.hitBoxes.push_back(e);
+ }
+ for (int i = 0; i < NUM_KEYCHARS; ++i) {
+ _hotspotSnapshot.chars[i].num = _keyCharsTable[i].num;
+ _hotspotSnapshot.chars[i].flags = _keyCharsTable[i].flags;
+ _hotspotSnapshot.chars[i].prevBoundingRect = _keyCharsTable[i].prevBoundingRect;
+ }
+}
+
+bool ToucheEngine::hotspotDirty() const {
+ const int16 flag618 = _flagsTable[618];
+ const int16 scrollX = _flagsTable[614];
+ const int16 scrollY = _flagsTable[615];
+
+ // Fast path: any scalar change forces a snapshot rebuild.
+ if (flag618 != _hotspotSnapshot.flag618 ||
+ scrollX != _hotspotSnapshot.scrollX ||
+ scrollY != _hotspotSnapshot.scrollY ||
+ _currentEpisodeNum != _hotspotSnapshot.currentEpisodeNum ||
+ _programHitBoxTable.size() != _hotspotSnapshot.hitBoxes.size()) {
+ rebuildHotspotSnapshot();
+ return true;
+ }
+
+ // When globally suppressed there are no hotspots to redraw.
+ if (flag618 != 0)
+ return false;
+
+ // Per-hitbox and per-character checks.
+ for (uint i = 0; i < _programHitBoxTable.size(); ++i) {
+ const ProgramHitBoxData &hb = _programHitBoxTable[i];
+ const HotspotSnapshot::HitBoxEntry &snap = _hotspotSnapshot.hitBoxes[i];
+
+ if (hb.item != snap.item ||
+ hb.hitBoxes[0].top != snap.lockedTop ||
+ hb.str != snap.str ||
+ hb.defaultStr != snap.defaultStr ||
+ hb.hitBoxes[0] != snap.hitBox0) {
+ rebuildHotspotSnapshot();
+ return true;
+ }
+
+ if (hb.item & 0x4000) {
+ const int charIdx = hb.item & ~0x4000;
+ const KeyChar &kc = _keyCharsTable[charIdx];
+ const HotspotSnapshot::CharEntry &cs = _hotspotSnapshot.chars[charIdx];
+ if (kc.num != cs.num ||
+ kc.flags != cs.flags ||
+ kc.prevBoundingRect != cs.prevBoundingRect) {
+ rebuildHotspotSnapshot();
+ return true;
+ }
+ }
+
+ if (hb.item & 0x1000)
+ break;
+ }
+
+ return false;
+}
+
void ToucheEngine::showCursor(bool show) {
debugC(9, kDebugEngine, "ToucheEngine::showCursor()");
CursorMan.showMouse(show);
diff --git a/engines/touche/touche.h b/engines/touche/touche.h
index 927b45a4cf0..c11675756d1 100644
--- a/engines/touche/touche.h
+++ b/engines/touche/touche.h
@@ -637,6 +637,10 @@ protected:
return Common::String::format("%s.%d", _targetName.c_str(), slot);
}
+ void getHotspotPositions(Common::Array< ::Graphics::HotspotInfo> &hotspots) override;
+ bool hotspotDirty() const override;
+ void rebuildHotspotSnapshot() const;
+
void setupOpcodes();
void op_nop();
void op_jnz();
@@ -911,6 +915,28 @@ protected:
Common::Rect _dirtyRectsTable[NUM_DIRTY_RECTS];
int _dirtyRectsTableCount;
+ struct HotspotSnapshot {
+ struct HitBoxEntry {
+ int16 item;
+ int16 lockedTop;
+ int16 str;
+ int16 defaultStr;
+ Common::Rect hitBox0;
+ };
+ struct CharEntry {
+ uint16 num;
+ uint16 flags;
+ Common::Rect prevBoundingRect;
+ };
+ int16 flag618;
+ int16 scrollX;
+ int16 scrollY;
+ int currentEpisodeNum;
+ Common::Array<HitBoxEntry> hitBoxes;
+ CharEntry chars[NUM_KEYCHARS];
+ };
+ mutable HotspotSnapshot _hotspotSnapshot;
+
static const uint8 _directionsTable[NUM_DIRECTIONS];
};
Commit: 0379edd7713a7e47345a0f6de1530cc630e900e8
https://github.com/scummvm/scummvm/commit/0379edd7713a7e47345a0f6de1530cc630e900e8
Author: Mariusz Górski (takashivip at gmail.com)
Date: 2026-07-13T13:35:35+02:00
Commit Message:
GRAPHICS: Scale hotspot overlay and support 32-bit backends
Assisted-by: Claude:claude-opus-4.8
Changed paths:
graphics/hotspot_renderer.cpp
graphics/hotspot_renderer.h
diff --git a/graphics/hotspot_renderer.cpp b/graphics/hotspot_renderer.cpp
index b8d42a16cb5..a262d6a9780 100644
--- a/graphics/hotspot_renderer.cpp
+++ b/graphics/hotspot_renderer.cpp
@@ -28,7 +28,9 @@
namespace Graphics {
-HotspotRenderer::HotspotRenderer() {
+HotspotRenderer::HotspotRenderer() :
+ _sizeScale(1.0f), _markerSize(kBaseMarkerSize), _glowSize(kBaseGlowSize),
+ _pointRadius(kBasePointRadius), _lineThickness(kBaseLineThickness) {
}
HotspotRenderer::~HotspotRenderer() {
@@ -44,14 +46,23 @@ void HotspotRenderer::render(Surface *surface,
if (!surface || hotspots.empty())
return;
+ if (format.bytesPerPixel < 2)
+ return;
+
+ float scaleX = (float)overlayWidth / (float)gameWidth;
+ float scaleY = (float)overlayHeight / (float)gameHeight;
+
+ _sizeScale = scaleX;
+ _markerSize = MAX(2, (int)(kBaseMarkerSize * _sizeScale));
+ _glowSize = MAX(1, (int)(kBaseGlowSize * _sizeScale));
+ _pointRadius = MAX(1, (int)(kBasePointRadius * _sizeScale));
+ _lineThickness = MAX(1, (int)(kBaseLineThickness * _sizeScale));
+
const Font *font = nullptr;
if (showText) {
font = FontMan.getFontByUsage(FontManager::kGUIFont);
}
- float scaleX = (float)overlayWidth / (float)gameWidth;
- float scaleY = (float)overlayHeight / (float)gameHeight;
-
for (uint i = 0; i < hotspots.size(); i++) {
int gameX = hotspots[i].position.x;
int gameY = hotspots[i].position.y;
@@ -88,54 +99,79 @@ void HotspotRenderer::drawMarker(Surface *surface, int x, int y, const PixelForm
void HotspotRenderer::drawHotspotLabel(Surface *surface, int overlayX, int overlayY,
const Common::U32String &label, int overlayWidth, int overlayHeight,
const PixelFormat &format, const Font *font) {
- int textX = overlayX + kMarkerSize / 2 + 8;
- int textY = overlayY - font->getFontHeight() / 2;
+ int baseTextWidth = font->getStringWidth(label);
+ int baseTextHeight = font->getFontHeight();
+ if (baseTextWidth <= 0 || baseTextHeight <= 0)
+ return;
- int textWidth = font->getStringWidth(label);
- int textHeight = font->getFontHeight();
+ int textWidth = MAX(1, (int)(baseTextWidth * _sizeScale));
+ int textHeight = MAX(1, (int)(baseTextHeight * _sizeScale));
- int bgX = textX - 4;
- int bgY = textY - 2;
- int bgW = textWidth + 8;
- int bgH = textHeight + 4;
+ int gap = MAX(1, (int)(8 * _sizeScale));
+ int textX = overlayX + _markerSize / 2 + gap;
+ int textY = overlayY - textHeight / 2;
+
+ int padX = MAX(1, (int)(4 * _sizeScale));
+ int padY = MAX(1, (int)(2 * _sizeScale));
+ int bgX = textX - padX;
+ int bgY = textY - padY;
+ int bgW = textWidth + padX * 2;
+ int bgH = textHeight + padY * 2;
drawLabelBox(surface, bgX, bgY, bgW, bgH,
overlayWidth, overlayHeight, format);
+ Surface textSurface;
+ textSurface.create(baseTextWidth, baseTextHeight, format);
+ textSurface.fillRect(Common::Rect(0, 0, baseTextWidth, baseTextHeight), 0);
+
uint32 textColor = format.RGBToColor(255, 255, 255);
- font->drawString(surface, label, textX, textY,
- overlayWidth - textX, textColor, kTextAlignLeft);
+ font->drawString(&textSurface, label, 0, 0, baseTextWidth, textColor, kTextAlignLeft);
+
+ Surface *scaledText = textSurface.scale((int16)textWidth, (int16)textHeight, false);
+ textSurface.free();
+ if (!scaledText)
+ return;
+
+ for (int sy = 0; sy < textHeight; sy++) {
+ int py = textY + sy;
+ if (py < 0 || py >= overlayHeight)
+ continue;
+ for (int sx = 0; sx < textWidth; sx++) {
+ int px = textX + sx;
+ if (px < 0 || px >= overlayWidth)
+ continue;
+ uint32 pixel = scaledText->getPixel(sx, sy);
+ if (pixel == 0)
+ continue;
+ surface->setPixel(px, py, pixel);
+ }
+ }
+
+ scaledText->free();
+ delete scaledText;
}
void HotspotRenderer::drawCrosshairMarker(Surface *surface, int x, int y,
int width, int height, const PixelFormat &format) {
- if (format.bytesPerPixel != 2)
- return;
+ const int lineLength = (_markerSize / 2) - 1;
- const int lineLength = (kMarkerSize / 2) - 1;
-
- drawLineWithGlow(surface, x - lineLength, y, x + lineLength, y, width, height, format, kLineThickness);
- drawLineWithGlow(surface, x, y - lineLength, x, y + lineLength, width, height, format, kLineThickness);
+ drawLineWithGlow(surface, x - lineLength, y, x + lineLength, y, width, height, format, _lineThickness);
+ drawLineWithGlow(surface, x, y - lineLength, x, y + lineLength, width, height, format, _lineThickness);
}
void HotspotRenderer::drawSquareMarker(Surface *surface, int x, int y,
int width, int height, const PixelFormat &format) {
- if (format.bytesPerPixel != 2)
- return;
-
- int size = kMarkerSize;
- int rectX = x - kMarkerSize / 2;
- int rectY = y - kMarkerSize / 2;
+ int size = _markerSize;
+ int rectX = x - _markerSize / 2;
+ int rectY = y - _markerSize / 2;
drawRectWithGlow(surface, rectX, rectY, size, size, width, height, format);
}
void HotspotRenderer::drawPointMarker(Surface *surface, int x, int y,
int width, int height, const PixelFormat &format) {
- if (format.bytesPerPixel != 2)
- return;
-
- int maxRadius = kPointRadius + kGlowSize;
+ int maxRadius = _pointRadius + _glowSize;
for (int dy = -maxRadius; dy <= maxRadius; dy++) {
for (int dx = -maxRadius; dx <= maxRadius; dx++) {
@@ -148,16 +184,15 @@ void HotspotRenderer::drawPointMarker(Surface *surface, int x, int y,
int distSq = dx * dx + dy * dy;
int dist = (int)(sqrtf((float)distSq) + 0.5f);
- blendPixelWithGlow(surface, px, py, format, dist, kPointRadius);
+ blendPixelWithGlow(surface, px, py, format, dist, _pointRadius);
}
}
}
void HotspotRenderer::blendPixelWithGlow(Surface *surface, int px, int py,
const PixelFormat &format, int distance, int solidSize) {
- uint16 *destPixel = (uint16 *)surface->getBasePtr(px, py);
byte bgR, bgG, bgB, bgA;
- format.colorToARGB(*destPixel, bgA, bgR, bgG, bgB);
+ format.colorToARGB(surface->getPixel(px, py), bgA, bgR, bgG, bgB);
// Convert solidSize to maximum solid distance (solidSize=1 means center pixel only, distance=0)
int maxSolidDistance = solidSize - 1;
@@ -168,22 +203,19 @@ void HotspotRenderer::blendPixelWithGlow(Surface *surface, int px, int py,
byte r = ((255 * solidBeta) + (bgR * solidAlpha)) / 255;
byte g = ((255 * solidBeta) + (bgG * solidAlpha)) / 255;
byte b = ((255 * solidBeta) + (bgB * solidAlpha)) / 255;
- *destPixel = format.RGBToColor(r, g, b);
- } else if (distance <= maxSolidDistance + kGlowSize) {
+ surface->setPixel(px, py, format.RGBToColor(r, g, b));
+ } else if (distance <= maxSolidDistance + _glowSize) {
int glowDist = distance - maxSolidDistance;
- int alpha = ((kGlowSize - glowDist) * 80) / kGlowSize;
+ int alpha = ((_glowSize - glowDist) * 80) / _glowSize;
byte r = ((255 * alpha) + (bgR * (255 - alpha))) / 255;
byte g = ((255 * alpha) + (bgG * (255 - alpha))) / 255;
byte b = ((255 * alpha) + (bgB * (255 - alpha))) / 255;
- *destPixel = format.RGBToColor(r, g, b);
+ surface->setPixel(px, py, format.RGBToColor(r, g, b));
}
}
void HotspotRenderer::drawLineWithGlow(Surface *surface, int x1, int y1, int x2, int y2,
int width, int height, const PixelFormat &format, int lineThickness) {
- if (format.bytesPerPixel != 2)
- return;
-
bool isHorizontal = (y1 == y2);
bool isVertical = (x1 == x2);
@@ -203,7 +235,7 @@ void HotspotRenderer::drawLineWithGlow(Surface *surface, int x1, int y1, int x2,
}
for (int along = alongMin; along <= alongMax; along++) {
- for (int thickness = -lineThickness - kGlowSize; thickness <= lineThickness + kGlowSize; thickness++) {
+ for (int thickness = -lineThickness - _glowSize; thickness <= lineThickness + _glowSize; thickness++) {
int px, py;
if (isHorizontal) {
px = along;
@@ -224,32 +256,28 @@ void HotspotRenderer::drawLineWithGlow(Surface *surface, int x1, int y1, int x2,
void HotspotRenderer::drawRectWithGlow(Surface *surface, int x, int y, int w, int h,
int overlayWidth, int overlayHeight, const PixelFormat &format) {
- drawLineWithGlow(surface, x, y, x + w - 1, y, overlayWidth, overlayHeight, format, kLineThickness);
+ drawLineWithGlow(surface, x, y, x + w - 1, y, overlayWidth, overlayHeight, format, _lineThickness);
drawLineWithGlow(surface, x, y + h - 1, x + w - 1, y + h - 1,
- overlayWidth, overlayHeight, format, kLineThickness);
- drawLineWithGlow(surface, x, y + 1, x, y + h - 2, overlayWidth, overlayHeight, format, kLineThickness);
+ overlayWidth, overlayHeight, format, _lineThickness);
+ drawLineWithGlow(surface, x, y + 1, x, y + h - 2, overlayWidth, overlayHeight, format, _lineThickness);
drawLineWithGlow(surface, x + w - 1, y + 1, x + w - 1, y + h - 2,
- overlayWidth, overlayHeight, format, kLineThickness);
+ overlayWidth, overlayHeight, format, _lineThickness);
}
void HotspotRenderer::drawLabelBox(Surface *surface, int x, int y, int w, int h,
int overlayWidth, int overlayHeight, const PixelFormat &format) {
- if (format.bytesPerPixel != 2)
- return;
-
for (int py = y + 1; py < y + h - 1; py++) {
for (int px = x + 1; px < x + w - 1; px++) {
if (px < 0 || px >= overlayWidth || py < 0 || py >= overlayHeight)
continue;
- uint16 *destPixel = (uint16 *)surface->getBasePtr(px, py);
byte bgR, bgG, bgB, bgA;
- format.colorToARGB(*destPixel, bgA, bgR, bgG, bgB);
+ format.colorToARGB(surface->getPixel(px, py), bgA, bgR, bgG, bgB);
byte r = (bgR * 40) / 100;
byte g = (bgG * 40) / 100;
byte b = (bgB * 40) / 100;
- *destPixel = format.RGBToColor(r, g, b);
+ surface->setPixel(px, py, format.RGBToColor(r, g, b));
}
}
diff --git a/graphics/hotspot_renderer.h b/graphics/hotspot_renderer.h
index adb5b038b20..c64dfddbe03 100644
--- a/graphics/hotspot_renderer.h
+++ b/graphics/hotspot_renderer.h
@@ -96,12 +96,18 @@ public:
private:
enum {
- kMarkerSize = 10, ///< Size of square and crosshair markers
- kGlowSize = 3, ///< Size of glow effect around markers and text
- kPointRadius = 3, ///< Radius of point marker
- kLineThickness = 1 ///< Thickness of lines for markers and text boxes
+ kBaseMarkerSize = 10, ///< Size of square and crosshair markers at 1x scale
+ kBaseGlowSize = 3, ///< Size of glow effect around markers and text at 1x scale
+ kBasePointRadius = 3, ///< Radius of point marker at 1x scale
+ kBaseLineThickness = 1 ///< Thickness of lines for markers and text boxes at 1x scale
};
+ float _sizeScale; ///< Overlay/game scale factor applied to marker geometry
+ int _markerSize; ///< Scaled size of square and crosshair markers
+ int _glowSize; ///< Scaled size of glow effect around markers and text
+ int _pointRadius; ///< Scaled radius of point marker
+ int _lineThickness; ///< Scaled thickness of lines for markers and text boxes
+
void drawMarker(Surface *surface, int x, int y, const PixelFormat &format, MarkerShape markerShape);
void drawCrosshairMarker(Surface *surface, int x, int y, int width, int height, const PixelFormat &format);
void drawSquareMarker(Surface *surface, int x, int y, int width, int height, const PixelFormat &format);
Commit: a1bba7b319e96e38ff8fe5235acdfa4d5865dd1a
https://github.com/scummvm/scummvm/commit/a1bba7b319e96e38ff8fe5235acdfa4d5865dd1a
Author: Mariusz Górski (takashivip at gmail.com)
Date: 2026-07-13T13:35:35+02:00
Commit Message:
ENGINES: Hide mouse cursor while hotspots overlay is shown
Assisted-by: Claude:claude-opus-4.8
Changed paths:
engines/engine.cpp
engines/engine.h
diff --git a/engines/engine.cpp b/engines/engine.cpp
index aaa7a7870e6..a2e92e1a241 100644
--- a/engines/engine.cpp
+++ b/engines/engine.cpp
@@ -158,7 +158,8 @@ Engine::Engine(OSystem *syst)
_autosaveInterval(ConfMan.getInt("autosave_period")),
_lastAutosaveTime(_system->getMillis()),
_showHotspots(false),
- _hotspotForceRedraw(false) {
+ _hotspotForceRedraw(false),
+ _hotspotPrevCursorVisible(false) {
g_engine = this;
_quitRequested = false;
@@ -747,12 +748,19 @@ void Engine::pauseEngineIntern(bool pause) {
}
void Engine::showHotspots(bool show) {
+ bool wasShowing = _showHotspots;
_showHotspots = show;
- if (show)
+ if (show) {
_hotspotForceRedraw = true;
- else if (g_system->isOverlayVisible())
- g_system->hideOverlay();
+ if (!wasShowing)
+ _hotspotPrevCursorVisible = CursorMan.showMouse(false);
+ } else {
+ if (wasShowing)
+ CursorMan.showMouse(_hotspotPrevCursorVisible);
+ if (g_system->isOverlayVisible())
+ g_system->hideOverlay();
+ }
}
void Engine::getHotspotPositions(Common::Array<Graphics::HotspotInfo> &hotspots) {
diff --git a/engines/engine.h b/engines/engine.h
index 73508b474d7..daaec3cdf5a 100644
--- a/engines/engine.h
+++ b/engines/engine.h
@@ -199,6 +199,13 @@ protected:
*/
bool _hotspotForceRedraw;
+ /**
+ * Mouse cursor visibility from before hotspots were shown. While hotspots
+ * are displayed the cursor is hidden (otherwise it leaves trails over the
+ * overlay), and this remembers the state to restore afterwards.
+ */
+ bool _hotspotPrevCursorVisible;
+
private:
/**
* The associated metaengine
Commit: 50184ef27df59e562824c99c0b1abf7a35c700d1
https://github.com/scummvm/scummvm/commit/50184ef27df59e562824c99c0b1abf7a35c700d1
Author: Mariusz Górski (takashivip at gmail.com)
Date: 2026-07-13T13:35:35+02:00
Commit Message:
COMMON: Drive hotspot display with a start/end event pair
Replace the EVENT_TOGGLE_HOTSPOTS event and the kEngineActionHotspotToggle
custom engine action with an EVENT_HOTSPOTS_SHOW / EVENT_HOTSPOTS_HIDE pair
registered in Keymapper::convertStartToEnd(). The keymap action now sends the
event directly via Action::setEvent(), so the reserved global EngineAction
enum is no longer needed.
Because the pair is a keymapper start/end event, SHOW is emitted while the
hotspot key is held and HIDE when it is released, preserving the hold-to-show
behavior that a single plain event (which only fires on key press) could not
provide.
Assisted-by: Claude:claude-opus-4.8
Changed paths:
backends/events/default/default-events.cpp
backends/keymapper/keymapper.cpp
common/events.h
engines/metaengine.cpp
engines/touche/metaengine.cpp
test/common/events.h
diff --git a/backends/events/default/default-events.cpp b/backends/events/default/default-events.cpp
index 056aef9124a..1179c47877a 100644
--- a/backends/events/default/default-events.cpp
+++ b/backends/events/default/default-events.cpp
@@ -120,20 +120,18 @@ bool DefaultEventManager::pollEvent(Common::Event &event) {
_modifierState = event.kbd.flags;
break;
- case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
- if (event.customType == Common::kEngineActionHotspotToggle && g_engine) {
- if (ConfMan.getBool("enable_hotspots")) {
- g_engine->showHotspots(true);
- }
+ case Common::EVENT_HOTSPOTS_SHOW:
+ if (g_engine && ConfMan.getBool("enable_hotspots")) {
+ g_engine->showHotspots(true);
}
+ forwardEvent = false;
break;
- case Common::EVENT_CUSTOM_ENGINE_ACTION_END:
- if (event.customType == Common::kEngineActionHotspotToggle && g_engine) {
- if (ConfMan.getBool("enable_hotspots")) {
- g_engine->showHotspots(false);
- }
+ case Common::EVENT_HOTSPOTS_HIDE:
+ if (g_engine && ConfMan.getBool("enable_hotspots")) {
+ g_engine->showHotspots(false);
}
+ forwardEvent = false;
break;
case Common::EVENT_MOUSEMOVE:
diff --git a/backends/keymapper/keymapper.cpp b/backends/keymapper/keymapper.cpp
index 303cde16552..33c6338c778 100644
--- a/backends/keymapper/keymapper.cpp
+++ b/backends/keymapper/keymapper.cpp
@@ -377,6 +377,9 @@ EventType Keymapper::convertStartToEnd(EventType type) {
case EVENT_CUSTOM_ENGINE_ACTION_START:
result = EVENT_CUSTOM_ENGINE_ACTION_END;
break;
+ case EVENT_HOTSPOTS_SHOW:
+ result = EVENT_HOTSPOTS_HIDE;
+ break;
default:
break;
}
diff --git a/common/events.h b/common/events.h
index 6e69117d176..702080247f8 100644
--- a/common/events.h
+++ b/common/events.h
@@ -117,8 +117,13 @@ enum EventType {
EVENT_FOCUS_GAINED = 36,
EVENT_FOCUS_LOST = 37,
- /** Toggle hotspot display. */
- EVENT_TOGGLE_HOTSPOTS = 38,
+ /**
+ * Hotspot display, driven by a hold-to-show key. Bound as a keymapper
+ * start/end pair (see Keymapper::convertStartToEnd), so SHOW is sent while
+ * the key is held and HIDE when it is released.
+ */
+ EVENT_HOTSPOTS_SHOW = 38,
+ EVENT_HOTSPOTS_HIDE = 39,
/**
* We reserve some event ids for custom events.
@@ -132,15 +137,6 @@ enum EventType {
EVENT_USER_LAST_AVAILABLE = 9999
};
-/**
- * Custom engine action types for EVENT_CUSTOM_ENGINE_ACTION_START/END.
- * These are used by cross-engine features. Values start at 20000 to avoid
- * clashing with engine-specific custom action ids, which start from 0.
- */
-enum EngineAction {
- kEngineActionHotspotToggle = 20000
-};
-
const int16 JOYAXIS_MIN = -32768;
const int16 JOYAXIS_MAX = 32767;
diff --git a/engines/metaengine.cpp b/engines/metaengine.cpp
index f98ae29bdcb..23f10114393 100644
--- a/engines/metaengine.cpp
+++ b/engines/metaengine.cpp
@@ -160,7 +160,7 @@ Common::KeymapArray MetaEngine::initKeymaps(const char *target) const {
engineKeyMap->addAction(act);
act = new Action(kStandardActionToggleHotspots, _("Show hotspots"));
- act->setCustomEngineActionEvent(Common::kEngineActionHotspotToggle);
+ act->setEvent(Common::EVENT_HOTSPOTS_SHOW);
act->addDefaultInputMapping("h");
engineKeyMap->addAction(act);
diff --git a/engines/touche/metaengine.cpp b/engines/touche/metaengine.cpp
index 0c0b0099db3..215c69623d5 100644
--- a/engines/touche/metaengine.cpp
+++ b/engines/touche/metaengine.cpp
@@ -133,7 +133,7 @@ Common::KeymapArray ToucheMetaEngine::initKeymaps(const char *target) const {
engineKeyMap->addAction(act);
act = new Action(kStandardActionToggleHotspots, _("Show hotspots"));
- act->setCustomEngineActionEvent(Common::kEngineActionHotspotToggle);
+ act->setEvent(Common::EVENT_HOTSPOTS_SHOW);
act->addDefaultInputMapping("h");
gameKeyMap->addAction(act);
diff --git a/test/common/events.h b/test/common/events.h
index 183ecf186e4..88a21c1fbb0 100644
--- a/test/common/events.h
+++ b/test/common/events.h
@@ -24,25 +24,17 @@
class EventsTestSuite : public CxxTest::TestSuite {
public:
- void test_engine_action_hotspot_toggle() {
- TS_ASSERT_EQUALS(Common::kEngineActionHotspotToggle, 20000);
- }
-
- void test_custom_engine_action_event() {
+ void test_hotspots_show_event() {
Common::Event event;
- event.type = Common::EVENT_CUSTOM_ENGINE_ACTION_START;
- event.customType = Common::kEngineActionHotspotToggle;
+ event.type = Common::EVENT_HOTSPOTS_SHOW;
- TS_ASSERT_EQUALS(event.type, Common::EVENT_CUSTOM_ENGINE_ACTION_START);
- TS_ASSERT_EQUALS(event.customType, Common::kEngineActionHotspotToggle);
+ TS_ASSERT_EQUALS(event.type, Common::EVENT_HOTSPOTS_SHOW);
}
- void test_custom_engine_action_end_event() {
+ void test_hotspots_hide_event() {
Common::Event event;
- event.type = Common::EVENT_CUSTOM_ENGINE_ACTION_END;
- event.customType = Common::kEngineActionHotspotToggle;
+ event.type = Common::EVENT_HOTSPOTS_HIDE;
- TS_ASSERT_EQUALS(event.type, Common::EVENT_CUSTOM_ENGINE_ACTION_END);
- TS_ASSERT_EQUALS(event.customType, Common::kEngineActionHotspotToggle);
+ TS_ASSERT_EQUALS(event.type, Common::EVENT_HOTSPOTS_HIDE);
}
};
More information about the Scummvm-git-logs
mailing list