[Scummvm-git-logs] scummvm master -> 1d4874acffb537a6ad01857afb1751f095ebab69
bluegr
noreply at scummvm.org
Wed Aug 19 21:53:47 UTC 2026
This automated email contains information about 5 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
880280ed42 NANCY: NANCY13: Disable AVF video playing and update stale comments
4c1c74c749 NANCY: Fix immediate scene changes that point to the same scene
18fea43456 NANCY: Enhancement: Allow skipping cutscenes and dialog lines with esc
ca25044ca9 NANCY: Enhancement: Add mouse wheel functionality to several UI widgets
1d4874acff NEWS: Add entries for new functionality and fixes for Nancy games
Commit: 880280ed42d2028689701c7c1719158fb40c6cec
https://github.com/scummvm/scummvm/commit/880280ed42d2028689701c7c1719158fb40c6cec
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-08-20T00:53:26+03:00
Commit Message:
NANCY: NANCY13: Disable AVF video playing and update stale comments
Nancy13 dropped AVF videos altogether, and only used Bink videos
Changed paths:
engines/nancy/action/secondarymovie.cpp
engines/nancy/action/secondarymovie.h
diff --git a/engines/nancy/action/secondarymovie.cpp b/engines/nancy/action/secondarymovie.cpp
index 81b15f545f5..d300a17d4a5 100644
--- a/engines/nancy/action/secondarymovie.cpp
+++ b/engines/nancy/action/secondarymovie.cpp
@@ -214,8 +214,8 @@ void PlaySecondaryMovie::readRandomMovieDataNancy14(Common::Serializer &ser, Com
}
// Recognition ("secondary") movie: its name followed by its own blt
- // descriptors. Stored for future playback; the descriptors are consumed to
- // keep the stream aligned (no home in the struct yet).
+ // descriptors. The descriptors are consumed to keep the stream aligned
+ // (no home in the struct yet).
readFilename(ser, _secondaryMovie.name);
uint16 numSecondaryDescs = 0;
ser.syncAsUint16LE(numSecondaryDescs);
@@ -229,7 +229,6 @@ void PlaySecondaryMovie::readRandomMovieDataNancy14(Common::Serializer &ser, Com
void PlaySecondaryMovie::applyStartingRandomSequence() {
// "RandomMovie" picks any sequence; otherwise look up by name.
- // Only the first sequence is played; chained playback is TODO.
if (!_sequences.empty()) {
int startIdx = -1;
if (_startingSequenceName == "RandomMovie") {
@@ -561,7 +560,11 @@ void PlaySecondaryMovie::readData(Common::SeekableReadStream &stream) {
if (g_nancy->getGameType() >= kGameTypeNancy7) {
uint16 videoType = 0;
ser.syncAsUint16LE(videoType);
- _videoPlaytype = videoType == kVideoPlaytypeBink ? kVideoPlaytypeBink : kVideoPlaytypeAVF;
+
+ // Nancy13 dropped AVF altogether, so the slot no longer selects a container.
+ if (g_nancy->getGameType() <= kGameTypeNancy12) {
+ _videoPlaytype = videoType == kVideoPlaytypeBink ? kVideoPlaytypeBink : kVideoPlaytypeAVF;
+ }
}
ser.skip(2, kGameTypeVampire, kGameTypeNancy9); // videoPlaySource
diff --git a/engines/nancy/action/secondarymovie.h b/engines/nancy/action/secondarymovie.h
index fd366c78cd1..4cc4792806e 100644
--- a/engines/nancy/action/secondarymovie.h
+++ b/engines/nancy/action/secondarymovie.h
@@ -95,7 +95,7 @@ public:
Common::Path _paletteName;
Common::Path _bitmapOverlayName;
- // Container the record asks for; only present from Nancy7 to Nancy13.
+ // Container the record asks for; only meaningful from Nancy7 to Nancy12.
byte _videoPlaytype = kVideoPlaytypeAuto;
uint16 _videoFormat = kLargeVideoFormat;
@@ -139,9 +139,8 @@ public:
uint16 _randomPlayerCursorAllowed = kPlayerCursorAllowed;
Common::Array<RandomSequence> _sequences;
- // Nancy13+ carries one extra "secondary" movie (a recognition animation)
- // after the sequence list. Stored for future playback; reading it is
- // required so the trailing hotspot list stays aligned.
+ // Nancy13+ carries one extra "secondary" movie after the sequence list: the
+ // character's recognition animation, played while the mouse hovers it.
RandomSequence _secondaryMovie;
// Nancy13 talkable characters: the scene to open when the character is
Commit: 4c1c74c74909ee89a674791f472376b8296e6a63
https://github.com/scummvm/scummvm/commit/4c1c74c74909ee89a674791f472376b8296e6a63
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-08-20T00:53:27+03:00
Commit Message:
NANCY: Fix immediate scene changes that point to the same scene
This allows us to finally get rid of a hack for Nancy9, scene 5651
(bottle stuck between rocks).
Fix #16782 and #17025
Changed paths:
engines/nancy/action/soundrecords.cpp
engines/nancy/state/scene.cpp
diff --git a/engines/nancy/action/soundrecords.cpp b/engines/nancy/action/soundrecords.cpp
index 1814f86a04d..b4143548b55 100644
--- a/engines/nancy/action/soundrecords.cpp
+++ b/engines/nancy/action/soundrecords.cpp
@@ -211,7 +211,9 @@ void PlaySound::readDataNancy13(Common::SeekableReadStream &stream) {
void PlaySound::execute() {
switch (_state) {
case kBegin:
- g_nancy->_sound->loadSound(_sound, &_soundEffect);
+ // The channel is always unloaded and reloaded, so a sound that is still
+ // playing restarts from the beginning instead of being left alone
+ g_nancy->_sound->loadSound(_sound, &_soundEffect, true);
g_nancy->_sound->playSound(_sound);
// Nancy13+ shows the sound's subtitle (resolved from its name) in the
@@ -239,17 +241,13 @@ void PlaySound::execute() {
break;
}
- if (_changeSceneImmediately) {
- applyAfterSoundAction();
- NancySceneState.changeScene(_sceneChange);
- finishExecution();
- break;
- }
-
_state = kRun;
break;
case kRun:
- if (!g_nancy->_sound->isSoundPlaying(_sound)) {
+ // changeSceneImmediately means the record doesn't wait for the sound to
+ // end, not that the scene changes within this same pass; the records
+ // between this one and the end of the list still get to run first
+ if (_changeSceneImmediately || !g_nancy->_sound->isSoundPlaying(_sound)) {
_state = kActionTrigger;
}
@@ -262,7 +260,9 @@ void PlaySound::execute() {
NancySceneState.setEventFlag(_flag);
}
- g_nancy->_sound->stopSound(_sound);
+ if (!_changeSceneImmediately) {
+ g_nancy->_sound->stopSound(_sound);
+ }
finishExecution();
break;
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index e36efabcc9a..739895202fc 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -240,19 +240,6 @@ void Scene::changeScene(const SceneChangeDescription &sceneDescription) {
return;
}
- // HACK: Nancy 9 tries to reload the same scene when changing
- // angle/power in scene 5651 (stuck bottle in rocks). This ends up
- // resetting the scene flags, which makes the angle/power buttons
- // unresponsive. We avoid reloading the scene in this case, if the
- // new scene is the same as the current one. This has the negative
- // side-effect that the button arrows are not updated, but at least
- // it makes them usable.
- // TODO: find a better solution for this.
- if (sceneDescription.sceneID == _sceneState.currentScene.sceneID &&
- g_nancy->getGameType() == kGameTypeNancy9 && sceneDescription.sceneID == 5651) {
- return;
- }
-
_sceneState.nextScene = sceneDescription;
_state = kLoad;
}
Commit: 18fea4345639fce73a4bb4c2567319d6109a94c9
https://github.com/scummvm/scummvm/commit/18fea4345639fce73a4bb4c2567319d6109a94c9
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-08-20T00:53:29+03:00
Commit Message:
NANCY: Enhancement: Allow skipping cutscenes and dialog lines with esc
Changed paths:
engines/nancy/action/conversation.cpp
engines/nancy/action/conversation.h
engines/nancy/action/secondarymovie.cpp
engines/nancy/action/secondarymovie.h
engines/nancy/state/scene.cpp
engines/nancy/state/scene.h
diff --git a/engines/nancy/action/conversation.cpp b/engines/nancy/action/conversation.cpp
index 8c1f4889866..b975a2b484f 100644
--- a/engines/nancy/action/conversation.cpp
+++ b/engines/nancy/action/conversation.cpp
@@ -392,7 +392,7 @@ void ConversationSound::execute() {
}
}
- if (!g_nancy->_sound->isSoundPlaying(_sound) && isVideoDonePlaying()) {
+ if (!g_nancy->_sound->isSoundPlaying(_sound) && (_isSkipped || isVideoDonePlaying())) {
g_nancy->_sound->stopSound(_sound);
bool hasResponses = false;
@@ -449,6 +449,8 @@ void ConversationSound::execute() {
}
break;
case kActionTrigger:
+ _isSkipped = false;
+
if (!g_nancy->_sound->isSoundPlaying(_responseGenericSound)) {
// process flags structs
for (auto &flags : _flagsStructs) {
@@ -493,6 +495,23 @@ void ConversationSound::execute() {
}
}
+void ConversationSound::skipLine() {
+ switch (_state) {
+ case kRun:
+ // Cut the NPC's line short. Stopping the sound and marking the line as
+ // skipped makes the next execute() take the "line has finished" path.
+ g_nancy->_sound->stopSound(_sound);
+ _isSkipped = true;
+ break;
+ case kActionTrigger:
+ // The player's chosen response is playing; cut that short instead
+ g_nancy->_sound->stopSound(_responseGenericSound);
+ break;
+ default:
+ break;
+ }
+}
+
void ConversationSound::addConditionalDialogue() {
if (g_nancy->getGameType() >= kGameTypeNancy12) {
addConditionalDialogueNancy12();
diff --git a/engines/nancy/action/conversation.h b/engines/nancy/action/conversation.h
index 8d15bda741c..4dc3c4835b3 100644
--- a/engines/nancy/action/conversation.h
+++ b/engines/nancy/action/conversation.h
@@ -50,6 +50,10 @@ public:
virtual bool isVideoDonePlaying() { return true; }
bool isViewportRelative() const override { return true; }
+ // Enhancement: cut the currently playing line short, as if its sound and
+ // video had just finished. Any available responses still get shown.
+ void skipLine();
+
protected:
struct ConversationFlag {
byte type;
@@ -137,6 +141,7 @@ protected:
bool _hasDrawnTextbox;
int16 _pickedResponse;
+ bool _isSkipped = false;
const byte _noResponse;
};
diff --git a/engines/nancy/action/secondarymovie.cpp b/engines/nancy/action/secondarymovie.cpp
index d300a17d4a5..7016df86ed0 100644
--- a/engines/nancy/action/secondarymovie.cpp
+++ b/engines/nancy/action/secondarymovie.cpp
@@ -917,6 +917,21 @@ void PlaySecondaryMovie::execute() {
}
}
+void PlaySecondaryMovie::skip() {
+ if (_isRandom || _state != kRun || _playerCursorAllowed != kNoPlayerCursorAllowed) {
+ return;
+ }
+
+ g_nancy->_sound->stopSound(_sound);
+
+ if (!_isFinished) {
+ _decoder.pauseVideo(true);
+ _isFinished = true;
+ }
+
+ _state = kActionTrigger;
+}
+
// --- PlayRandomMovieControl --------------------------------------------
void PlayRandomMovieControl::readData(Common::SeekableReadStream &stream) {
diff --git a/engines/nancy/action/secondarymovie.h b/engines/nancy/action/secondarymovie.h
index 4cc4792806e..49fd839635a 100644
--- a/engines/nancy/action/secondarymovie.h
+++ b/engines/nancy/action/secondarymovie.h
@@ -91,6 +91,11 @@ public:
bool getIsFinished() const { return _isFinished; }
+ // Enhancement: jump a cinematic straight to its end, as if it had finished
+ // playing on its own. Only movies that hide the player cursor are skipped;
+ // the rest are background animations the player isn't waiting on.
+ void skip();
+
Common::Path _videoName;
Common::Path _paletteName;
Common::Path _bitmapOverlayName;
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index 739895202fc..396bc28787b 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -36,6 +36,7 @@
#include "engines/nancy/state/scene.h"
#include "engines/nancy/state/map.h"
+#include "engines/nancy/action/conversation.h"
#include "engines/nancy/action/secondarymovie.h"
#include "engines/nancy/ui/button.h"
@@ -1566,9 +1567,21 @@ void Scene::handleInput() {
g_nancy->_cursor->warpCursor(input.mousePos);
}
}
- } else if (!_activeMovie) {
- // Check if player has pressed esc
- if (input.input & NancyInput::kOpenMainMenu) {
+ }
+
+ // Check if player has pressed esc. While a dialogue line or a cinematic is
+ // playing, esc skips it instead of opening the main menu. Only the initial
+ // press counts, so holding the key down doesn't skip line after line.
+ const bool escPressed = (input.input & NancyInput::kOpenMainMenu) != 0;
+ const bool escJustPressed = escPressed && !_escHeld;
+ _escHeld = escPressed;
+
+ if (escJustPressed) {
+ if (_activeConversation) {
+ _activeConversation->skipLine();
+ } else if (_activeMovie) {
+ _activeMovie->skip();
+ } else {
g_nancy->setState(NancyState::kMainMenu);
return;
}
diff --git a/engines/nancy/state/scene.h b/engines/nancy/state/scene.h
index 29cd250b325..1c742ff67f7 100644
--- a/engines/nancy/state/scene.h
+++ b/engines/nancy/state/scene.h
@@ -372,6 +372,9 @@ private:
// down a persistent random-movie whose scene chain is over.
bool _hadRandomMovieARThisScene = false;
+ // Whether esc was already down last frame, so a held key only skips once
+ bool _escHeld = false;
+
// Contains a screenshot of the Scene state from the last time it was exited
Graphics::ManagedSurface _lastScreenshot;
Commit: ca25044ca9667b5e74bec1fd822be5038cd1f264
https://github.com/scummvm/scummvm/commit/ca25044ca9667b5e74bec1fd822be5038cd1f264
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-08-20T00:53:34+03:00
Commit Message:
NANCY: Enhancement: Add mouse wheel functionality to several UI widgets
Mouse wheel functionality can now be used to scroll the content of the
following widgets:
- Notebook
- Task list
- Inventory
- Cellphone help
- Cellphone web browser
- Text box
- Conversation text box
Changed paths:
engines/nancy/action/puzzle/peepholepuzzle.cpp
engines/nancy/input.cpp
engines/nancy/input.h
engines/nancy/ui/cellphonepopup.cpp
engines/nancy/ui/cellphonepopup.h
engines/nancy/ui/conversationpopup.cpp
engines/nancy/ui/inventorybox.cpp
engines/nancy/ui/inventorypopup.cpp
engines/nancy/ui/notebookpopup.cpp
engines/nancy/ui/scrollbar.cpp
engines/nancy/ui/scrollbar.h
engines/nancy/ui/scrolltextbox.cpp
engines/nancy/ui/textbox.cpp
diff --git a/engines/nancy/action/puzzle/peepholepuzzle.cpp b/engines/nancy/action/puzzle/peepholepuzzle.cpp
index 67273a35a35..aa3c614405e 100644
--- a/engines/nancy/action/puzzle/peepholepuzzle.cpp
+++ b/engines/nancy/action/puzzle/peepholepuzzle.cpp
@@ -30,6 +30,8 @@
#include "engines/nancy/state/scene.h"
+#include "engines/nancy/ui/scrollbar.h"
+
namespace Nancy {
namespace Action {
@@ -113,6 +115,24 @@ void PeepholePuzzle::handleInput(NancyInput &input) {
}
}
+ // The mouse wheel scrolls the contents vertically while the cursor is over them,
+ // as an alternative to holding down the up/down buttons
+ if ((input.input & NancyInput::kMouseWheel) && _innerBounds.height() > _dest.height() &&
+ NancySceneState.getViewport().convertViewportToScreen(_dest).contains(input.mousePos)) {
+ const int scrollPixels = UI::wheelScrollPixels(_dest.height());
+ _currentSrc.translate(0, (input.input & NancyInput::kMouseWheelUp) ? -scrollPixels : scrollPixels);
+
+ if (_currentSrc.top < _innerBounds.top) {
+ _currentSrc.translate(0, _innerBounds.top - _currentSrc.top);
+ } else if (_currentSrc.bottom > _innerBounds.bottom) {
+ _currentSrc.translate(0, _innerBounds.bottom - _currentSrc.bottom);
+ }
+
+ input.eatMouseWheelInput();
+ checkButtons();
+ drawInner();
+ }
+
if (_pressedButton != -1) {
if (input.input & NancyInput::kLeftMouseButtonHeld) {
// Player is still holding the left button, check if mouse has moved outside bounds
diff --git a/engines/nancy/input.cpp b/engines/nancy/input.cpp
index dbae5afb1d3..c63dcbe14ac 100644
--- a/engines/nancy/input.cpp
+++ b/engines/nancy/input.cpp
@@ -38,7 +38,7 @@ void InputManager::processEvents() {
using namespace Common;
Common::Event event;
- _inputs &= ~(NancyInput::kLeftMouseButtonDown | NancyInput::kLeftMouseButtonUp | NancyInput::kRightMouseButtonDown | NancyInput::kRightMouseButtonUp | NancyInput::kRaycastMap);
+ _inputs &= ~(NancyInput::kLeftMouseButtonDown | NancyInput::kLeftMouseButtonUp | NancyInput::kRightMouseButtonDown | NancyInput::kRightMouseButtonUp | NancyInput::kRaycastMap | NancyInput::kMouseWheel);
_otherKbdInput.clear();
while (g_nancy->getEventManager()->pollEvent(event)) {
@@ -48,6 +48,14 @@ void InputManager::processEvents() {
_otherKbdInput.push_back(event.kbd);
_inputBeginState = g_nancy->getState();
break;
+ case EVENT_WHEELUP:
+ _inputs |= NancyInput::kMouseWheelUp;
+ _inputBeginState = g_nancy->getState();
+ break;
+ case EVENT_WHEELDOWN:
+ _inputs |= NancyInput::kMouseWheelDown;
+ _inputBeginState = g_nancy->getState();
+ break;
case EVENT_CUSTOM_ENGINE_ACTION_START:
_inputBeginState = g_nancy->getState();
diff --git a/engines/nancy/input.h b/engines/nancy/input.h
index 5f232f3e149..3f2c16777c4 100644
--- a/engines/nancy/input.h
+++ b/engines/nancy/input.h
@@ -53,16 +53,23 @@ struct NancyInput {
kMoveFastModifier = 1 << 10,
kOpenMainMenu = 1 << 11,
kRaycastMap = 1 << 12,
+ kMouseWheelUp = 1 << 13,
+ kMouseWheelDown = 1 << 14,
kLeftMouseButton = kLeftMouseButtonDown | kLeftMouseButtonHeld | kLeftMouseButtonUp,
- kRightMouseButton = kRightMouseButtonDown | kRightMouseButtonHeld | kRightMouseButtonUp
+ kRightMouseButton = kRightMouseButtonDown | kRightMouseButtonHeld | kRightMouseButtonUp,
+ kMouseWheel = kMouseWheelUp | kMouseWheelDown
};
Common::Point mousePos;
uint16 input;
Common::Array<Common::KeyState> otherKbdInput;
- void eatMouseInput() { mousePos.x = -1; input &= ~(kLeftMouseButton | kRightMouseButton); }
+ void eatMouseInput() { mousePos.x = -1; input &= ~(kLeftMouseButton | kRightMouseButton | kMouseWheel); }
+
+ // Consumes just the wheel input, leaving the mouse position and buttons alone,
+ // so a widget that scrolls can still keep updating its hover state
+ void eatMouseWheelInput() { input &= ~kMouseWheel; }
};
// This class handles collecting events and translating them to a NancyInput object,
diff --git a/engines/nancy/ui/cellphonepopup.cpp b/engines/nancy/ui/cellphonepopup.cpp
index 2436a588ce0..57bfe8101f6 100644
--- a/engines/nancy/ui/cellphonepopup.cpp
+++ b/engines/nancy/ui/cellphonepopup.cpp
@@ -1173,6 +1173,12 @@ void CellPhonePopup::renderContentPage(int surfaceWidth) {
_contentCacheHotspots = ht.hotspots();
}
+const Common::Rect &CellPhonePopup::contentViewScreenRect() const {
+ return (isHelpContentView() || _uiclData->emailListContainer.isEmpty())
+ ? _uiclData->welcomeScreen.destRect
+ : _uiclData->emailListContainer;
+}
+
uint CellPhonePopup::contentScrollStep() const {
const Font *font = g_nancy->_graphics->getFont(_uiclData->fontId2);
if (!font) {
@@ -1181,11 +1187,7 @@ uint CellPhonePopup::contentScrollStep() const {
// Original: one click scrolls ~1/10th of the article (capped near a full
// page), plus 1.25 line heights.
- const Common::Rect &ws =
- (isHelpContentView() || _uiclData->emailListContainer.isEmpty())
- ? _uiclData->welcomeScreen.destRect
- : _uiclData->emailListContainer;
- const int viewH = MAX(0, ws.height() - 2);
+ const int viewH = MAX(0, contentViewScreenRect().height() - 2);
int page = MIN((int)_contentCacheTextHeight / 10, MAX(0, viewH - 30));
return (font->getFontHeight() * 5) / 4 + page;
}
@@ -1204,10 +1206,7 @@ void CellPhonePopup::drawContentView() {
// blits fullEmptyScreenSrc), so the keypad is no longer visible underneath
// and we render into the larger LCD area that emailListContainer defines.
// The help page keeps the regular chrome, so it renders into the small LCD.
- const Common::Rect &ws =
- (isHelpContentView() || _uiclData->emailListContainer.isEmpty())
- ? _uiclData->welcomeScreen.destRect
- : _uiclData->emailListContainer;
+ const Common::Rect &ws = contentViewScreenRect();
const int lcdLeft = ws.left - _screenPosition.left;
const int lcdTop = ws.top - _screenPosition.top;
const int lcdW = ws.width();
@@ -2687,6 +2686,25 @@ void CellPhonePopup::handleInput(NancyInput &input) {
const Common::Point popupMouseLink(chunkMouse.x - _screenPosition.left,
chunkMouse.y - _screenPosition.top);
+ // The mouse wheel scrolls the page over the LCD itself, by the same
+ // amount as a click on the up/down arrows. drawContentView() clamps.
+ if ((input.input & NancyInput::kMouseWheel) &&
+ contentViewScreenRect().contains(chunkMouse)) {
+ const uint wheelStep = contentScrollStep();
+ const uint oldScroll = _contentScroll;
+ if (input.input & NancyInput::kMouseWheelUp) {
+ _contentScroll = _contentScroll > wheelStep ? _contentScroll - wheelStep : 0;
+ } else {
+ _contentScroll += wheelStep;
+ }
+
+ input.eatMouseWheelInput();
+
+ if (_contentScroll != oldScroll) {
+ drawScreenContent();
+ }
+ }
+
// The main browser page carries the top-row SEARCH button (subButtons[8])
// which opens the search list; it highlights green while hovered.
if (_contentHeading == &_uiclData->browserHeading && !isBrowserArticle() &&
diff --git a/engines/nancy/ui/cellphonepopup.h b/engines/nancy/ui/cellphonepopup.h
index 3c9003533d1..d71a955c265 100644
--- a/engines/nancy/ui/cellphonepopup.h
+++ b/engines/nancy/ui/cellphonepopup.h
@@ -181,6 +181,9 @@ private:
// surface (+ text height, image/link hotspots). Called by drawContentView
// only when the page key changes.
void renderContentPage(int surfaceWidth);
+ // The LCD area the content view renders into: the small screen for the help
+ // page, the taller zoomed-in one for browser / email articles.
+ const Common::Rect &contentViewScreenRect() const;
// Per-click scroll amount (pixels) for the article/help content view.
uint contentScrollStep() const;
// Enter the content view for a list entry whose AUTOTEXT key is `key`.
diff --git a/engines/nancy/ui/conversationpopup.cpp b/engines/nancy/ui/conversationpopup.cpp
index b91c3a4254e..32977ada207 100644
--- a/engines/nancy/ui/conversationpopup.cpp
+++ b/engines/nancy/ui/conversationpopup.cpp
@@ -27,6 +27,8 @@
#include "engines/nancy/state/scene.h"
+#include "engines/nancy/ui/scrollbar.h"
+
#include "engines/nancy/ui/conversationpopup.h"
namespace Nancy {
@@ -273,6 +275,11 @@ void ConversationPopup::handleInput(NancyInput &input) {
return;
}
+ if (scrollWithMouseWheel(input, _screenPosition, _scrollPos,
+ wheelScrollStep(getLocalTextRect().height(), getInnerHeight()))) {
+ redrawScroll();
+ }
+
if (overThumb != _scrollbarHovered) {
_scrollbarHovered = overThumb;
drawScrollbar(overThumb ? kUIButtonHover : kUIButtonIdle);
diff --git a/engines/nancy/ui/inventorybox.cpp b/engines/nancy/ui/inventorybox.cpp
index 28b8661679b..da709be0d45 100644
--- a/engines/nancy/ui/inventorybox.cpp
+++ b/engines/nancy/ui/inventorybox.cpp
@@ -98,6 +98,17 @@ void InventoryBox::handleInput(NancyInput &input) {
}
if (_order.size()) {
+ // The mouse wheel scrolls a whole page of items at a time, both over the
+ // item grid and over the scrollbar itself
+ const uint numPages = (_order.size() - 1) / 4 + 1;
+ Common::Rect wheelArea = _screenPosition;
+ wheelArea.extend(_scrollbar->getTrackRect());
+
+ float scrollPos = _scrollbar->getPos();
+ if (scrollWithMouseWheel(input, wheelArea, scrollPos, 1.0f / (float)numPages)) {
+ _scrollbar->setPosition(scrollPos);
+ }
+
_scrollbar->handleInput(input);
}
diff --git a/engines/nancy/ui/inventorypopup.cpp b/engines/nancy/ui/inventorypopup.cpp
index 3b9b8bf482c..3052673dd19 100644
--- a/engines/nancy/ui/inventorypopup.cpp
+++ b/engines/nancy/ui/inventorypopup.cpp
@@ -29,6 +29,7 @@
#include "engines/nancy/state/scene.h"
#include "engines/nancy/ui/inventorypopup.h"
+#include "engines/nancy/ui/scrollbar.h"
#include "engines/nancy/ui/taskbar.h"
namespace Nancy {
@@ -427,6 +428,13 @@ void InventoryPopup::handleInput(NancyInput &input) {
return;
}
+ // The mouse wheel moves a whole page of items at a time
+ const uint numPages = (_visibleItems.size() + kSlotsPerPage - 1) / kSlotsPerPage;
+ if (numPages > 1 && scrollWithMouseWheel(input, _screenPosition, _scrollPos, 1.0f / (float)(numPages - 1))) {
+ updatePageFromScroll();
+ refreshGrid();
+ }
+
if (overScrollbar != _scrollbarHovered) {
_scrollbarHovered = overScrollbar;
drawScrollbar(overScrollbar ? kUIButtonHover : kUIButtonIdle);
diff --git a/engines/nancy/ui/notebookpopup.cpp b/engines/nancy/ui/notebookpopup.cpp
index d329d03d4f2..c78eccb5d3d 100644
--- a/engines/nancy/ui/notebookpopup.cpp
+++ b/engines/nancy/ui/notebookpopup.cpp
@@ -33,6 +33,7 @@
#include "engines/nancy/state/scene.h"
+#include "engines/nancy/ui/scrollbar.h"
#include "engines/nancy/ui/taskbar.h"
#include "engines/nancy/ui/notebookpopup.h"
@@ -315,6 +316,11 @@ void NotebookPopup::handleInput(NancyInput &input) {
return;
}
+ if (scrollWithMouseWheel(input, _screenPosition, _scrollPos,
+ wheelScrollStep(toPopupLocal(_uinbData->textRect, false).height(), _drawnTextHeight))) {
+ redrawScroll();
+ }
+
if (overThumb != _scrollbarHovered) {
_scrollbarHovered = overThumb;
drawScrollbar(overThumb ? kUIButtonHover : kUIButtonIdle);
diff --git a/engines/nancy/ui/scrollbar.cpp b/engines/nancy/ui/scrollbar.cpp
index c59b45a7d6c..b11f2c7d009 100644
--- a/engines/nancy/ui/scrollbar.cpp
+++ b/engines/nancy/ui/scrollbar.cpp
@@ -29,6 +29,38 @@
namespace Nancy {
namespace UI {
+// A single mouse wheel notch scrolls this much of the visible height
+static const float kWheelScrollPageFraction = 0.25f;
+
+int wheelScrollPixels(int visibleHeight) {
+ return (int)(visibleHeight * kWheelScrollPageFraction);
+}
+
+float wheelScrollStep(int visibleHeight, int contentHeight) {
+ const int scrollableHeight = contentHeight - visibleHeight;
+ if (visibleHeight <= 0 || scrollableHeight <= 0) {
+ return 0.0f;
+ }
+
+ return (float)wheelScrollPixels(visibleHeight) / (float)scrollableHeight;
+}
+
+bool scrollWithMouseWheel(NancyInput &input, const Common::Rect &hotspot, float &scrollPos, float step) {
+ if (!(input.input & NancyInput::kMouseWheel) || step <= 0.0f || !hotspot.contains(input.mousePos)) {
+ return false;
+ }
+
+ const float newPos = CLIP<float>(scrollPos + (input.input & NancyInput::kMouseWheelUp ? -step : step), 0.0f, 1.0f);
+ input.eatMouseWheelInput();
+
+ if (newPos == scrollPos) {
+ return false;
+ }
+
+ scrollPos = newPos;
+ return true;
+}
+
Scrollbar::Scrollbar(uint16 zOrder, const Common::Rect &srcBounds, const Common::Point &topPosition, uint16 scrollDistance, bool isVertical) :
Scrollbar(zOrder, srcBounds, g_nancy->_graphics->_object0, topPosition, scrollDistance, isVertical) {}
@@ -111,6 +143,19 @@ void Scrollbar::handleInput(NancyInput &input) {
}
}
+Common::Rect Scrollbar::getTrackRect() const {
+ Common::Rect track = _screenPosition;
+ track.moveTo(_startPosition);
+
+ if (_isVertical) {
+ track.bottom += _maxDist;
+ } else {
+ track.right += _maxDist;
+ }
+
+ return track;
+}
+
void Scrollbar::setPosition(float pos) {
_currentPosition = pos;
if (_isVertical) {
diff --git a/engines/nancy/ui/scrollbar.h b/engines/nancy/ui/scrollbar.h
index 07a0ee72760..a774b3798ef 100644
--- a/engines/nancy/ui/scrollbar.h
+++ b/engines/nancy/ui/scrollbar.h
@@ -30,6 +30,18 @@ struct NancyInput;
namespace UI {
+// Returns the number of pixels a single mouse wheel notch should scroll a view of
+// the given height, so that scrolling advances by a fixed part of a page.
+int wheelScrollPixels(int visibleHeight);
+
+// Same, expressed as a fraction of the total scroll range. Returns 0 when the
+// content fits inside the viewport and thus cannot be scrolled.
+float wheelScrollStep(int visibleHeight, int contentHeight);
+
+// Applies mouse wheel input to a [0, 1] scroll position, as long as the mouse is
+// inside the hotspot. Consumes the wheel input and returns true if the position changed.
+bool scrollWithMouseWheel(NancyInput &input, const Common::Rect &hotspot, float &scrollPos, float step);
+
class Scrollbar : public RenderObject {
public:
Scrollbar(uint16 zOrder, const Common::Rect &srcBounds, const Common::Point &topPosition, uint16 scrollDistance, bool isVertical = true);
@@ -43,6 +55,10 @@ public:
void resetPosition();
float getPos() const { return _currentPosition; }
+ // The screen rect the thumb travels along. Unlike _screenPosition, which only
+ // covers the thumb itself, this covers the whole scrollbar.
+ Common::Rect getTrackRect() const;
+
void setPosition(float pos);
void calculatePosition();
diff --git a/engines/nancy/ui/scrolltextbox.cpp b/engines/nancy/ui/scrolltextbox.cpp
index ad2f24153f9..480fd46a1d1 100644
--- a/engines/nancy/ui/scrolltextbox.cpp
+++ b/engines/nancy/ui/scrolltextbox.cpp
@@ -27,6 +27,7 @@
#include "engines/nancy/state/scene.h"
+#include "engines/nancy/ui/scrollbar.h"
#include "engines/nancy/ui/scrolltextbox.h"
namespace Nancy {
@@ -407,6 +408,11 @@ void ScrollTextBox::handleInput(NancyInput &input) {
return;
}
+ if (scrollWithMouseWheel(input, _screenPosition, _scrollPos,
+ wheelScrollStep(textViewportLocal().height(), getInnerHeight()))) {
+ redrawScroll();
+ }
+
if (overThumb != _scrollbarHovered) {
_scrollbarHovered = overThumb;
drawScrollbar(overThumb ? kUIButtonHover : kUIButtonIdle);
diff --git a/engines/nancy/ui/textbox.cpp b/engines/nancy/ui/textbox.cpp
index ca2eda74480..0fc6a8a1bb0 100644
--- a/engines/nancy/ui/textbox.cpp
+++ b/engines/nancy/ui/textbox.cpp
@@ -137,8 +137,18 @@ void Textbox::handleInput(NancyInput &input) {
if (!isVisible())
return;
- if (_scrollbar)
+ if (_scrollbar) {
+ Common::Rect wheelArea = _screenPosition;
+ wheelArea.extend(_scrollbar->getTrackRect());
+
+ float scrollPos = _scrollbar->getPos();
+ if (scrollWithMouseWheel(input, wheelArea, scrollPos,
+ wheelScrollStep(_screenPosition.height(), getInnerHeight()))) {
+ _scrollbar->setPosition(scrollPos);
+ }
+
_scrollbar->handleInput(input);
+ }
bool hasHighlight = false;
for (uint i = 0; i < _hotspots.size(); ++i) {
Commit: 1d4874acffb537a6ad01857afb1751f095ebab69
https://github.com/scummvm/scummvm/commit/1d4874acffb537a6ad01857afb1751f095ebab69
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-08-20T00:53:35+03:00
Commit Message:
NEWS: Add entries for new functionality and fixes for Nancy games
Changed paths:
NEWS.md
diff --git a/NEWS.md b/NEWS.md
index 7592f650650..e9a1752e34e 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -76,6 +76,11 @@ For a more comprehensive changelog of the latest experimental code, see:
- Generic flags are no longer cleared when saving or auto-saving. Clearing
them introduced issues when auto-saving was done in some scenes.
- Loading from the GMM is now possible before starting a new scene.
+ - It's now possible to skip cutscenes and dialog lines with escape.
+ - Mouse wheel functionality has been added to several UI widgets.
+ - Fixed issues with the memory puzzle in Nancy9.
+ - Fixed issues with the "I can't" item sound.
+ - Answers entered in password kind of puzzles are now more forgiving (e.g. "Xoc" and "Lady Xoc" are now both acceptable answers).
SCUMM:
- Improved cursor scale in early Windows HE games.
More information about the Scummvm-git-logs
mailing list