[Scummvm-git-logs] scummvm master -> 2374c2e5144955e80415001afc29f6daf4abdd8b

bluegr noreply at scummvm.org
Tue Sep 8 21:31:47 UTC 2026


This automated email contains information about 11 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
2d9def5e37 GUI: Remove obsolete and unreachable code
12e84fd6cd GUI: Move handleTooltipUpdate to Widget class
a0884c5b08 GUI: Store last mouse position using absolute coordinates
2c0ac2e45f GUI: Rework tickle system
056f4c9e64 GUI: Remove useless WIDGET_RETAIN_FOCUS flag in PopUpWidget
be0871d2db GUI: Simplify focused widget logic
87aab725a5 GUI: Remove WIDGET_IGNORE_DRAG flag
20c2b2d597 GUI: Introduce WIDGET_HOOK_DRAG flag
0ace2ef96e GUI: Rework ScrollContainerWidget to use WIDGET_HOOK_DRAG
d2a9c78389 GUI: Avoid clashes when dragging in a ScrollContainerWidget
2374c2e514 GUI: Renumber Widget flags


Commit: 2d9def5e37261374152460fa04c9dfa72049ba20
    https://github.com/scummvm/scummvm/commit/2d9def5e37261374152460fa04c9dfa72049ba20
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2026-09-09T00:31:37+03:00

Commit Message:
GUI: Remove obsolete and unreachable code

Since commits 5051b080a2cf and b56c7b88d7d (20 years ago) these flags are unused
or have no effect.

Changed paths:
    gui/widget.cpp
    gui/widget.h


diff --git a/gui/widget.cpp b/gui/widget.cpp
index 30393486116..f11cee607e7 100644
--- a/gui/widget.cpp
+++ b/gui/widget.cpp
@@ -115,28 +115,9 @@ void Widget::draw() {
 		}
 		oldClip = g_gui.theme()->swapClipRect(clip);
 
-		// Draw border
-		if (_flags & WIDGET_BORDER) {
-			g_gui.theme()->drawWidgetBackground(Common::Rect(_x, _y, _x + _w, _y + _h),
-			                                    ThemeEngine::kWidgetBackgroundBorder);
-			_x += 4;
-			_y += 4;
-			_w -= 8;
-			_h -= 8;
-		}
-
 		// Now perform the actual widget draw
 		drawWidget();
 
-
-		// Restore x/y
-		if (_flags & WIDGET_BORDER) {
-			_x -= 4;
-			_y -= 4;
-			_w += 8;
-			_h += 8;
-		}
-
 		_x = oldX;
 		_y = oldY;
 
@@ -396,7 +377,7 @@ ButtonWidget::ButtonWidget(GuiObject *boss, int x, int y, int w, int h, bool sca
 		_lowresHotkey = hotkey;
 	}
 
-	setFlags(WIDGET_ENABLED/* | WIDGET_BORDER*/ | WIDGET_CLEARBG);
+	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG);
 	_type = kButtonWidget;
 }
 
@@ -418,7 +399,7 @@ ButtonWidget::ButtonWidget(GuiObject *boss, const Common::String &name, const Co
 		_lowresHotkey = hotkey;
 	}
 
-	setFlags(WIDGET_ENABLED/* | WIDGET_BORDER*/ | WIDGET_CLEARBG);
+	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG);
 	_type = kButtonWidget;
 }
 
@@ -618,7 +599,7 @@ PicButtonWidget::PicButtonWidget(GuiObject *boss, int x, int y, int w, int h, bo
 	: ButtonWidget(boss, x, y, w, h, scale, Common::U32String(), tooltip, cmd, hotkey),
 	  _showButton(true) {
 	Common::fill(_alphaType, _alphaType + ARRAYSIZE(_alphaType), Graphics::ALPHA_OPAQUE);
-	setFlags(WIDGET_ENABLED/* | WIDGET_BORDER*/ | WIDGET_CLEARBG);
+	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG);
 	_type = kButtonWidget;
 }
 
@@ -630,7 +611,7 @@ PicButtonWidget::PicButtonWidget(GuiObject *boss, const Common::String &name, co
 	: ButtonWidget(boss, name, Common::U32String(), tooltip, cmd, hotkey),
 	  _showButton(true) {
 	Common::fill(_alphaType, _alphaType + ARRAYSIZE(_alphaType), Graphics::ALPHA_OPAQUE);
-	setFlags(WIDGET_ENABLED/* | WIDGET_BORDER*/ | WIDGET_CLEARBG);
+	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG);
 	_type = kButtonWidget;
 }
 
@@ -762,7 +743,6 @@ void CheckboxWidget::handleMouseUp(int x, int y, int button, int clickCount) {
 void CheckboxWidget::setState(bool state) {
 	if (_state != state) {
 		_state = state;
-		//_flags ^= WIDGET_INV_BORDER;
 		markAsDirty();
 	}
 	sendCommand(_cmd, _state);
@@ -842,7 +822,6 @@ void RadiobuttonWidget::setState(bool state, bool setGroup) {
 
 	if (_state != state) {
 		_state = state;
-		//_flags ^= WIDGET_INV_BORDER;
 		markAsDirty();
 	}
 	sendCommand(_cmd, _state);
diff --git a/gui/widget.h b/gui/widget.h
index 1dd1f6a60d9..54ef1f4e419 100644
--- a/gui/widget.h
+++ b/gui/widget.h
@@ -43,9 +43,7 @@ enum {
 	WIDGET_ENABLED		= 1 <<  0,
 	WIDGET_INVISIBLE	= 1 <<  1,
 	WIDGET_HILITED		= 1 <<  2,
-	WIDGET_BORDER		= 1 <<  3,
 	WIDGET_PRESSED		= 1 <<	4,
-	//WIDGET_INV_BORDER	= 1 <<  4,
 	WIDGET_CLEARBG		= 1 <<  5,
 	WIDGET_WANT_TICKLE	= 1 <<  7,
 	WIDGET_TRACK_MOUSE	= 1 <<  8,


Commit: 12e84fd6cd67c068a2f7b05c05285b9a61b98a53
    https://github.com/scummvm/scummvm/commit/12e84fd6cd67c068a2f7b05c05285b9a61b98a53
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2026-09-09T00:31:37+03:00

Commit Message:
GUI: Move handleTooltipUpdate to Widget class

A GuiObject has no idea of tooltip

Changed paths:
    gui/object.h
    gui/widget.h


diff --git a/gui/object.h b/gui/object.h
index 1eaa2ebb434..a1c4e8d3eb8 100644
--- a/gui/object.h
+++ b/gui/object.h
@@ -104,7 +104,6 @@ public:
 	virtual Common::Rect getClipRect() const;
 
 	virtual void handleMouseWheel(int x, int y, int direction) {};
-	virtual void handleTooltipUpdate(int x, int y) {};
 protected:
 	virtual void	releaseFocus() = 0;
 };
diff --git a/gui/widget.h b/gui/widget.h
index 54ef1f4e419..407dd460cbc 100644
--- a/gui/widget.h
+++ b/gui/widget.h
@@ -146,6 +146,7 @@ public:
 	virtual bool handleKeyDown(Common::KeyState state) { return false; }	// Return true if the event was handled
 	virtual bool handleKeyUp(Common::KeyState state) { return false; }	// Return true if the event was handled
 	virtual void handleOtherEvent(const Common::Event &evt) {}
+	virtual void handleTooltipUpdate(int x, int y) {}
 	virtual void handleTickle() {}
 
 	/** Mark the widget and its children as dirty so they are redrawn on the next screen update */


Commit: a0884c5b08b9c95e39dcc7f723a4eb8ae314fd0a
    https://github.com/scummvm/scummvm/commit/a0884c5b08b9c95e39dcc7f723a4eb8ae314fd0a
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2026-09-09T00:31:37+03:00

Commit Message:
GUI: Store last mouse position using absolute coordinates

This avoids the need to recalculate coordinates on dialog focus change.
As a consequence, the setMouseUpdatedOnFocus hack can be removed.
Alos, the Tooltip::setup doesn't need the parent Dialog anymore as we
pass it absolute coordinates.

Also reorder conditions to avoid calling widget functions when variable
checks are false.
Also don't make use of _lastClick type for mouse position as count is
not needed.

Changed paths:
    gui/Tooltip.cpp
    gui/Tooltip.h
    gui/dialog.cpp
    gui/dialog.h
    gui/gui-manager.cpp
    gui/gui-manager.h


diff --git a/gui/Tooltip.cpp b/gui/Tooltip.cpp
index 0c2cae77d07..c3e814d7eca 100644
--- a/gui/Tooltip.cpp
+++ b/gui/Tooltip.cpp
@@ -33,13 +33,11 @@ Tooltip::Tooltip() :
 	_backgroundType = GUI::ThemeEngine::kDialogBackgroundTooltip;
 }
 
-void Tooltip::setup(Dialog *parent, Widget *widget, int x, int y) {
+void Tooltip::setup(Widget *widget, int x, int y) {
 	assert(widget->hasTooltip());
 
 	_widget = widget;
 
-	setMouseUpdatedOnFocus(false);
-
 	_maxWidth = g_gui.xmlEval()->getVar("Globals.Tooltip.MaxWidth", 100);
 	_xdelta = g_gui.xmlEval()->getVar("Globals.Tooltip.XDelta", 0);
 	_ydelta = g_gui.xmlEval()->getVar("Globals.Tooltip.YDelta", 0);
@@ -52,8 +50,8 @@ void Tooltip::setup(Dialog *parent, Widget *widget, int x, int y) {
 	_w = tooltipFont->wordWrapText(widget->getTooltip(), _maxWidth - _xpadding * 2, _wrappedLines) + _xpadding * 2;
 	_h = (tooltipFont->getFontHeight() + 2) * _wrappedLines.size() + _ypadding * 2;
 
-	_x = MIN<int16>(parent->_x + x + _xdelta + _xpadding, g_system->getOverlayWidth() - _w - _xpadding * 2);
-	_y = MIN<int16>(parent->_y + y + _ydelta + _ypadding, g_system->getOverlayHeight() - _h - _ypadding * 2);
+	_x = MIN<int16>(x + _xdelta + _xpadding, g_system->getOverlayWidth() - _w - _xpadding * 2);
+	_y = MIN<int16>(y + _ydelta + _ypadding, g_system->getOverlayHeight() - _h - _ypadding * 2);
 
 	if (ConfMan.hasKey("tts_enabled", "scummvm") &&
 			ConfMan.getBool("tts_enabled", "scummvm")) {
diff --git a/gui/Tooltip.h b/gui/Tooltip.h
index 981373a968b..32f33e7d74c 100644
--- a/gui/Tooltip.h
+++ b/gui/Tooltip.h
@@ -36,7 +36,7 @@ private:
 
 public:
 	Tooltip();
-	void setup(Dialog *parent, Widget *widget, int x, int y);
+	void setup(Widget *widget, int x, int y);
 	void drawDialog(DrawLayer layerToDraw, bool resetClipping = true) override;
 	void receivedFocus(int x = -1, int y = -1) override {}
 
diff --git a/gui/dialog.cpp b/gui/dialog.cpp
index 3c816725b5f..37eb9623fef 100644
--- a/gui/dialog.cpp
+++ b/gui/dialog.cpp
@@ -48,7 +48,6 @@ Dialog::Dialog(int x, int y, int w, int h, bool scale)
 	// started a 640x480 game with a non 1x scaler.
 	g_gui.checkScreenChange();
 
-	_mouseUpdatedOnFocus = true;
 	_result = -1;
 }
 
@@ -66,7 +65,6 @@ Dialog::Dialog(const Common::String &name)
 	// and bug #2903: "SCUMM: F5 crashes game (640x480)"
 	g_gui.checkScreenChange();
 
-	_mouseUpdatedOnFocus = true;
 	_result = -1;
 }
 
diff --git a/gui/dialog.h b/gui/dialog.h
index 31f79cb976d..3301032c1b7 100644
--- a/gui/dialog.h
+++ b/gui/dialog.h
@@ -56,11 +56,6 @@ protected:
 	Widget  *_dragWidget;
 	Widget 	*_tickleWidget;
 	bool	_visible;
-	// _mouseUpdatedOnFocus instructs gui-manager whether
-	// its lastMousePosition (time and x,y coordinates)
-	// should be updated, when this Dialog acquires focus.
-	// Default value is true.
-	bool    _mouseUpdatedOnFocus;
 
 	ThemeEngine::DialogBackground _backgroundType;
 
@@ -76,8 +71,6 @@ public:
 
 	bool	isVisible() const override	{ return _visible; }
 
-	bool    isMouseUpdatedOnFocus() const { return _mouseUpdatedOnFocus; }
-
 	void	releaseFocus() override;
 	void	setFocusWidget(Widget *widget);
 	Widget *getFocusWidget() { return _focusedWidget; }
@@ -124,7 +117,6 @@ protected:
 	Widget *findWidget(const char *name);
 	void removeWidget(Widget *widget) override;
 
-	void setMouseUpdatedOnFocus(bool mouseUpdatedOnFocus) { _mouseUpdatedOnFocus = mouseUpdatedOnFocus; }
 	void setDefaultFocusedWidget();
 
 	void setResult(int result) { _result = result; }
diff --git a/gui/gui-manager.cpp b/gui/gui-manager.cpp
index f78e95c72b4..8f10dbe8442 100644
--- a/gui/gui-manager.cpp
+++ b/gui/gui-manager.cpp
@@ -54,7 +54,7 @@ enum {
 
 // Constructor
 GuiManager::GuiManager() : CommandSender(nullptr), _redrawStatus(kRedrawDisabled), _stateIsSaved(false),
-	_cursorAnimateCounter(0), _cursorAnimateTimer(0), _tooltip(nullptr) {
+	_cursorAnimateCounter(0), _cursorAnimateTimer(0), _tooltip(nullptr), _lastMouseMoveTime(0), _globalMousePosition(-1, -1) {
 	_theme = nullptr;
 	_useStdCursor = false;
 
@@ -671,38 +671,30 @@ void GuiManager::runLoop() {
 		// Handle tooltip for the widget under the mouse cursor.
 		// 1. Only try to show a tooltip if the mouse cursor was actually moved
 		//    and sufficient time (kTooltipDelay) passed since mouse cursor rested in-place.
-		//    Note, Dialog objects acquiring or losing focus lead to a _lastMousePosition update,
-		//    which may lead to a change of its time and x,y coordinate values.
-		//    See: GuiManager::giveFocusToDialog()
-		//    We avoid updating _lastMousePosition when giving focus to the Tooltip object
-		//    by having the Tooltip objects set a false value for their (inherited) member
-		//    var _mouseUpdatedOnFocus (in Tooltip::setup()).
-		//    However, when the tooltip loses focus, _lastMousePosition will be updated.
-		//    If the mouse had stayed in the same position in the meantime,
-		//    then at the time of the tooltip losing focus
-		//    the _lastMousePosition.time will be new, but the x,y cordinates
-		//    will be the same as the stored ones in _lastTooltipShown.
 		// 2. If the mouse was moved but ended on the same (tooltip enabled) widget,
 		//    then delay showing the tooltip based on the value of kTooltipSameWidgetDelay.
 		uint32 systemMillisNowForTooltipCheck = _system->getMillis(true);
 		if (!_tooltip
-		    && (_lastTooltipShown.x != _lastMousePosition.x || _lastTooltipShown.y != _lastMousePosition.y)
-		    && systemMillisNowForTooltipCheck - _lastMousePosition.time > (uint32)kTooltipDelay
+		    && (_lastTooltipShown.x != _globalMousePosition.x || _lastTooltipShown.y != _globalMousePosition.y)
+		    && systemMillisNowForTooltipCheck - _lastMouseMoveTime > (uint32)kTooltipDelay
 		    && !activeDialog->isDragging()) {
-			Widget *wdg = activeDialog->findWidget(_lastMousePosition.x, _lastMousePosition.y);
-			if (wdg && (wdg->hasTooltip() || (wdg->getFlags() & WIDGET_DYN_TOOLTIP)) && !(wdg->getFlags() & WIDGET_PRESSED)
-			    && (_lastTooltipShown.wdg != wdg || systemMillisNowForTooltipCheck - _lastTooltipShown.time > (uint32)kTooltipSameWidgetDelay)) {
+			int16 relX = _globalMousePosition.x - activeDialog->_x,
+			      relY = _globalMousePosition.y - activeDialog->_y;
+			Widget *wdg = activeDialog->findWidget(relX, relY);
+			if (wdg &&
+			    (_lastTooltipShown.wdg != wdg || systemMillisNowForTooltipCheck - _lastTooltipShown.time > (uint32)kTooltipSameWidgetDelay) &&
+			    (wdg->hasTooltip() || (wdg->getFlags() & WIDGET_DYN_TOOLTIP)) && !(wdg->getFlags() & WIDGET_PRESSED)) {
 				_lastTooltipShown.time = systemMillisNowForTooltipCheck;
 				_lastTooltipShown.wdg  = wdg;
-				_lastTooltipShown.x = _lastMousePosition.x;
-				_lastTooltipShown.y = _lastMousePosition.y;
+				_lastTooltipShown.x = _globalMousePosition.x;
+				_lastTooltipShown.y = _globalMousePosition.y;
 				if (wdg->getType() != kEditTextWidget || activeDialog->getFocusWidget() != wdg) {
 					if (wdg->getFlags() & WIDGET_DYN_TOOLTIP)
-						wdg->handleTooltipUpdate(_lastMousePosition.x + activeDialog->_x - wdg->getAbsX(), _lastMousePosition.y + activeDialog->_y - wdg->getAbsY());
+						wdg->handleTooltipUpdate(_globalMousePosition.x - wdg->getAbsX(), _globalMousePosition.y - wdg->getAbsY());
 
 					if (wdg->hasTooltip()) {
 						Tooltip *tooltip = new Tooltip();
-						tooltip->setup(activeDialog, wdg, _lastMousePosition.x, _lastMousePosition.y);
+						tooltip->setup(wdg, _globalMousePosition.x, _globalMousePosition.y);
 						_tooltip = tooltip;
 						_tooltip->runModal();
 						// _tooltip is reset in closeTopDialog
@@ -936,14 +928,13 @@ void GuiManager::processEvent(const Common::Event &event, Dialog *const activeDi
 		activeDialog->handleKeyUp(event.kbd);
 		break;
 	case Common::EVENT_MOUSEMOVE:
-		_globalMousePosition.x = mouseX;
-		_globalMousePosition.y = event.mouse.y;
-		activeDialog->handleMouseMoved(mouse.x, mouse.y, 0);
-
-		if (mouse.x != _lastMousePosition.x || mouse.y != _lastMousePosition.y) {
-			setLastMousePos(mouse.x, mouse.y);
+		if (_globalMousePosition.x != mouseX || _globalMousePosition.y != event.mouse.y) {
+			_globalMousePosition.x = mouseX;
+			_globalMousePosition.y = event.mouse.y;
+			_lastMouseMoveTime = _system->getMillis(true);
 		}
 
+		activeDialog->handleMouseMoved(mouse.x, mouse.y, 0);
 		break;
 		// We don't distinguish between mousebuttons (for now at least)
 	case Common::EVENT_LBUTTONDOWN:
@@ -999,15 +990,6 @@ void GuiManager::giveFocusToDialog(Dialog *dialog) {
 	int16 dialogX = _globalMousePosition.x - dialog->_x;
 	int16 dialogY = _globalMousePosition.y - dialog->_y;
 	dialog->receivedFocus(dialogX, dialogY);
-	if (dialog->isMouseUpdatedOnFocus()) {
-		setLastMousePos(dialogX, dialogY);
-	}
-}
-
-void GuiManager::setLastMousePos(int16 x, int16 y) {
-	_lastMousePosition.x = x;
-	_lastMousePosition.y = y;
-	_lastMousePosition.time = _system->getMillis(true);
 }
 
 void GuiManager::setLanguageRTL() {
diff --git a/gui/gui-manager.h b/gui/gui-manager.h
index a4f0cec9429..bfb5eaea576 100644
--- a/gui/gui-manager.h
+++ b/gui/gui-manager.h
@@ -203,7 +203,9 @@ protected:
 		int16 x, y;	// Position of mouse when the click occurred
 		uint32 time;	// Time
 		int count;	// How often was it already pressed?
-	} _lastClick, _lastMousePosition, _globalMousePosition;
+	} _lastClick;
+	Common::Point _globalMousePosition;
+	uint32 _lastMouseMoveTime;
 
 	struct TooltipData {
 		TooltipData() : x(-1), y(-1), wdg(nullptr) { time = 0; }
@@ -246,7 +248,6 @@ protected:
 	void screenChange();
 
 	void giveFocusToDialog(Dialog *dialog);
-	void setLastMousePos(int16 x, int16 y);
 
 	void emptyTrash(Dialog *const activeDialog);
 };


Commit: 2c0ac2e45fb03fb0339fd481b874371bb04d24c8
    https://github.com/scummvm/scummvm/commit/2c0ac2e45fb03fb0339fd481b874371bb04d24c8
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2026-09-09T00:31:37+03:00

Commit Message:
GUI: Rework tickle system

Allow a widget to register for tickles.
If it has been registered, assume it is interested (so no need to set
the WIDGET_WANT_TICKLE flag).

Remove the automatic registration for widgets with scrollbars and make
these widgets register by themselves.
Also make them unregister when kinetic scrolling is done and when they
are deleted.

A dragging widget now also receives tickles in order to limit focus to
keyboard matters only.

Cleanup various nefarious changes in the tickle and focus system.

Changed paths:
    gui/dialog.cpp
    gui/dialog.h
    gui/helpdialog.cpp
    gui/helpdialog.h
    gui/object.h
    gui/widget.cpp
    gui/widget.h
    gui/widgets/grid.cpp
    gui/widgets/grid.h
    gui/widgets/groupedlist.cpp
    gui/widgets/list.cpp
    gui/widgets/list.h
    gui/widgets/richtext.cpp
    gui/widgets/richtext.h
    gui/widgets/scrollbar.h
    gui/widgets/scrollcontainer.cpp
    gui/widgets/scrollcontainer.h
    gui/widgets/tab.cpp
    gui/widgets/tab.h


diff --git a/gui/dialog.cpp b/gui/dialog.cpp
index 37eb9623fef..9c1ceb10a80 100644
--- a/gui/dialog.cpp
+++ b/gui/dialog.cpp
@@ -123,7 +123,8 @@ void Dialog::lostFocus() {
 	}
 
 	if (_tickleWidget) {
-		_tickleWidget->lostFocus();
+		_tickleWidget->cancelTickle();
+		_tickleWidget = nullptr;
 	}
 }
 
@@ -269,21 +270,9 @@ void Dialog::handleMouseWheel(int x, int y, int direction) {
 	Widget *w = findWidget(x, y);
 	if (!w)
 		w = _focusedWidget;
-	if (w) {
-		w->handleMouseWheel(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y), direction);
-		// Find the scrollable ancestor to set as the tickle target
-		Widget *scrollable = w;
-		while (scrollable) {
-			if (scrollable->hasVisibleScrollBar()) {
-				setTickleWidget(scrollable);
-				break;
-			}
-			if (scrollable->_boss == this)		
-				break;
 
-			scrollable = static_cast<Widget *>(scrollable->_boss);
-		}
-	}
+	if (w)
+		w->handleMouseWheel(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y), direction);
 
 	_handlingMouseWheel = false;
 }
@@ -394,12 +383,22 @@ void Dialog::handleMouseMoved(int x, int y, int button) {
 }
 
 void Dialog::handleTickle() {
+	if (_tickleWidget)
+		_tickleWidget->handleTickle();
+
 	// Focused widget receives tickle notifications
-	if (_focusedWidget && _focusedWidget->getFlags() & WIDGET_WANT_TICKLE)
+	if (_focusedWidget &&
+	    _focusedWidget != _tickleWidget &&
+	    _focusedWidget->getFlags() & WIDGET_WANT_TICKLE)
 		_focusedWidget->handleTickle();
 
-	if (_tickleWidget && _tickleWidget->getFlags() & WIDGET_WANT_TICKLE)
-		_tickleWidget->handleTickle();
+	// Drag widget also receives tickle notifications
+	if (_dragWidget &&
+	    _dragWidget != _tickleWidget &&
+	    _dragWidget != _focusedWidget &&
+	    _dragWidget->getFlags() & WIDGET_WANT_TICKLE)
+		_dragWidget->handleTickle();
+
 }
 
 void Dialog::handleOtherEvent(const Common::Event &evt) {
@@ -448,4 +447,17 @@ void Dialog::removeWidget(Widget *del) {
 	GuiObject::removeWidget(del);
 }
 
+void Dialog::registerTickleWidget(Widget *widget) {
+	if (_tickleWidget && _tickleWidget != widget) {
+		_tickleWidget->cancelTickle();
+	}
+	_tickleWidget = widget;
+}
+
+void Dialog::unregisterTickleWidget(Widget *widget) {
+	if (_tickleWidget == widget) {
+		_tickleWidget = nullptr;
+	}
+}
+
 } // End of namespace GUI
diff --git a/gui/dialog.h b/gui/dialog.h
index 3301032c1b7..8605ec6cb73 100644
--- a/gui/dialog.h
+++ b/gui/dialog.h
@@ -77,10 +77,6 @@ public:
 
 	bool isDragging() const { return _dragWidget != nullptr; }
 
-	void setTickleWidget(Widget *widget) { _tickleWidget = widget; }
-	void unSetTickleWidget() { _tickleWidget = nullptr; }
-	Widget *getTickleWidget() { return _tickleWidget; }
-
 	void reflowLayout() override;
 	virtual void lostFocus();
 	virtual void receivedFocus(int x = -1, int y = -1) { if (x >= 0 && y >= 0) handleMouseMoved(x, y, 0); }
@@ -119,6 +115,9 @@ protected:
 
 	void setDefaultFocusedWidget();
 
+	void registerTickleWidget(Widget *widget) override;
+	void unregisterTickleWidget(Widget *widget) override;
+
 	void setResult(int result) { _result = result; }
 	int getResult() const { return _result; }
 };
diff --git a/gui/helpdialog.cpp b/gui/helpdialog.cpp
index 635d3c8c598..07ce8b2b973 100644
--- a/gui/helpdialog.cpp
+++ b/gui/helpdialog.cpp
@@ -156,8 +156,4 @@ void HelpDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
 	}
 }
 
-void HelpDialog::handleTickle() {
-	_tab->handleTickle();
-}
-
 } // End of namespace GUI
diff --git a/gui/helpdialog.h b/gui/helpdialog.h
index 3e8b0a34ff3..59ed5208ffb 100644
--- a/gui/helpdialog.h
+++ b/gui/helpdialog.h
@@ -39,7 +39,6 @@ public:
 	HelpDialog();
 
 	void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
-	void handleTickle() override;
 
 private:
 	void addTabs(const char * const *tabs);
diff --git a/gui/object.h b/gui/object.h
index a1c4e8d3eb8..032e3dd0078 100644
--- a/gui/object.h
+++ b/gui/object.h
@@ -103,9 +103,13 @@ public:
 	 */
 	virtual Common::Rect getClipRect() const;
 
-	virtual void handleMouseWheel(int x, int y, int direction) {};
 protected:
-	virtual void	releaseFocus() = 0;
+	virtual void handleMouseWheel(int x, int y, int direction) = 0;
+	virtual void releaseFocus() = 0;
+	virtual void registerTickleWidget(Widget *widget) = 0;
+	// As this function can be called from a widget destructor, don't make it pure virtual to avoid it
+	// being called when GuiObject destroys its widgets chain
+	virtual void unregisterTickleWidget(Widget *widget) {}
 };
 
 } // End of namespace GUI
diff --git a/gui/widget.cpp b/gui/widget.cpp
index f11cee607e7..dcd2c983807 100644
--- a/gui/widget.cpp
+++ b/gui/widget.cpp
@@ -167,16 +167,6 @@ Widget *Widget::findWidgetInChain(Widget *w, uint32 type) {
 	return nullptr;
 }
 
-bool Widget::hasVisibleScrollBar() const {
-	Widget *w = _firstWidget;
-	while (w) {
-		if (w->getType() == kScrollBarWidget && w->isVisible())
-			return true;
-		w = w->_next;
-	}
-	return false;
-}
-
 bool Widget::containsWidgetInChain(Widget *w, Widget *search) {
 	while (w) {
 		if (w == search || w->containsWidget(search))
diff --git a/gui/widget.h b/gui/widget.h
index 407dd460cbc..abcac03aa88 100644
--- a/gui/widget.h
+++ b/gui/widget.h
@@ -149,6 +149,8 @@ public:
 	virtual void handleTooltipUpdate(int x, int y) {}
 	virtual void handleTickle() {}
 
+	virtual void cancelTickle() {}
+
 	/** Mark the widget and its children as dirty so they are redrawn on the next screen update */
 	virtual void markAsDirty();
 
@@ -161,8 +163,6 @@ public:
 
 	uint32 getType() const { return _type; }
 
-	// Check if the widget or its children contain a visible scrollbar
-	virtual bool hasVisibleScrollBar() const;
 	void setFlags(int flags);
 	void clearFlags(int flags);
 	int getFlags() const		{ return _flags; }
@@ -199,6 +199,10 @@ protected:
 
 	void releaseFocus() override { assert(_boss); _boss->releaseFocus(); }
 
+	// Tickles are handled in Dialog
+	void registerTickleWidget(Widget *widget) override { assert(_boss); _boss->registerTickleWidget(widget); }
+	void unregisterTickleWidget(Widget *widget) override { assert(_boss); _boss->unregisterTickleWidget(widget); }
+
 	// By default, delegate unhandled commands to the boss
 	void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override { assert(_boss); _boss->handleCommand(sender, cmd, data); }
 };
diff --git a/gui/widgets/grid.cpp b/gui/widgets/grid.cpp
index f4beb0514fa..16d329dbcc3 100644
--- a/gui/widgets/grid.cpp
+++ b/gui/widgets/grid.cpp
@@ -544,7 +544,7 @@ GridWidget::GridWidget(GuiObject *boss, const Common::String &name)
 	_filterMatcher = GridWidgetDefaultMatcher;
 	_filterMatcherArg = nullptr;
 
-	setFlags(getFlags() | WIDGET_TRACK_MOUSE | WIDGET_WANT_TICKLE | WIDGET_RETAIN_FOCUS);
+	setFlags(getFlags() | WIDGET_TRACK_MOUSE);
 }
 
 GridWidget::~GridWidget() {
@@ -561,6 +561,7 @@ GridWidget::~GridWidget() {
 	_platformIconsAlpha.clear();
 	_languageIconsAlpha.clear();
 	_extraIconsAlpha.clear();
+	unregisterTickleWidget(this);
 	delete _fluidScroller;
 }
 
@@ -1035,6 +1036,7 @@ void GridWidget::handleMouseWheel(int x, int y, int direction) {
 		return;
 
 	_fluidScroller->handleMouseWheel(direction);
+	registerTickleWidget(this);
 }
 
 void GridWidget::handleMouseDown(int x, int y, int button, int clickCount) {
@@ -1056,6 +1058,7 @@ void GridWidget::handleMouseUp(int x, int y, int button, int clickCount) {
 
 	_isMouseDown = false;
 	_isDragging = false;
+	_dragStartY = _dragLastY = 0;
 	_selectionPending = false;
 
 	if (wasPending && !wasDragging && !_wasAnimating) {
@@ -1065,8 +1068,10 @@ void GridWidget::handleMouseUp(int x, int y, int button, int clickCount) {
 			((GridItemWidget *)w)->doSelection();
 	}
 
-	if (wasDragging)
+	if (wasDragging) {
 		_fluidScroller->startFling();
+		registerTickleWidget(this);
+	}
 	_wasAnimating = false;
 }
 
@@ -1104,16 +1109,11 @@ void GridWidget::applyScrollPos() {
 }
 
 void GridWidget::handleTickle() {
-	if (_fluidScroller->update(g_system->getMillis(), _scrollPos))
+	if (_fluidScroller->update(g_system->getMillis(), _scrollPos)) {
 		applyScrollPos();
-}
-
-bool GridWidget::handleKeyDown(Common::KeyState state) {
-	return false;
-}
-
-bool GridWidget::handleKeyUp(Common::KeyState state) {
-	return false;
+	} else {
+		unregisterTickleWidget(this);
+	}
 }
 
 void GridWidget::lostFocusWidget() {
@@ -1122,6 +1122,10 @@ void GridWidget::lostFocusWidget() {
 	_wasAnimating = false;
 }
 
+void GridWidget::cancelTickle() {
+	_fluidScroller->stopAnimation();
+}
+
 void GridWidget::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
 	// Work in progress
 	switch (cmd) {
diff --git a/gui/widgets/grid.h b/gui/widgets/grid.h
index e2187eb294b..6b23a525030 100644
--- a/gui/widgets/grid.h
+++ b/gui/widgets/grid.h
@@ -252,11 +252,9 @@ public:
 	void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
 	void reflowLayout() override;
 
-	bool wantsFocus() override { return true; }
-
 	void lostFocusWidget() override;
-	bool handleKeyDown(Common::KeyState state) override;
-	bool handleKeyUp(Common::KeyState state) override;
+	void cancelTickle() override;
+
 	void openTrayAtSelected();
 	void scrollBarRecalc();
 
diff --git a/gui/widgets/groupedlist.cpp b/gui/widgets/groupedlist.cpp
index 91f0a8cdca4..b4e60dc8ee8 100644
--- a/gui/widgets/groupedlist.cpp
+++ b/gui/widgets/groupedlist.cpp
@@ -281,8 +281,10 @@ void GroupedListWidget::handleMouseDown(int x, int y, int button, int clickCount
 
 void GroupedListWidget::handleMouseUp(int x, int y, int button, int clickCount) {
 	if (button == 1 || button == 2) {
-		if (_isMouseDown && button == 1 && _isDragging)
+		if (_isMouseDown && button == 1 && _isDragging) {
 			_fluidScroller->startFling();
+			registerTickleWidget(this);
+		}
 
 		if (_isMouseDown && !_isDragging && !_wasAnimating) {
 			int newSelectedItem = findItem(x, y);
@@ -364,6 +366,7 @@ void GroupedListWidget::handleMouseWheel(int x, int y, int direction) {
 		return;
 
 	_fluidScroller->handleMouseWheel(direction);
+	registerTickleWidget(this);
 }
 
 void GroupedListWidget::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
diff --git a/gui/widgets/list.cpp b/gui/widgets/list.cpp
index 64219878cc4..1860c02391f 100644
--- a/gui/widgets/list.cpp
+++ b/gui/widgets/list.cpp
@@ -144,6 +144,7 @@ ListWidget::ListWidget(Dialog *boss, int x, int y, int w, int h, bool scale, con
 }
 
 ListWidget::~ListWidget() {
+	unregisterTickleWidget(this);
 	delete _fluidScroller;
 }
 
@@ -368,9 +369,15 @@ void ListWidget::handleTickle() {
 
 	if (_fluidScroller->update(g_system->getMillis(), _scrollPos)) {
 		applyScrollPos();
+	} else {
+		unregisterTickleWidget(this);
 	}
 }
 
+void ListWidget::cancelTickle() {
+	_fluidScroller->stopAnimation();
+}
+
 void ListWidget::applyScrollPos() {
 	const int lineHeight = kLineHeight + _itemSpacing;
 	_currentPos = (int)(_scrollPos / lineHeight);
@@ -399,8 +406,10 @@ void ListWidget::handleMouseDown(int x, int y, int button, int clickCount) {
 
 void ListWidget::handleMouseUp(int x, int y, int button, int clickCount) {
 	if (button == 1 || button == 2) {
-		if (_isMouseDown && button == 1 && _isDragging)
+		if (_isMouseDown && button == 1 && _isDragging) {
 			_fluidScroller->startFling();
+			registerTickleWidget(this);
+		}
 
 		if (_isMouseDown && !_isDragging && !_wasAnimating) {
 			// Perform selection
@@ -468,6 +477,7 @@ void ListWidget::handleMouseWheel(int x, int y, int direction) {
 		return;
 
 	_fluidScroller->handleMouseWheel(direction);
+	registerTickleWidget(this);
 }
 
 void ListWidget::handleMouseMoved(int x, int y, int button) {
diff --git a/gui/widgets/list.h b/gui/widgets/list.h
index 42fcbf89f69..27575edfc1d 100644
--- a/gui/widgets/list.h
+++ b/gui/widgets/list.h
@@ -180,6 +180,8 @@ public:
 	bool handleKeyUp(Common::KeyState state) override;
 	void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
 
+	void cancelTickle() override;
+
 	void reflowLayout() override;
 
 	bool wantsFocus() override { return true; }
diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index 8c7e6437e6c..520aa5bc687 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -66,7 +66,7 @@ RichTextWidget::RichTextWidget(GuiObject *boss, const Common::String &name, cons
 }
 
 void RichTextWidget::init() {
-	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_TRACK_MOUSE | WIDGET_DYN_TOOLTIP | WIDGET_WANT_TICKLE | WIDGET_RETAIN_FOCUS);
+	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_TRACK_MOUSE | WIDGET_DYN_TOOLTIP);
 
 	_type = kRichTextWidget;
 
@@ -99,6 +99,8 @@ RichTextWidget::~RichTextWidget() {
 	if (_cachedTextSurface)
 		_cachedTextSurface->free();
 	delete _cachedTextSurface;
+
+	unregisterTickleWidget(this);
 	delete _fluidScroller;
 }
 
@@ -106,7 +108,7 @@ void RichTextWidget::handleMouseWheel(int x, int y, int direction) {
 	if (!_verticalScroll->isVisible())
 		return;
 	_fluidScroller->handleMouseWheel(direction);
-	applyScrollPos();
+	registerTickleWidget(this);
 }
 
 void RichTextWidget::handleMouseDown(int x, int y, int button, int clickCount) {
@@ -115,18 +117,13 @@ void RichTextWidget::handleMouseDown(int x, int y, int button, int clickCount) {
 }
 
 void RichTextWidget::handleMouseUp(int x, int y, int button, int clickCount) {
-	if (_isDragging)
+	_mouseDownY = _mouseDownStartY = 0;
+	if (_isDragging) {
 		_fluidScroller->startFling();
-
-	// Allow some tiny finger slipping
-	if (ABS(_mouseDownY - _mouseDownStartY) > 5 || _isDragging) {
-		_mouseDownY = _mouseDownStartY = 0;
+		registerTickleWidget(this);
 		_isDragging = false;
-
 		return;
 	}
-
-	_mouseDownY = _mouseDownStartY = 0;
 	_isDragging = false;
 
 	if (!_txtWnd)
@@ -166,8 +163,15 @@ void RichTextWidget::handleMouseMoved(int x, int y, int button) {
 }
 
 void RichTextWidget::handleTickle() {
-	if (_fluidScroller->update(g_system->getMillis(), _scrollPos))
+	if (_fluidScroller->update(g_system->getMillis(), _scrollPos)) {
 		applyScrollPos();
+	} else {
+		unregisterTickleWidget(this);
+	}
+}
+
+void RichTextWidget::cancelTickle() {
+	_fluidScroller->stopAnimation();
 }
 
 void RichTextWidget::applyScrollPos() {
diff --git a/gui/widgets/richtext.h b/gui/widgets/richtext.h
index 9782831175b..e11248d46f9 100644
--- a/gui/widgets/richtext.h
+++ b/gui/widgets/richtext.h
@@ -78,10 +78,11 @@ public:
 	void handleTickle() override;
 	void handleTooltipUpdate(int x, int y) override;
 
+	void cancelTickle() override;
+
 	void markAsDirty() override;
 
 	bool containsWidget(Widget *) const override;
-	bool wantsFocus() override { return true; }
 
 	void setImageArchive(const Common::Path &fname) { _imageArchive = fname; }
 
diff --git a/gui/widgets/scrollbar.h b/gui/widgets/scrollbar.h
index 49c98154293..bae13f06a29 100644
--- a/gui/widgets/scrollbar.h
+++ b/gui/widgets/scrollbar.h
@@ -71,7 +71,6 @@ public:
 	void handleMouseEntered(int button) override	{ setFlags(WIDGET_HILITED); }
 	void handleMouseLeft(int button) override	{ clearFlags(WIDGET_HILITED); _part = kNoPart; markAsDirty(); }
 	void handleTickle() override;
-	bool wantsFocus() override { return true; }
 
 	// FIXME - this should be private, but then we also have to add accessors
 	// for _numEntries, _entriesPerPage and _currentPos. This again leads to the question:
diff --git a/gui/widgets/scrollcontainer.cpp b/gui/widgets/scrollcontainer.cpp
index 352e5c4690a..04fd8d59539 100644
--- a/gui/widgets/scrollcontainer.cpp
+++ b/gui/widgets/scrollcontainer.cpp
@@ -39,7 +39,7 @@ ScrollContainerWidget::ScrollContainerWidget(GuiObject *boss, const Common::Stri
 }
 
 void ScrollContainerWidget::init() {
-	setFlags(WIDGET_ENABLED | WIDGET_TRACK_MOUSE | WIDGET_WANT_TICKLE | WIDGET_RETAIN_FOCUS);
+	setFlags(WIDGET_ENABLED | WIDGET_TRACK_MOUSE);
 	_type = kScrollContainerWidget;
 	_backgroundType = ThemeEngine::kWidgetBackgroundPlain;
 	_verticalScroll = new ScrollBarWidget(this, _w, 0, 16, _h);
@@ -58,6 +58,7 @@ void ScrollContainerWidget::handleMouseWheel(int x, int y, int direction) {
 		return;
 
 	_fluidScroller->handleMouseWheel(direction);
+	registerTickleWidget(this);
 }
 
 void ScrollContainerWidget::handleMouseDown(int x, int y, int button, int clickCount) {
@@ -102,8 +103,15 @@ void ScrollContainerWidget::handleMouseMoved(int x, int y, int button) {
 }
 
 void ScrollContainerWidget::handleTickle() {
-	if (_fluidScroller->update(g_system->getMillis(), _scrollPos))
+	if (_fluidScroller->update(g_system->getMillis(), _scrollPos)) {
 		applyScrollPos();
+	} else {
+		unregisterTickleWidget(this);
+	}
+}
+
+void ScrollContainerWidget::cancelTickle() {
+	_fluidScroller->stopAnimation();
 }
 
 void ScrollContainerWidget::applyScrollPos() {
@@ -121,8 +129,10 @@ void ScrollContainerWidget::handleMouseUp(int x, int y, int button, int clickCou
 	Widget *child = _childUnderMouse;
 	bool isDragging = _isDragging;
 
-	if (_isMouseDown && _isDragging)
+	if (_isMouseDown && _isDragging) {
 		_fluidScroller->startFling();
+		registerTickleWidget(this);
+	}
 
 	_mouseDownY = _mouseDownStartY = 0;
 	_isMouseDown = false;
@@ -172,6 +182,7 @@ void ScrollContainerWidget::recalc() {
 
 
 ScrollContainerWidget::~ScrollContainerWidget() {
+	unregisterTickleWidget(this);
 	delete _fluidScroller;
 }
 
diff --git a/gui/widgets/scrollcontainer.h b/gui/widgets/scrollcontainer.h
index d62f4a31beb..93524a52deb 100644
--- a/gui/widgets/scrollcontainer.h
+++ b/gui/widgets/scrollcontainer.h
@@ -70,13 +70,14 @@ public:
 	void lostFocusWidget() override;
 	void handleTickle() override;
 
+	void cancelTickle() override;
+
 	// We overload getChildY to make sure child widgets are positioned correctly.
 	// Essentially this compensates for the space taken up by the tab title header.
 	int16	getChildX() const override;
 	int16	getChildY() const override;
 	uint16	getWidth() const override;
 	uint16	getHeight() const override;
-	bool wantsFocus() override { return true; }
 
 	void draw() override;
 	void markAsDirty() override;
diff --git a/gui/widgets/tab.cpp b/gui/widgets/tab.cpp
index a36fd3deff3..83354982ff0 100644
--- a/gui/widgets/tab.cpp
+++ b/gui/widgets/tab.cpp
@@ -62,7 +62,7 @@ TabWidget::TabWidget(GuiObject *boss, const Common::String &name, ThemeEngine::T
 }
 
 void TabWidget::init() {
-	setFlags(WIDGET_ENABLED | WIDGET_TRACK_MOUSE | WIDGET_WANT_TICKLE);
+	setFlags(WIDGET_ENABLED | WIDGET_TRACK_MOUSE);
 	_type = kTabWidget;
 	_activeTab = -1;
 	_firstVisibleTab = 0;
@@ -300,15 +300,6 @@ void TabWidget::handleMouseWheel(int x, int y, int direction) {
 	}
 }
 
-void TabWidget::handleTickle() {
-	Widget *w = _firstWidget;
-	while (w) {
-		if (w->getFlags() & WIDGET_WANT_TICKLE)
-			w->handleTickle();
-		w = w->next();
-	}
-}
-
 void TabWidget::adjustTabs(int value) {
 	// Determine which tab is next
 	int tabID = _activeTab + value;
diff --git a/gui/widgets/tab.h b/gui/widgets/tab.h
index 2026d71ff18..26f531531a4 100644
--- a/gui/widgets/tab.h
+++ b/gui/widgets/tab.h
@@ -113,7 +113,6 @@ public:
 	void handleMouseLeft(int button) override { _lastRead = -1; };
 	bool handleKeyDown(Common::KeyState state) override;
 	void handleMouseWheel(int x, int y, int direction) override;
-	void handleTickle() override;
 	void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
 	virtual int getFirstVisible() const;
 	virtual void setFirstVisible(int tabID, bool adjustIfRoom = false);


Commit: 056f4c9e64e57bc49e678bcebe0fcb0d4b9834dc
    https://github.com/scummvm/scummvm/commit/056f4c9e64e57bc49e678bcebe0fcb0d4b9834dc
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2026-09-09T00:31:37+03:00

Commit Message:
GUI: Remove useless WIDGET_RETAIN_FOCUS flag in PopUpWidget

PopUpWidget doesn't override wantsFocus so it cannot retain the focus.

Changed paths:
    gui/widgets/popup.cpp


diff --git a/gui/widgets/popup.cpp b/gui/widgets/popup.cpp
index fc911f96efe..16371967a8c 100644
--- a/gui/widgets/popup.cpp
+++ b/gui/widgets/popup.cpp
@@ -431,7 +431,7 @@ void PopUpDialog::drawMenuEntry(int entry, bool hilite) {
 
 PopUpWidget::PopUpWidget(GuiObject *boss, const Common::String &name, const Common::U32String &tooltip, uint32 cmd)
 	: Widget(boss, name, tooltip), CommandSender(boss) {
-	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_IGNORE_DRAG);
+	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_IGNORE_DRAG);
 	_type = kPopUpWidget;
 	_cmd = cmd;
 
@@ -441,7 +441,7 @@ PopUpWidget::PopUpWidget(GuiObject *boss, const Common::String &name, const Comm
 
 PopUpWidget::PopUpWidget(GuiObject *boss, int x, int y, int w, int h, const Common::U32String &tooltip, uint32 cmd)
 	: Widget(boss, x, y, w, h, tooltip), CommandSender(boss) {
-	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_IGNORE_DRAG);
+	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_IGNORE_DRAG);
 	_type = kPopUpWidget;
 	_cmd = cmd;
 


Commit: be0871d2db8f5496a44a8d3b9c12ab045c6e1776
    https://github.com/scummvm/scummvm/commit/be0871d2db8f5496a44a8d3b9c12ab045c6e1776
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2026-09-09T00:31:37+03:00

Commit Message:
GUI: Simplify focused widget logic

Focus is now only related to keyboard, tickles and (in rare cases) wheel
events.
WIDGET_RETAIN_FOCUS is deleted as keyboard focus is independent of mouse
button state and is not released on mouse up anymore.

All of this was a remnant from the time where there was no _dragWidget
according to a comment in Dialog::handleMouseDown introduced in c0df1c2df203.

Changed paths:
    gui/dialog.cpp
    gui/widget.h
    gui/widgets/edittext.cpp
    gui/widgets/grid.cpp
    gui/widgets/grid.h
    gui/widgets/list.cpp
    gui/widgets/list.h
    gui/widgets/richtext.cpp
    gui/widgets/richtext.h
    gui/widgets/scrollcontainer.cpp
    gui/widgets/scrollcontainer.h


diff --git a/gui/dialog.cpp b/gui/dialog.cpp
index 9c1ceb10a80..61e00f9272d 100644
--- a/gui/dialog.cpp
+++ b/gui/dialog.cpp
@@ -118,7 +118,7 @@ void Dialog::reflowLayout() {
 
 void Dialog::lostFocus() {
 	if (_dragWidget) {
-		_dragWidget->lostFocus();
+		_dragWidget->cancelDrag();
 		_dragWidget = nullptr;
 	}
 
@@ -231,15 +231,6 @@ void Dialog::handleMouseDown(int x, int y, int button, int clickCount) {
 void Dialog::handleMouseUp(int x, int y, int button, int clickCount) {
 	Widget *w;
 
-	if (_focusedWidget) {
-		//w = _focusedWidget;
-
-		// Lose focus on mouseup unless the widget requested to retain the focus
-		if (! (_focusedWidget->getFlags() & WIDGET_RETAIN_FOCUS )) {
-			releaseFocus();
-		}
-	}
-
 	if (_dragWidget)
 		w = _dragWidget;
 	else
@@ -333,28 +324,6 @@ void Dialog::handleKeyUp(Common::KeyState state) {
 void Dialog::handleMouseMoved(int x, int y, int button) {
 	Widget *w;
 
-	if (_focusedWidget && !_dragWidget) {
-		w = _focusedWidget;
-		int wx = w->getAbsX() - _x;
-		int wy = w->getAbsY() - _y;
-
-		// We still send mouseEntered/Left messages to the focused item
-		// (but to no other items).
-		bool mouseInFocusedWidget = (x >= wx && x < wx + w->_w && y >= wy && y < wy + w->_h);
-		if (mouseInFocusedWidget && _mouseWidget != w) {
-			if (_mouseWidget)
-				_mouseWidget->handleMouseLeft(button);
-			_mouseWidget = w;
-			w->handleMouseEntered(button);
-		} else if (!mouseInFocusedWidget && _mouseWidget == w) {
-			_mouseWidget = nullptr;
-			w->handleMouseLeft(button);
-		}
-
-		if (w->getFlags() & WIDGET_TRACK_MOUSE)
-			w->handleMouseMoved(x - wx, y - wy, button);
-	}
-
 	// We process mouseEntered/Left events if we don't have any
 	// currently active dragged widget or if the currently dragged widget
 	// does not want to be informed about the mouse mouse events.
diff --git a/gui/widget.h b/gui/widget.h
index abcac03aa88..3e595f1daef 100644
--- a/gui/widget.h
+++ b/gui/widget.h
@@ -47,17 +47,13 @@ enum {
 	WIDGET_CLEARBG		= 1 <<  5,
 	WIDGET_WANT_TICKLE	= 1 <<  7,
 	WIDGET_TRACK_MOUSE	= 1 <<  8,
-	// Retain focus on mouse up. By default widgets lose focus on mouseup,
-	// but some widgets might want to retain it - widgets where you enter
-	// text, for instance
-	WIDGET_RETAIN_FOCUS	= 1 <<  9,
 	// Usually widgets would lock mouse input when the user pressed the
 	// left mouse button till the user releases it.
 	// The PopUpWidget for example does not want this behavior, since the
 	// mouse down will open up a new dialog which silently eats the mouse
 	// up event for its own purposes.
-	WIDGET_IGNORE_DRAG	= 1 << 10,
-	WIDGET_DYN_TOOLTIP  = 1 << 11, // Widgets updates tooltip by coordinates
+	WIDGET_IGNORE_DRAG	= 1 << 9,
+	WIDGET_DYN_TOOLTIP  = 1 << 10, // Widgets updates tooltip by coordinates
 };
 
 enum {
@@ -149,6 +145,7 @@ public:
 	virtual void handleTooltipUpdate(int x, int y) {}
 	virtual void handleTickle() {}
 
+	virtual void cancelDrag() {}
 	virtual void cancelTickle() {}
 
 	/** Mark the widget and its children as dirty so they are redrawn on the next screen update */
diff --git a/gui/widgets/edittext.cpp b/gui/widgets/edittext.cpp
index 45ce306e899..b1cae209d0f 100644
--- a/gui/widgets/edittext.cpp
+++ b/gui/widgets/edittext.cpp
@@ -30,7 +30,7 @@ namespace GUI {
 
 EditTextWidget::EditTextWidget(GuiObject *boss, int x, int y, int w, int h, bool scale, const Common::U32String &text, const Common::U32String &tooltip, uint32 cmd, uint32 finishCmd, ThemeEngine::FontStyle font)
 	: EditableWidget(boss, x, y - 1, w, h + 2, scale, tooltip, cmd) {
-	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_WANT_TICKLE);
+	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_WANT_TICKLE);
 	_type = kEditTextWidget;
 	_finishCmd = finishCmd;
 
@@ -46,7 +46,7 @@ EditTextWidget::EditTextWidget(GuiObject *boss, int x, int y, int w, int h, cons
 
 EditTextWidget::EditTextWidget(GuiObject *boss, const Common::String &name, const Common::U32String &text, const Common::U32String &tooltip, uint32 cmd, uint32 finishCmd, ThemeEngine::FontStyle font)
 	: EditableWidget(boss, name, tooltip, cmd) {
-	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_WANT_TICKLE);
+	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_WANT_TICKLE);
 	_type = kEditTextWidget;
 	_finishCmd = finishCmd;
 
diff --git a/gui/widgets/grid.cpp b/gui/widgets/grid.cpp
index 16d329dbcc3..f84ed457442 100644
--- a/gui/widgets/grid.cpp
+++ b/gui/widgets/grid.cpp
@@ -1075,6 +1075,17 @@ void GridWidget::handleMouseUp(int x, int y, int button, int clickCount) {
 	_wasAnimating = false;
 }
 
+void GridWidget::cancelDrag() {
+	if (_isDragging) {
+		_fluidScroller->stopAnimation();
+	}
+	_isMouseDown = false;
+	_isDragging = false;
+	_dragStartY = _dragLastY = 0;
+	_selectionPending = false;
+	_wasAnimating = false;
+}
+
 void GridWidget::handleMouseMoved(int x, int y, int button) {
 	if (!_isMouseDown || !_scrollBar->isVisible())
 		return;
@@ -1116,12 +1127,6 @@ void GridWidget::handleTickle() {
 	}
 }
 
-void GridWidget::lostFocusWidget() {
-	_isMouseDown = _isDragging = false;
-	_dragStartY = _dragLastY = 0;
-	_wasAnimating = false;
-}
-
 void GridWidget::cancelTickle() {
 	_fluidScroller->stopAnimation();
 }
diff --git a/gui/widgets/grid.h b/gui/widgets/grid.h
index 6b23a525030..1f9ee2ab44a 100644
--- a/gui/widgets/grid.h
+++ b/gui/widgets/grid.h
@@ -252,7 +252,7 @@ public:
 	void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
 	void reflowLayout() override;
 
-	void lostFocusWidget() override;
+	void cancelDrag() override;
 	void cancelTickle() override;
 
 	void openTrayAtSelected();
diff --git a/gui/widgets/list.cpp b/gui/widgets/list.cpp
index 1860c02391f..783a3d41ccd 100644
--- a/gui/widgets/list.cpp
+++ b/gui/widgets/list.cpp
@@ -46,7 +46,7 @@ ListWidget::ListWidget(Dialog *boss, const Common::String &name, const Common::U
 	_scrollBar = new ScrollBarWidget(this, _w - _scrollBarWidth, 0, _scrollBarWidth, _h);
 	_scrollBar->setTarget(this);
 
-	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_WANT_TICKLE | WIDGET_TRACK_MOUSE);
+	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_WANT_TICKLE | WIDGET_TRACK_MOUSE);
 	_type = kListWidget;
 	_editMode = false;
 	_numberingMode = kListNumberingOne;
@@ -98,7 +98,7 @@ ListWidget::ListWidget(Dialog *boss, int x, int y, int w, int h, bool scale, con
 	_scrollBar = new ScrollBarWidget(this, _w - _scrollBarWidth, 0, _scrollBarWidth, _h);
 	_scrollBar->setTarget(this);
 
-	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_RETAIN_FOCUS | WIDGET_WANT_TICKLE | WIDGET_TRACK_MOUSE);
+	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_WANT_TICKLE | WIDGET_TRACK_MOUSE);
 	_type = kListWidget;
 	_editMode = false;
 	_numberingMode = kListNumberingOne;
@@ -472,6 +472,15 @@ void ListWidget::handleMouseUp(int x, int y, int button, int clickCount) {
 	_wasAnimating = false;
 }
 
+void ListWidget::cancelDrag() {
+	if (_isDragging) {
+		_fluidScroller->stopAnimation();
+	}
+	_dragStartY = _dragLastY = 0;
+	_isMouseDown = false;
+	_isDragging = false;
+}
+
 void ListWidget::handleMouseWheel(int x, int y, int direction) {
 	if (!_scrollBar->isVisible())
 		return;
@@ -845,9 +854,6 @@ void ListWidget::receivedFocusWidget() {
 
 void ListWidget::lostFocusWidget() {
 	_inversion = ThemeEngine::kTextInversion;
-	_isMouseDown = _isDragging = false;
-	_dragStartY = _dragLastY = 0;
-
 	// If we lose focus, we simply forget the user changes
 	_editMode = false;
 	g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false);
diff --git a/gui/widgets/list.h b/gui/widgets/list.h
index 27575edfc1d..9161868fb12 100644
--- a/gui/widgets/list.h
+++ b/gui/widgets/list.h
@@ -180,6 +180,7 @@ public:
 	bool handleKeyUp(Common::KeyState state) override;
 	void handleCommand(CommandSender *sender, uint32 cmd, uint32 data) override;
 
+	void cancelDrag() override;
 	void cancelTickle() override;
 
 	void reflowLayout() override;
diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index 520aa5bc687..def58d58627 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -135,6 +135,14 @@ void RichTextWidget::handleMouseUp(int x, int y, int button, int clickCount) {
 		g_system->openUrl(link);
 }
 
+void RichTextWidget::cancelDrag() {
+	if (_isDragging) {
+		_fluidScroller->stopAnimation();
+	}
+	_mouseDownY = _mouseDownStartY = 0;
+	_isDragging = false;
+}
+
 void RichTextWidget::handleMouseMoved(int x, int y, int button) {
 	if (_txtWnd) {
 		Common::String link = _txtWnd->getMouseLink(x - _innerMargin + _scrolledX, y - _innerMargin + _scrolledY).encode();
@@ -396,11 +404,6 @@ void RichTextWidget::markAsDirty() {
 	}
 }
 
-void RichTextWidget::lostFocusWidget() {
-	_mouseDownY = _mouseDownStartY = 0;
-	_isDragging = false;
-}
-
 bool RichTextWidget::containsWidget(Widget *w) const {
 	if (w == _verticalScroll || _verticalScroll->containsWidget(w))
 		return true;
diff --git a/gui/widgets/richtext.h b/gui/widgets/richtext.h
index e11248d46f9..0bac66dd63b 100644
--- a/gui/widgets/richtext.h
+++ b/gui/widgets/richtext.h
@@ -74,10 +74,10 @@ public:
 	void handleMouseDown(int x, int y, int button, int clickCount) override;
 	void handleMouseUp(int x, int y, int button, int clickCount) override;
 	void handleMouseMoved(int x, int y, int button) override;
-	void lostFocusWidget() override;
 	void handleTickle() override;
 	void handleTooltipUpdate(int x, int y) override;
 
+	void cancelDrag() override;
 	void cancelTickle() override;
 
 	void markAsDirty() override;
diff --git a/gui/widgets/scrollcontainer.cpp b/gui/widgets/scrollcontainer.cpp
index 04fd8d59539..8e6b066a302 100644
--- a/gui/widgets/scrollcontainer.cpp
+++ b/gui/widgets/scrollcontainer.cpp
@@ -147,6 +147,15 @@ void ScrollContainerWidget::handleMouseUp(int x, int y, int button, int clickCou
 	_wasAnimating = false;
 }
 
+void ScrollContainerWidget::cancelDrag() {
+	if (_isDragging) {
+		_fluidScroller->stopAnimation();
+	}
+	_mouseDownY = _mouseDownStartY = 0;
+	_isMouseDown = false;
+	_isDragging = false;
+}
+
 void ScrollContainerWidget::recalc() {
 	_scrollbarWidth = g_gui.xmlEval()->getVar("Globals.Scrollbar.Width", 0);
 	_limitH = _h;
@@ -265,12 +274,6 @@ void ScrollContainerWidget::markAsDirty() {
 	}
 }
 
-void ScrollContainerWidget::lostFocusWidget() {
-	_isMouseDown = _isDragging = false;
-	_mouseDownY = _mouseDownStartY = 0;
-	_wasAnimating = false;
-}
-
 bool ScrollContainerWidget::containsWidget(Widget *w) const {
 	if (w == _verticalScroll || _verticalScroll->containsWidget(w))
 		return true;
diff --git a/gui/widgets/scrollcontainer.h b/gui/widgets/scrollcontainer.h
index 93524a52deb..e28b7bd9b35 100644
--- a/gui/widgets/scrollcontainer.h
+++ b/gui/widgets/scrollcontainer.h
@@ -67,9 +67,9 @@ public:
 	void handleMouseDown(int x, int y, int button, int clickCount) override;
 	void handleMouseUp(int x, int y, int button, int clickCount) override;
 	void handleMouseMoved(int x, int y, int button) override;
-	void lostFocusWidget() override;
 	void handleTickle() override;
 
+	void cancelDrag() override;
 	void cancelTickle() override;
 
 	// We overload getChildY to make sure child widgets are positioned correctly.


Commit: 87aab725a5a9601908665d5c2a4226abe4529f08
    https://github.com/scummvm/scummvm/commit/87aab725a5a9601908665d5c2a4226abe4529f08
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2026-09-09T00:31:37+03:00

Commit Message:
GUI: Remove WIDGET_IGNORE_DRAG flag

It was used to avoid issues with PopUpWidget which got assigned to
_dragWidget but then opened a new dialog.
The commit be1fdf59bb80 incidently brought another way to fix the bug.
In addition, the widget cancelDrag being called, this could help to
detect and synchronize the widget state.

This also removes a comment which is deprecated for a long time.

Changed paths:
    gui/console.cpp
    gui/dialog.cpp
    gui/widget.h
    gui/widgets/popup.cpp
    gui/widgets/scrollcontainer.cpp


diff --git a/gui/console.cpp b/gui/console.cpp
index 07586792733..73e8226ccf9 100644
--- a/gui/console.cpp
+++ b/gui/console.cpp
@@ -899,8 +899,7 @@ void ConsoleDialog::handleMouseDown(int x, int y, int button, int clickCount) {
 	w = findWidget(x, y);
 
 	if (w) {
-		if (!(w->getFlags() & WIDGET_IGNORE_DRAG))
-			_dragWidget = w;
+		_dragWidget = w;
 
 		if (w != _focusedWidget && w->wantsFocus()) {
 			setFocusWidget(w);
diff --git a/gui/dialog.cpp b/gui/dialog.cpp
index 61e00f9272d..45e48205b32 100644
--- a/gui/dialog.cpp
+++ b/gui/dialog.cpp
@@ -28,15 +28,6 @@
 
 namespace GUI {
 
-/*
- * TODO list
- * - add some sense of the window being "active" (i.e. in front) or not. If it
- *   was inactive and just became active, reset certain vars (like who is focused).
- *   Maybe we should just add lostFocus and receivedFocus methods to Dialog, just
- *   like we have for class Widget?
- * ...
- */
-
 Dialog::Dialog(int x, int y, int w, int h, bool scale)
 	: GuiObject(x, y, w, h, scale),
 	  _mouseWidget(nullptr), _focusedWidget(nullptr), _dragWidget(nullptr), _tickleWidget(nullptr), _visible(false),
@@ -215,7 +206,7 @@ void Dialog::handleMouseDown(int x, int y, int button, int clickCount) {
 
 	w = findWidget(x, y);
 
-	if (w && !(w->getFlags() & WIDGET_IGNORE_DRAG))
+	if (w)
 		_dragWidget = w;
 
 	// If the click occurred inside a widget which is not the currently
diff --git a/gui/widget.h b/gui/widget.h
index 3e595f1daef..d6d754adcb9 100644
--- a/gui/widget.h
+++ b/gui/widget.h
@@ -47,12 +47,6 @@ enum {
 	WIDGET_CLEARBG		= 1 <<  5,
 	WIDGET_WANT_TICKLE	= 1 <<  7,
 	WIDGET_TRACK_MOUSE	= 1 <<  8,
-	// Usually widgets would lock mouse input when the user pressed the
-	// left mouse button till the user releases it.
-	// The PopUpWidget for example does not want this behavior, since the
-	// mouse down will open up a new dialog which silently eats the mouse
-	// up event for its own purposes.
-	WIDGET_IGNORE_DRAG	= 1 << 9,
 	WIDGET_DYN_TOOLTIP  = 1 << 10, // Widgets updates tooltip by coordinates
 };
 
diff --git a/gui/widgets/popup.cpp b/gui/widgets/popup.cpp
index 16371967a8c..6f2091917e1 100644
--- a/gui/widgets/popup.cpp
+++ b/gui/widgets/popup.cpp
@@ -431,7 +431,7 @@ void PopUpDialog::drawMenuEntry(int entry, bool hilite) {
 
 PopUpWidget::PopUpWidget(GuiObject *boss, const Common::String &name, const Common::U32String &tooltip, uint32 cmd)
 	: Widget(boss, name, tooltip), CommandSender(boss) {
-	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_IGNORE_DRAG);
+	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG);
 	_type = kPopUpWidget;
 	_cmd = cmd;
 
@@ -441,7 +441,7 @@ PopUpWidget::PopUpWidget(GuiObject *boss, const Common::String &name, const Comm
 
 PopUpWidget::PopUpWidget(GuiObject *boss, int x, int y, int w, int h, const Common::U32String &tooltip, uint32 cmd)
 	: Widget(boss, x, y, w, h, tooltip), CommandSender(boss) {
-	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_IGNORE_DRAG);
+	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG);
 	_type = kPopUpWidget;
 	_cmd = cmd;
 
diff --git a/gui/widgets/scrollcontainer.cpp b/gui/widgets/scrollcontainer.cpp
index 8e6b066a302..3463289b1bb 100644
--- a/gui/widgets/scrollcontainer.cpp
+++ b/gui/widgets/scrollcontainer.cpp
@@ -74,11 +74,6 @@ void ScrollContainerWidget::handleMouseDown(int x, int y, int button, int clickC
 		int childX = x - (child->getAbsX() - getAbsX());
 		int childY = y - (child->getAbsY() - getAbsY());
 		child->handleMouseDown(childX, childY, button, clickCount);
-
-		if (child->getFlags() & WIDGET_IGNORE_DRAG) {
-			_isMouseDown = false;
-			_isDragging = false;
-		}
 	}
 }
 


Commit: 20c2b2d5978fb99b786d353ae7cbabd83ac17cbc
    https://github.com/scummvm/scummvm/commit/20c2b2d5978fb99b786d353ae7cbabd83ac17cbc
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2026-09-09T00:31:37+03:00

Commit Message:
GUI: Introduce WIDGET_HOOK_DRAG flag

This flag allows a parent widget to steal the drag events from its
descendants.
This will allow for a better behaving ScrollContainerWidget.

Changed paths:
    gui/dialog.cpp
    gui/dialog.h
    gui/widget.h


diff --git a/gui/dialog.cpp b/gui/dialog.cpp
index 45e48205b32..b6cbe9dba56 100644
--- a/gui/dialog.cpp
+++ b/gui/dialog.cpp
@@ -30,8 +30,8 @@ namespace GUI {
 
 Dialog::Dialog(int x, int y, int w, int h, bool scale)
 	: GuiObject(x, y, w, h, scale),
-	  _mouseWidget(nullptr), _focusedWidget(nullptr), _dragWidget(nullptr), _tickleWidget(nullptr), _visible(false),
-	_backgroundType(GUI::ThemeEngine::kDialogBackgroundDefault), _handlingMouseWheel(false) {
+	  _mouseWidget(nullptr), _focusedWidget(nullptr), _dragWidget(nullptr), _dragHookWidget(nullptr), _tickleWidget(nullptr),
+	  _visible(false), _backgroundType(GUI::ThemeEngine::kDialogBackgroundDefault), _handlingMouseWheel(false) {
 	// Some dialogs like LauncherDialog use internally a fixed size, even though
 	// their widgets rely on the layout to be initialized correctly by the theme.
 	// Thus we need to catch screen changes here too. If we do not do that, it
@@ -44,8 +44,8 @@ Dialog::Dialog(int x, int y, int w, int h, bool scale)
 
 Dialog::Dialog(const Common::String &name)
 	: GuiObject(name),
-	  _mouseWidget(nullptr), _focusedWidget(nullptr), _dragWidget(nullptr), _tickleWidget(nullptr), _visible(false),
-	_backgroundType(GUI::ThemeEngine::kDialogBackgroundDefault), _handlingMouseWheel(false) {
+	  _mouseWidget(nullptr), _focusedWidget(nullptr), _dragWidget(nullptr), _dragHookWidget(nullptr), _tickleWidget(nullptr),
+	  _visible(false), _backgroundType(GUI::ThemeEngine::kDialogBackgroundDefault), _handlingMouseWheel(false) {
 
 	// It may happen that we have 3x scaler in launcher (960xY) and then 640x480
 	// game will be forced to 1x. At this stage GUI will not be aware of
@@ -109,6 +109,11 @@ void Dialog::reflowLayout() {
 
 void Dialog::lostFocus() {
 	if (_dragWidget) {
+		if (_dragHookWidget) {
+			// We can't eat a cancelDrag
+			_dragHookWidget->handleDragHook(_dragWidget, kDragHookStateCancel, 0, 0, 0);
+			_dragHookWidget = nullptr;
+		}
 		_dragWidget->cancelDrag();
 		_dragWidget = nullptr;
 	}
@@ -206,15 +211,48 @@ void Dialog::handleMouseDown(int x, int y, int button, int clickCount) {
 
 	w = findWidget(x, y);
 
-	if (w)
+	if (w) {
 		_dragWidget = w;
 
+		// Find the next parent willing to hook for drag
+		Widget *it = w;
+		while (true) {
+			if (it->getFlags() & WIDGET_HOOK_DRAG) {
+				_dragHookWidget = it;
+				break;
+			}
+
+			if (it->_boss == this) {
+				break;
+			}
+			it = (Widget *)it->_boss;
+		}
+
+		// If the widget hooks itself disable the hook
+		// This allows a widget to prevent hooking by its parents
+		if (_dragHookWidget == w) {
+			_dragHookWidget = nullptr;
+		}
+	}
+
 	// If the click occurred inside a widget which is not the currently
 	// focused one, change the focus to that widget.
 	if (w && w != _focusedWidget && w->wantsFocus()) {
 		setFocusWidget(w);
 	}
 
+	if (_dragHookWidget) {
+		if (_dragHookWidget->handleDragHook(w, kDragHookStateMouseDown,
+			x - (_dragHookWidget->getAbsX() - _x),
+			y - (_dragHookWidget->getAbsY() - _y),
+			button)) {
+			// hook widget ate the event and will eat the next ones
+			_dragWidget = _dragHookWidget;
+			_dragHookWidget = nullptr;
+			return;
+		}
+	}
+
 	if (w)
 		w->handleMouseDown(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y), button, clickCount);
 }
@@ -227,11 +265,22 @@ void Dialog::handleMouseUp(int x, int y, int button, int clickCount) {
 	else
 		w = findWidget(x, y);
 
+	if (_dragHookWidget) {
+		if (_dragHookWidget->handleDragHook(_dragWidget, kDragHookStateMouseUp,
+			x - (_dragHookWidget->getAbsX() - _x),
+			y - (_dragHookWidget->getAbsY() - _y),
+			button)) {
+			// hook widget ate the event and no other event will arise
+			w = nullptr;
+		}
+	}
+
 	if (w)
 		w->handleMouseUp(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y), button, clickCount);
 
 	if (_dragWidget) {
 		_dragWidget = nullptr;
+		_dragHookWidget = nullptr;
 		// Fake a mouse move to refresh now hovered widget
 		handleMouseMoved(x, y, button);
 	}
@@ -337,6 +386,18 @@ void Dialog::handleMouseMoved(int x, int y, int button) {
 		_mouseWidget = w;
 	}
 
+	if (_dragHookWidget) {
+		if (_dragHookWidget->handleDragHook(_dragWidget, kDragHookStateMouseMoved,
+			x - (_dragHookWidget->getAbsX() - _x),
+			y - (_dragHookWidget->getAbsY() - _y),
+			button)) {
+			// hook widget ate the event and will eat the next ones
+			_dragWidget = _dragHookWidget;
+			_dragHookWidget = nullptr;
+			return;
+		}
+	}
+
 	// We only sent mouse move events when the widget requests to be informed about them.
 	if (w && (w->getFlags() & WIDGET_TRACK_MOUSE))
 		w->handleMouseMoved(x - (w->getAbsX() - _x), y - (w->getAbsY() - _y), button);
@@ -401,8 +462,11 @@ void Dialog::removeWidget(Widget *del) {
 		_mouseWidget = nullptr;
 	if (del == _focusedWidget || del->containsWidget(_focusedWidget))
 		_focusedWidget = nullptr;
-	if (del == _dragWidget || del->containsWidget(_dragWidget))
+	if (del == _dragWidget || del->containsWidget(_dragWidget)) {
 		_dragWidget = nullptr;
+		// As _dragHookWidget is a parent of _dragWidget we don't need a specific check
+		_dragHookWidget = nullptr;
+	}
 
 	GuiObject::removeWidget(del);
 }
diff --git a/gui/dialog.h b/gui/dialog.h
index 8605ec6cb73..bca81a0b620 100644
--- a/gui/dialog.h
+++ b/gui/dialog.h
@@ -54,6 +54,7 @@ protected:
 	Widget	*_mouseWidget;
 	Widget  *_focusedWidget;
 	Widget  *_dragWidget;
+	Widget  *_dragHookWidget;
 	Widget 	*_tickleWidget;
 	bool	_visible;
 
diff --git a/gui/widget.h b/gui/widget.h
index d6d754adcb9..0d4ba1b5194 100644
--- a/gui/widget.h
+++ b/gui/widget.h
@@ -47,6 +47,9 @@ enum {
 	WIDGET_CLEARBG		= 1 <<  5,
 	WIDGET_WANT_TICKLE	= 1 <<  7,
 	WIDGET_TRACK_MOUSE	= 1 <<  8,
+	/* Used by containers which need to receive
+	 * mouse events in place of their children */
+	WIDGET_HOOK_DRAG	= 1 << 9,
 	WIDGET_DYN_TOOLTIP  = 1 << 10, // Widgets updates tooltip by coordinates
 };
 
@@ -85,6 +88,13 @@ enum {
 	kPicButtonStateMax = 3
 };
 
+enum {
+	kDragHookStateCancel = -2,
+	kDragHookStateMouseUp = -1,
+	kDragHookStateMouseMoved = 0,
+	kDragHookStateMouseDown = 1,
+};
+
 /* Widget */
 class Widget : public GuiObject {
 	friend class Dialog;
@@ -138,6 +148,7 @@ public:
 	virtual void handleOtherEvent(const Common::Event &evt) {}
 	virtual void handleTooltipUpdate(int x, int y) {}
 	virtual void handleTickle() {}
+	virtual bool handleDragHook(Widget *origTarget, int state, int x, int y, int button) { return false; }
 
 	virtual void cancelDrag() {}
 	virtual void cancelTickle() {}


Commit: 0ace2ef96eac931d1ba4eb083f6b347412ea6798
    https://github.com/scummvm/scummvm/commit/0ace2ef96eac931d1ba4eb083f6b347412ea6798
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2026-09-09T00:31:37+03:00

Commit Message:
GUI: Rework ScrollContainerWidget to use WIDGET_HOOK_DRAG

This avoids messing with findWidget and make sure the proper widgets get
the events.

Fix #17094 and #17079.

Changed paths:
    gui/widgets/scrollcontainer.cpp
    gui/widgets/scrollcontainer.h


diff --git a/gui/widgets/scrollcontainer.cpp b/gui/widgets/scrollcontainer.cpp
index 3463289b1bb..5c70fc62c0f 100644
--- a/gui/widgets/scrollcontainer.cpp
+++ b/gui/widgets/scrollcontainer.cpp
@@ -27,7 +27,6 @@
 
 namespace GUI {
 
-const int ScrollContainerWidget::kDragThreshold = 5;
 ScrollContainerWidget::ScrollContainerWidget(GuiObject *boss, int x, int y, int w, int h, uint32 reflowCmd)
 	: Widget(boss, x, y, w, h), CommandSender(nullptr), _reflowCmd(reflowCmd) {
 	init();
@@ -39,7 +38,7 @@ ScrollContainerWidget::ScrollContainerWidget(GuiObject *boss, const Common::Stri
 }
 
 void ScrollContainerWidget::init() {
-	setFlags(WIDGET_ENABLED | WIDGET_TRACK_MOUSE);
+	setFlags(WIDGET_ENABLED | WIDGET_TRACK_MOUSE | WIDGET_HOOK_DRAG);
 	_type = kScrollContainerWidget;
 	_backgroundType = ThemeEngine::kWidgetBackgroundPlain;
 	_verticalScroll = new ScrollBarWidget(this, _w, 0, 16, _h);
@@ -49,7 +48,6 @@ void ScrollContainerWidget::init() {
 	_scrollPos = 0.0f;
 	_limitH = 140;
 	_fluidScroller = new FluidScroller();
-	_wasAnimating = false;
 	recalc();
 }
 
@@ -64,21 +62,11 @@ void ScrollContainerWidget::handleMouseWheel(int x, int y, int direction) {
 void ScrollContainerWidget::handleMouseDown(int x, int y, int button, int clickCount) {
 	_mouseDownY = _mouseDownStartY = y;
 	_isMouseDown = true;
-	_wasAnimating = _fluidScroller->isAnimating();
 	_fluidScroller->stopAnimation();
-	if (_wasAnimating)
-		return;
-
-	Widget *child = _childUnderMouse;
-	if (child) {
-		int childX = x - (child->getAbsX() - getAbsX());
-		int childY = y - (child->getAbsY() - getAbsY());
-		child->handleMouseDown(childX, childY, button, clickCount);
-	}
 }
 
 void ScrollContainerWidget::handleMouseMoved(int x, int y, int button) {
-	if (!_isMouseDown || _mouseDownY == y || !_verticalScroll->isVisible())
+	if (!_isMouseDown || !_verticalScroll->isVisible())
 		return;
 
 	if (!_isDragging && ABS(y - _mouseDownStartY) > kDragThreshold)
@@ -87,7 +75,6 @@ void ScrollContainerWidget::handleMouseMoved(int x, int y, int button) {
 	if (_isDragging) {
 		int deltaY = _mouseDownY - y;
 		_mouseDownY = y;
-		_childUnderMouse = nullptr;
 
 		if (deltaY != 0) {
 			_fluidScroller->feedDrag(g_system->getMillis(), deltaY);
@@ -121,9 +108,6 @@ void ScrollContainerWidget::applyScrollPos() {
 }
 
 void ScrollContainerWidget::handleMouseUp(int x, int y, int button, int clickCount) {
-	Widget *child = _childUnderMouse;
-	bool isDragging = _isDragging;
-
 	if (_isMouseDown && _isDragging) {
 		_fluidScroller->startFling();
 		registerTickleWidget(this);
@@ -132,14 +116,6 @@ void ScrollContainerWidget::handleMouseUp(int x, int y, int button, int clickCou
 	_mouseDownY = _mouseDownStartY = 0;
 	_isMouseDown = false;
 	_isDragging = false;
-	_childUnderMouse = nullptr;
-
-	if (!isDragging && child && !_wasAnimating) {
-		int childX = x - (child->getAbsX() - getAbsX());
-		int childY = y - (child->getAbsY() - getAbsY());
-		child->handleMouseUp(childX, childY, button, clickCount);
-	}
-	_wasAnimating = false;
 }
 
 void ScrollContainerWidget::cancelDrag() {
@@ -151,6 +127,35 @@ void ScrollContainerWidget::cancelDrag() {
 	_isDragging = false;
 }
 
+bool ScrollContainerWidget::handleDragHook(Widget *origTarget, int state, int x, int y, int button) {
+	if (state == kDragHookStateMouseDown) {
+		bool wasAnimating = _fluidScroller->isAnimating();
+		handleMouseDown(x, y, button, 0);
+		if (wasAnimating) {
+			// If we were animating, we stop it and take over the events
+			return true;
+		}
+		// Let the button down event pass through to the underlying widget
+	} else if (state == kDragHookStateMouseUp) {
+		// We didn't catch the events: this must be a click
+		// Reset our internal state and let the event pass through
+		_isDragging = false;
+		handleMouseUp(x, y, button, 0);
+	} else if (state == kDragHookStateCancel) {
+		cancelDrag();
+	} else if (state == kDragHookStateMouseMoved) {
+		handleMouseMoved(x, y, button);
+		if (_isDragging) {
+			// We are taking over now: notify the original widget
+			// that the drag is finished for it
+			origTarget->handleMouseLeft(button);
+			origTarget->cancelDrag();
+			return true;
+		}
+	}
+	return false;
+}
+
 void ScrollContainerWidget::recalc() {
 	_scrollbarWidth = g_gui.xmlEval()->getVar("Globals.Scrollbar.Width", 0);
 	_limitH = _h;
@@ -225,7 +230,6 @@ void ScrollContainerWidget::handleCommand(CommandSender *sender, uint32 cmd, uin
 }
 
 void ScrollContainerWidget::reflowLayout() {
-	_childUnderMouse = nullptr;
 	Widget::reflowLayout();
 
 	if (!_dialogName.empty()) {
@@ -276,17 +280,11 @@ bool ScrollContainerWidget::containsWidget(Widget *w) const {
 }
 
 Widget *ScrollContainerWidget::findWidget(int x, int y) {
-	if (_verticalScroll->isVisible() && x >= _w) {
-		_childUnderMouse = nullptr;
+	if (_verticalScroll->isVisible() && x >= _w)
 		return _verticalScroll;
-	}
-	_childUnderMouse = Widget::findWidgetInChain(_firstWidget, x + _scrolledX, y + _scrolledY);
-	if (_childUnderMouse == _verticalScroll) 
-		_childUnderMouse = nullptr;
-
-	if (_childUnderMouse && _childUnderMouse->wantsFocus())
-		return _childUnderMouse;
-
+	Widget *w = Widget::findWidgetInChain(_firstWidget, x + _scrolledX, y + _scrolledY);
+	if (w)
+		return w;
 	return this;
 }
 
diff --git a/gui/widgets/scrollcontainer.h b/gui/widgets/scrollcontainer.h
index e28b7bd9b35..0c907f04ce5 100644
--- a/gui/widgets/scrollcontainer.h
+++ b/gui/widgets/scrollcontainer.h
@@ -37,15 +37,13 @@ class ScrollContainerWidget: public Widget, public CommandSender {
 	uint32 _reflowCmd;
 	ThemeEngine::WidgetBackground _backgroundType;
 	Common::String _dialogName;
+	static const int kDragThreshold = 5;
 	int _mouseDownY = 0;
-	static const int kDragThreshold;
 	int _mouseDownStartY = 0;
 	bool _isMouseDown = false;
 	bool _isDragging = false;
-	bool _wasAnimating = false;
 	float _scrollPos = 0.0f;
 	FluidScroller *_fluidScroller = nullptr;
-	Widget *_childUnderMouse = nullptr;
 
 	void recalc();
 	void applyScrollPos();
@@ -68,6 +66,7 @@ public:
 	void handleMouseUp(int x, int y, int button, int clickCount) override;
 	void handleMouseMoved(int x, int y, int button) override;
 	void handleTickle() override;
+	bool handleDragHook(Widget *origTarget, int state, int x, int y, int button) override;
 
 	void cancelDrag() override;
 	void cancelTickle() override;


Commit: d2a9c783895efe18d5b6900b6118f5bb632f993c
    https://github.com/scummvm/scummvm/commit/d2a9c783895efe18d5b6900b6118f5bb632f993c
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2026-09-09T00:31:37+03:00

Commit Message:
GUI: Avoid clashes when dragging in a ScrollContainerWidget

All widgets making use of a drag gesture are immunized.

Changed paths:
    gui/widget.cpp
    gui/widgets/editable.cpp
    gui/widgets/grid.cpp
    gui/widgets/list.cpp
    gui/widgets/richtext.cpp
    gui/widgets/scrollbar.cpp


diff --git a/gui/widget.cpp b/gui/widget.cpp
index dcd2c983807..709ab26e41e 100644
--- a/gui/widget.cpp
+++ b/gui/widget.cpp
@@ -826,7 +826,8 @@ void RadiobuttonWidget::drawWidget() {
 SliderWidget::SliderWidget(GuiObject *boss, int x, int y, int w, int h, bool scale, const Common::U32String &tooltip, uint32 cmd)
 	: Widget(boss, x, y, w, h, scale, tooltip), CommandSender(boss),
 	  _cmd(cmd), _value(0), _oldValue(0), _valueMin(0), _valueMax(100), _isDragging(false), _labelWidth(0) {
-	setFlags(WIDGET_ENABLED | WIDGET_TRACK_MOUSE | WIDGET_CLEARBG);
+	// WIDGET_HOOK_DRAG: prevent hooking by a container as we need drag for ourselves
+	setFlags(WIDGET_ENABLED | WIDGET_TRACK_MOUSE | WIDGET_CLEARBG | WIDGET_HOOK_DRAG);
 	_type = kSliderWidget;
 }
 
@@ -837,7 +838,8 @@ SliderWidget::SliderWidget(GuiObject *boss, int x, int y, int w, int h, const Co
 SliderWidget::SliderWidget(GuiObject *boss, const Common::String &name, const Common::U32String &tooltip, uint32 cmd)
 	: Widget(boss, name, tooltip), CommandSender(boss),
 	  _cmd(cmd), _value(0), _oldValue(0), _valueMin(0), _valueMax(100), _isDragging(false), _labelWidth(0) {
-	setFlags(WIDGET_ENABLED | WIDGET_TRACK_MOUSE | WIDGET_CLEARBG);
+	// WIDGET_HOOK_DRAG: prevent hooking by a container as we need drag for ourselves
+	setFlags(WIDGET_ENABLED | WIDGET_TRACK_MOUSE | WIDGET_CLEARBG | WIDGET_HOOK_DRAG);
 	_type = kSliderWidget;
 }
 
diff --git a/gui/widgets/editable.cpp b/gui/widgets/editable.cpp
index 42b5b89a402..cfbde9654b6 100644
--- a/gui/widgets/editable.cpp
+++ b/gui/widgets/editable.cpp
@@ -30,7 +30,8 @@ namespace GUI {
 
 EditableWidget::EditableWidget(GuiObject *boss, int x, int y, int w, int h, bool scale, const Common::U32String &tooltip, uint32 cmd)
 	: Widget(boss, x, y, w, h, scale, tooltip), CommandSender(boss), _cmd(cmd) {
-	setFlags(WIDGET_TRACK_MOUSE);
+	// WIDGET_HOOK_DRAG: prevent hooking by a container as we need drag for ourselves
+	setFlags(WIDGET_TRACK_MOUSE | WIDGET_HOOK_DRAG);
 	init();
 }
 
@@ -40,7 +41,8 @@ EditableWidget::EditableWidget(GuiObject *boss, int x, int y, int w, int h, cons
 
 EditableWidget::EditableWidget(GuiObject *boss, const Common::String &name, const Common::U32String &tooltip, uint32 cmd)
 	: Widget(boss, name, tooltip), CommandSender(boss), _cmd(cmd) {
-	setFlags(WIDGET_TRACK_MOUSE);
+	// WIDGET_HOOK_DRAG: prevent hooking by a container as we need drag for ourselves
+	setFlags(WIDGET_TRACK_MOUSE | WIDGET_HOOK_DRAG);
 	init();
 }
 
diff --git a/gui/widgets/grid.cpp b/gui/widgets/grid.cpp
index f84ed457442..8da5e64e660 100644
--- a/gui/widgets/grid.cpp
+++ b/gui/widgets/grid.cpp
@@ -544,7 +544,8 @@ GridWidget::GridWidget(GuiObject *boss, const Common::String &name)
 	_filterMatcher = GridWidgetDefaultMatcher;
 	_filterMatcherArg = nullptr;
 
-	setFlags(getFlags() | WIDGET_TRACK_MOUSE);
+	// WIDGET_HOOK_DRAG: prevent hooking by a container as we need drag for ourselves
+	setFlags(getFlags() | WIDGET_TRACK_MOUSE | WIDGET_HOOK_DRAG);
 }
 
 GridWidget::~GridWidget() {
diff --git a/gui/widgets/list.cpp b/gui/widgets/list.cpp
index 783a3d41ccd..431ee178c3c 100644
--- a/gui/widgets/list.cpp
+++ b/gui/widgets/list.cpp
@@ -46,7 +46,8 @@ ListWidget::ListWidget(Dialog *boss, const Common::String &name, const Common::U
 	_scrollBar = new ScrollBarWidget(this, _w - _scrollBarWidth, 0, _scrollBarWidth, _h);
 	_scrollBar->setTarget(this);
 
-	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_WANT_TICKLE | WIDGET_TRACK_MOUSE);
+	// WIDGET_HOOK_DRAG: prevent hooking by a container as we need drag for ourselves
+	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_WANT_TICKLE | WIDGET_TRACK_MOUSE | WIDGET_HOOK_DRAG);
 	_type = kListWidget;
 	_editMode = false;
 	_numberingMode = kListNumberingOne;
@@ -98,7 +99,8 @@ ListWidget::ListWidget(Dialog *boss, int x, int y, int w, int h, bool scale, con
 	_scrollBar = new ScrollBarWidget(this, _w - _scrollBarWidth, 0, _scrollBarWidth, _h);
 	_scrollBar->setTarget(this);
 
-	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_WANT_TICKLE | WIDGET_TRACK_MOUSE);
+	// WIDGET_HOOK_DRAG: prevent hooking by a container as we need drag for ourselves
+	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_WANT_TICKLE | WIDGET_TRACK_MOUSE | WIDGET_HOOK_DRAG);
 	_type = kListWidget;
 	_editMode = false;
 	_numberingMode = kListNumberingOne;
diff --git a/gui/widgets/richtext.cpp b/gui/widgets/richtext.cpp
index def58d58627..7d0effa9395 100644
--- a/gui/widgets/richtext.cpp
+++ b/gui/widgets/richtext.cpp
@@ -66,7 +66,8 @@ RichTextWidget::RichTextWidget(GuiObject *boss, const Common::String &name, cons
 }
 
 void RichTextWidget::init() {
-	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_TRACK_MOUSE | WIDGET_DYN_TOOLTIP);
+	// WIDGET_HOOK_DRAG: prevent hooking by a container as we need drag for ourselves
+	setFlags(WIDGET_ENABLED | WIDGET_CLEARBG | WIDGET_TRACK_MOUSE | WIDGET_DYN_TOOLTIP | WIDGET_HOOK_DRAG);
 
 	_type = kRichTextWidget;
 
diff --git a/gui/widgets/scrollbar.cpp b/gui/widgets/scrollbar.cpp
index 3edee8149d3..7dda4797e14 100644
--- a/gui/widgets/scrollbar.cpp
+++ b/gui/widgets/scrollbar.cpp
@@ -33,7 +33,8 @@ namespace GUI {
 
 ScrollBarWidget::ScrollBarWidget(GuiObject *boss, int x, int y, int w, int h)
 	: Widget (boss, x, y, w, h), CommandSender(boss) {
-	setFlags(WIDGET_ENABLED | WIDGET_TRACK_MOUSE | WIDGET_CLEARBG | WIDGET_WANT_TICKLE);
+	// WIDGET_HOOK_DRAG: prevent hooking by a container as we need drag for ourselves
+	setFlags(WIDGET_ENABLED | WIDGET_TRACK_MOUSE | WIDGET_CLEARBG | WIDGET_WANT_TICKLE | WIDGET_HOOK_DRAG);
 	_type = kScrollBarWidget;
 
 	_part = kNoPart;


Commit: 2374c2e5144955e80415001afc29f6daf4abdd8b
    https://github.com/scummvm/scummvm/commit/2374c2e5144955e80415001afc29f6daf4abdd8b
Author: Le Philousophe (lephilousophe at users.noreply.github.com)
Date: 2026-09-09T00:31:37+03:00

Commit Message:
GUI: Renumber Widget flags

Changed paths:
    gui/widget.h


diff --git a/gui/widget.h b/gui/widget.h
index 0d4ba1b5194..d7567f48635 100644
--- a/gui/widget.h
+++ b/gui/widget.h
@@ -40,17 +40,17 @@ namespace GUI {
 class ScrollContainerWidget;
 
 enum {
-	WIDGET_ENABLED		= 1 <<  0,
-	WIDGET_INVISIBLE	= 1 <<  1,
-	WIDGET_HILITED		= 1 <<  2,
-	WIDGET_PRESSED		= 1 <<	4,
-	WIDGET_CLEARBG		= 1 <<  5,
-	WIDGET_WANT_TICKLE	= 1 <<  7,
-	WIDGET_TRACK_MOUSE	= 1 <<  8,
+	WIDGET_ENABLED          = 1 <<  0,
+	WIDGET_INVISIBLE        = 1 <<  1,
+	WIDGET_HILITED          = 1 <<  2,
+	WIDGET_PRESSED          = 1 <<  3,
+	WIDGET_CLEARBG          = 1 <<  4,
+	WIDGET_WANT_TICKLE      = 1 <<  5,
+	WIDGET_TRACK_MOUSE      = 1 <<  6,
 	/* Used by containers which need to receive
 	 * mouse events in place of their children */
-	WIDGET_HOOK_DRAG	= 1 << 9,
-	WIDGET_DYN_TOOLTIP  = 1 << 10, // Widgets updates tooltip by coordinates
+	WIDGET_HOOK_DRAG        = 1 <<  7,
+	WIDGET_DYN_TOOLTIP      = 1 <<  8, // Widgets updates tooltip by coordinates
 };
 
 enum {




More information about the Scummvm-git-logs mailing list