[Scummvm-git-logs] scummvm master -> eba454f0e5e9d2ae0f45ac52a9ba86eaab040d1f
bluegr
noreply at scummvm.org
Mon Jul 6 23:47:17 UTC 2026
This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
f6bec97f04 NANCY: NANCY10: Don't handle the cellphone keypad when it isn't visible
47b5e078fa NANCY: NANCY10: Implement UI prep scene functionality
eba454f0e5 NANCY: Read autotext strings in a case-insensitive manner
Commit: f6bec97f044b832769804f478f268de2dfffcf7c
https://github.com/scummvm/scummvm/commit/f6bec97f044b832769804f478f268de2dfffcf7c
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-07T02:47:03+03:00
Commit Message:
NANCY: NANCY10: Don't handle the cellphone keypad when it isn't visible
Fixes clicking in zoomed in views (web, email, browser)
Changed paths:
engines/nancy/ui/cellphonepopup.cpp
diff --git a/engines/nancy/ui/cellphonepopup.cpp b/engines/nancy/ui/cellphonepopup.cpp
index 7bdb0691239..fe57e098486 100644
--- a/engines/nancy/ui/cellphonepopup.cpp
+++ b/engines/nancy/ui/cellphonepopup.cpp
@@ -1629,11 +1629,22 @@ void CellPhonePopup::handleInput(NancyInput &input) {
drawScreenContent();
}
+ // The keypad is only on screen in the non-zoomed chrome (welcome / dialing /
+ // directory / help), so its slots are only interactive there. The zoomed
+ // web / email / browser views hide the keypad but keep the slots' dest rects
+ // covering that now-blank area, so without this guard hovering or clicking
+ // there would light the underlying key sprites and even switch to dialing.
+ const bool keypadVisible = !isZoomedChromeState() || isHelpContentView();
+
// Depress the dial-pad key under the cursor while the mouse button is held;
// clear it on release (this runs before the click handlers so the depressed
// sprite isn't left behind once the key's action redraws the screen).
+ // Skip when the cursor is over a scroll arrow: the arrows overlap the dial
+ // pad geometrically (e.g. the down arrow sits over the "#" key), so pressing
+ // one would otherwise light the underlying key sprite.
int newPressed = -1;
- if ((input.input & (NancyInput::kLeftMouseButtonDown | NancyInput::kLeftMouseButtonHeld)) &&
+ if (keypadVisible && !overUp && !overDown &&
+ (input.input & (NancyInput::kLeftMouseButtonDown | NancyInput::kLeftMouseButtonHeld)) &&
!(input.input & NancyInput::kLeftMouseButtonUp)) {
for (uint i = 0; i < UICL::kNumDialPadSlots; ++i) {
if (_uiclData->dialPadSlots[i].destRect.contains(chunkMouse)) {
@@ -1923,8 +1934,9 @@ void CellPhonePopup::handleInput(NancyInput &input) {
}
// Call/talk button. Checked before the dial-pad loop so an overlapping
- // slot can't eat it. The Talk key is dial-pad slot 12.
- if (_uiclData->dialPadSlots[12].destRect.contains(chunkMouse)) {
+ // slot can't eat it. The Talk key is dial-pad slot 12. Only live while the
+ // keypad is on screen (skipped in the zoomed web / email / browser views).
+ if (keypadVisible && _uiclData->dialPadSlots[12].destRect.contains(chunkMouse)) {
g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
if (input.input & NancyInput::kLeftMouseButtonUp) {
@@ -1957,11 +1969,13 @@ void CellPhonePopup::handleInput(NancyInput &input) {
// 13 - web mode (TODO)
// 14 - directory toggle
int newHovered = -1;
- for (uint i = 0; i < UICL::kNumDialPadSlots; ++i) {
- const UICL::DialPadSlot &slot = _uiclData->dialPadSlots[i];
- if (slot.destRect.contains(chunkMouse)) {
- newHovered = (int)i;
- break;
+ if (keypadVisible) {
+ for (uint i = 0; i < UICL::kNumDialPadSlots; ++i) {
+ const UICL::DialPadSlot &slot = _uiclData->dialPadSlots[i];
+ if (slot.destRect.contains(chunkMouse)) {
+ newHovered = (int)i;
+ break;
+ }
}
}
_hoveredSlot = newHovered;
Commit: 47b5e078fa67733786abce69574a6ad7bf8d335f
https://github.com/scummvm/scummvm/commit/47b5e078fa67733786abce69574a6ad7bf8d335f
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-07T02:47:03+03:00
Commit Message:
NANCY: NANCY10: Implement UI prep scene functionality
This functionality was initially introduced in Nancy10, but is unused.
It started being used in Nancy11, for its Notebook functionality.
With this, the notebook in Nancy11 is finally operational
Changed paths:
engines/nancy/action/miscrecords.cpp
engines/nancy/action/miscrecords.h
engines/nancy/commontypes.h
engines/nancy/state/scene.cpp
engines/nancy/state/scene.h
engines/nancy/ui/notebookpopup.cpp
engines/nancy/ui/notebookpopup.h
diff --git a/engines/nancy/action/miscrecords.cpp b/engines/nancy/action/miscrecords.cpp
index f641f0bda14..fcc744ef5ea 100644
--- a/engines/nancy/action/miscrecords.cpp
+++ b/engines/nancy/action/miscrecords.cpp
@@ -302,9 +302,10 @@ void UIPopupPrepScene::readData(Common::SeekableReadStream &stream) {
}
void UIPopupPrepScene::execute() {
- // TODO: finish this
-
- debug("UIPopupPrepScene: UIType=%d, signalValue=%d", _uiType, _signalValue);
+ // Terminates a UI prep scene chain: the entry-adding ARs in the prep
+ // scene(s) have run, so signal the scene to restore the pre-open scene and
+ // open the (now populated) popup. A no-op if no prep is currently running.
+ NancySceneState.finishUIPrepScene();
finishExecution();
}
diff --git a/engines/nancy/action/miscrecords.h b/engines/nancy/action/miscrecords.h
index 740e8eecb8e..c98b690bfd5 100644
--- a/engines/nancy/action/miscrecords.h
+++ b/engines/nancy/action/miscrecords.h
@@ -148,12 +148,6 @@ protected:
// (inventory / notebook / cellphone) is enabled.
class ControlUIItems : public ActionRecord {
public:
- enum UIType {
- kUITypeInventory = 1,
- kUITypeNotebook = 2,
- kUITypeCellphone = 3
- };
-
void readData(Common::SeekableReadStream &stream) override;
void execute() override;
diff --git a/engines/nancy/commontypes.h b/engines/nancy/commontypes.h
index 65833886434..4b5b58d3c63 100644
--- a/engines/nancy/commontypes.h
+++ b/engines/nancy/commontypes.h
@@ -50,6 +50,14 @@ static const int8 kEvNoEvent = -1;
static const int8 kFrNoFrame = -1;
static const uint16 kNoScene = 9999;
+// Taskbar popup UI types. Shared by ControlUIItems (AR 29), UIPopupPrepScene
+// (AR 32) and the Scene UI-prep-scene machinery.
+enum UIType {
+ kUITypeInventory = 1,
+ kUITypeNotebook = 2,
+ kUITypeCellphone = 3
+};
+
// Inventory items use types
static const byte kInvItemUseThenLose = 0;
static const byte kInvItemKeepAlways = 1;
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index a361dec0cb9..ca6e0c59cf6 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -291,6 +291,51 @@ void Scene::popScene(bool inventory) {
}
}
+void Scene::startUIPrepScene(int16 uiType, int16 prepSceneID) {
+ if (_uiPrep.active || (uint16)prepSceneID == kNoScene) {
+ return;
+ }
+
+ _uiPrep.active = true;
+ _uiPrep.uiType = uiType;
+ _uiPrep.returnScene = _sceneState.currentScene;
+ _uiPrep.startMillis = g_system->getMillis();
+
+ SceneChangeDescription desc;
+ desc.sceneID = (uint16)prepSceneID;
+ desc.frameID = 0;
+ desc.verticalOffset = 0;
+ changeScene(desc);
+}
+
+void Scene::finishUIPrepScene() {
+ if (!_uiPrep.active) {
+ return;
+ }
+
+ _uiPrep.active = false;
+
+ // Restore the scene we were in when the popup was opened, keeping its sound.
+ SceneChangeDescription ret = _uiPrep.returnScene;
+ ret.continueSceneSound = kContinueSceneSound;
+ changeScene(ret);
+
+ // Open the popup whose prep scene just populated its content.
+ switch (_uiPrep.uiType) {
+ case kUITypeInventory:
+ _inventoryPopup.open();
+ break;
+ case kUITypeNotebook:
+ _notebookPopup.open();
+ break;
+ case kUITypeCellphone:
+ _cellPhonePopup.open();
+ break;
+ default:
+ break;
+ }
+}
+
void Scene::setPlayerTime(Time time, byte relative) {
if (relative == kRelativeClockBump) {
// Relative, add the specified time to current playerTime
@@ -1100,7 +1145,10 @@ void Scene::load(bool fromSaveFile) {
_sceneState.currentScene.paletteID = 0;
}
- if (_sceneState.summary.videoFile != "NO_ART_SCENE") {
+ // "NO_ART_SCENE" and (Nancy 11+) "POPUP_PREP_SCENE" are videoless sentinel
+ // scenes that carry only logic ARs; they have no viewport art to load.
+ if (_sceneState.summary.videoFile != "NO_ART_SCENE" &&
+ _sceneState.summary.videoFile != "POPUP_PREP_SCENE") {
const Common::Path palettePath = !_sceneState.summary.palettes.empty() ?
_sceneState.summary.palettes[(byte)_sceneState.currentScene.paletteID] :
Common::Path();
@@ -1321,6 +1369,19 @@ Common::Rect Scene::activePopupConfinement() const {
}
void Scene::handleInput() {
+ // While a UI prep scene is running the player shouldn't be able to interact
+ // with the (hidden, videoless) prep scenes. Swallow all input until the
+ // prep's UIPopupPrepScene AR finishes it. A safety timeout guards against a
+ // prep scene that never reaches its terminator so the game can't lock up.
+ if (_uiPrep.active) {
+ if (g_system->getMillis() - _uiPrep.startMillis > 5000) {
+ warning("UI prep scene did not finish within timeout; aborting");
+ finishUIPrepScene();
+ }
+ g_nancy->_input->getInput();
+ return;
+ }
+
NancyInput input = g_nancy->_input->getInput();
// Warp the mouse below the inactive zone during dialogue scenes
@@ -1454,9 +1515,19 @@ void Scene::handleInput() {
case kTaskButtonInventory:
_inventoryPopup.toggle();
break;
- case kTaskButtonNotebook:
- _notebookPopup.toggle();
+ case kTaskButtonNotebook: {
+ // Nancy 11+ populates the notebook lazily: opening it first
+ // runs a hidden prep scene (header.linkbackScene) whose ARs
+ // add the journal / task entries. Games without a prep scene
+ // (linkbackScene == kNoScene, e.g. Nancy 10) just toggle.
+ const int16 prepScene = _notebookPopup.getPrepSceneID();
+ if (!_notebookPopup.isVisible() && (uint16)prepScene != kNoScene) {
+ startUIPrepScene(kUITypeNotebook, prepScene);
+ } else {
+ _notebookPopup.toggle();
+ }
break;
+ }
case kTaskButtonCellphone:
_cellPhonePopup.toggle();
break;
diff --git a/engines/nancy/state/scene.h b/engines/nancy/state/scene.h
index da7915b467d..9b82d09a945 100644
--- a/engines/nancy/state/scene.h
+++ b/engines/nancy/state/scene.h
@@ -120,6 +120,17 @@ public:
void changeScene(const SceneChangeDescription &sceneDescription);
void pushScene(int16 itemID = -1);
void popScene(bool inventory = false);
+
+ // Nancy 11+ "UI prep scenes": opening a taskbar popup first runs a hidden,
+ // videoless scene whose event-flag-gated ARs populate the popup's content;
+ // a UIPopupPrepScene AR (32) then calls finishUIPrepScene to restore the
+ // prior scene and open the (now populated) popup. startUIPrepScene saves
+ // the current scene and jumps to prepSceneID; uiType (a UIType) selects
+ // which popup finishUIPrepScene opens. No-op if a prep is already running
+ // or prepSceneID is kNoScene.
+ void startUIPrepScene(int16 uiType, int16 prepSceneID);
+ void finishUIPrepScene();
+ bool isUIPrepActive() const { return _uiPrep.active; }
uint16 getSceneCounts(int16 hours) const {
return _flags.sceneCounts.contains(hours) ? _flags.sceneCounts[hours] : 0;
}
@@ -362,6 +373,14 @@ private:
bool _destroyOnExit;
bool _isRunningAd;
+ // State for a running UI prep scene (see startUIPrepScene).
+ struct UIPrepState {
+ bool active = false;
+ int16 uiType = 0; // UIType of the popup that requested the prep
+ SceneChangeDescription returnScene;
+ uint32 startMillis = 0;
+ } _uiPrep;
+
State _state;
};
diff --git a/engines/nancy/ui/notebookpopup.cpp b/engines/nancy/ui/notebookpopup.cpp
index cb1bb60a907..bef65ec9683 100644
--- a/engines/nancy/ui/notebookpopup.cpp
+++ b/engines/nancy/ui/notebookpopup.cpp
@@ -118,6 +118,10 @@ void NotebookPopup::updateGraphics() {
}
}
+int16 NotebookPopup::getPrepSceneID() const {
+ return _uinbData ? (int16)_uinbData->header.linkbackScene : (int16)kNoScene;
+}
+
void NotebookPopup::open() {
if (_isVisible)
return;
diff --git a/engines/nancy/ui/notebookpopup.h b/engines/nancy/ui/notebookpopup.h
index 78f2f1a57ae..10969d1bd6b 100644
--- a/engines/nancy/ui/notebookpopup.h
+++ b/engines/nancy/ui/notebookpopup.h
@@ -49,6 +49,11 @@ public:
void close();
void toggle() { if (_isVisible) close(); else open(); }
+ // Nancy 11+ lazily populates the notebook via a hidden "prep scene" run when
+ // it opens. Returns that scene ID (UINB header.linkbackScene), or kNoScene
+ // (9999) for games without one (e.g. Nancy 10), which populate inline.
+ int16 getPrepSceneID() const;
+
// Re-render the active tab's text content into the text rect.
// Called automatically on open() and on tab switch; Scene also
// invokes it after a ModifyListEntry AR runs while the popup is open.
Commit: eba454f0e5e9d2ae0f45ac52a9ba86eaab040d1f
https://github.com/scummvm/scummvm/commit/eba454f0e5e9d2ae0f45ac52a9ba86eaab040d1f
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-07T02:47:04+03:00
Commit Message:
NANCY: Read autotext strings in a case-insensitive manner
Nancy10 and newer look up autotext texts in a case-insensitive manner,
so we do that, too.
Changed paths:
engines/nancy/action/conversation.cpp
engines/nancy/action/miscrecords.cpp
engines/nancy/action/puzzle/cardgamepuzzle.cpp
engines/nancy/action/soundrecords.cpp
engines/nancy/enginedata.h
engines/nancy/ui/cellphonepopup.cpp
engines/nancy/ui/notebookpopup.cpp
engines/nancy/util.cpp
engines/nancy/util.h
diff --git a/engines/nancy/action/conversation.cpp b/engines/nancy/action/conversation.cpp
index 3b4c45ff4ef..46a6fbd8971 100644
--- a/engines/nancy/action/conversation.cpp
+++ b/engines/nancy/action/conversation.cpp
@@ -186,7 +186,7 @@ void ConversationSound::readTerseCaptionText(Common::SeekableReadStream &stream)
const CVTX *convo = (const CVTX *)g_nancy->getEngineData("CONVO");
assert(convo);
- _text = getTextFromCaseInsensitiveKey(convo->texts, key);
+ _text = convo->texts.getValOrDefault(key, "");
}
void ConversationSound::readTerseResponseText(Common::SeekableReadStream &stream, ResponseStruct &response) {
@@ -196,7 +196,7 @@ void ConversationSound::readTerseResponseText(Common::SeekableReadStream &stream
const CVTX *convo = (const CVTX *)g_nancy->getEngineData("CONVO");
assert(convo);
- response.text = getTextFromCaseInsensitiveKey(convo->texts, key);
+ response.text = convo->texts.getValOrDefault(key, "");
}
void ConversationSound::execute() {
diff --git a/engines/nancy/action/miscrecords.cpp b/engines/nancy/action/miscrecords.cpp
index fcc744ef5ea..9cbd372b5a3 100644
--- a/engines/nancy/action/miscrecords.cpp
+++ b/engines/nancy/action/miscrecords.cpp
@@ -119,7 +119,7 @@ static void readTextboxText(Common::SeekableReadStream &stream, Common::String &
const CVTX *autotext = (const CVTX *)g_nancy->getEngineData("AUTOTEXT");
assert(autotext);
- out = getTextFromCaseInsensitiveKey(autotext->texts, stringID);
+ out = autotext->texts.getValOrDefault(stringID, "");
} else if (size > 0) {
char *buf = new char[size];
stream.read(buf, size);
diff --git a/engines/nancy/action/puzzle/cardgamepuzzle.cpp b/engines/nancy/action/puzzle/cardgamepuzzle.cpp
index df99349ddeb..6e8cc7a35d5 100644
--- a/engines/nancy/action/puzzle/cardgamepuzzle.cpp
+++ b/engines/nancy/action/puzzle/cardgamepuzzle.cpp
@@ -463,7 +463,7 @@ void CardGamePuzzle::showSubtitle(const Common::String &soundName) {
return;
}
- Common::String text = getTextFromCaseInsensitiveKey(autotext->texts, soundName);
+ Common::String text = autotext->texts.getValOrDefault(soundName, "");
if (!text.empty()) {
NancySceneState.getTextbox().clear();
NancySceneState.getTextbox().addTextLine(text);
diff --git a/engines/nancy/action/soundrecords.cpp b/engines/nancy/action/soundrecords.cpp
index c9d91b2651f..18ef66d6693 100644
--- a/engines/nancy/action/soundrecords.cpp
+++ b/engines/nancy/action/soundrecords.cpp
@@ -213,7 +213,7 @@ void PlaySoundCC::readCCText(Common::SeekableReadStream &stream, Common::String
const CVTX *autotext = (const CVTX *)g_nancy->getEngineData("AUTOTEXT");
assert(autotext);
- out = getTextFromCaseInsensitiveKey(autotext->texts, key);
+ out = autotext->texts.getValOrDefault(key, "");
}
}
diff --git a/engines/nancy/enginedata.h b/engines/nancy/enginedata.h
index 40d56017f4c..680964e8d7b 100644
--- a/engines/nancy/enginedata.h
+++ b/engines/nancy/enginedata.h
@@ -480,7 +480,7 @@ struct ImageChunk : public EngineData {
struct CVTX : public EngineData {
CVTX(Common::SeekableReadStream *chunkStream);
- Common::HashMap<Common::String, Common::String> texts;
+ Common::HashMap<Common::String, Common::String, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> texts;
};
struct TABL : public EngineData {
diff --git a/engines/nancy/ui/cellphonepopup.cpp b/engines/nancy/ui/cellphonepopup.cpp
index fe57e098486..2d38db329c1 100644
--- a/engines/nancy/ui/cellphonepopup.cpp
+++ b/engines/nancy/ui/cellphonepopup.cpp
@@ -791,7 +791,7 @@ void CellPhonePopup::drawLinkList() {
}
Common::String lookupKey = list[absolute].key;
- Common::String rowText = getTextFromCaseInsensitiveKey(autotext->texts, lookupKey);
+ Common::String rowText = autotext->texts.getValOrDefault(lookupKey, "");
// Single-line draw â drop every <n> markup so they don't render as
// literal "<n>" glyphs and crowd the row.
@@ -859,7 +859,7 @@ void CellPhonePopup::drawContentView() {
// Render the engine's hypertext markup into a tall scratch surface,
// then blit a vertically-scrolled window of it into the LCD.
- const Common::String renderText = getTextFromCaseInsensitiveKey(autotext->texts, _contentKey);
+ const Common::String renderText = autotext->texts.getValOrDefault(_contentKey, "");
// Find this page in the UIBW chunk (browser pages only); its hotspot
// records are the per-page image table the article references.
diff --git a/engines/nancy/ui/notebookpopup.cpp b/engines/nancy/ui/notebookpopup.cpp
index bef65ec9683..88e588ebeda 100644
--- a/engines/nancy/ui/notebookpopup.cpp
+++ b/engines/nancy/ui/notebookpopup.cpp
@@ -471,7 +471,7 @@ void NotebookPopup::buildTextLines() {
Common::String combined;
for (int i = (int)entries.size() - 1; i >= 0; --i) {
Common::String stringID = entries[i].stringID;
- Common::String body = getTextFromCaseInsensitiveKey(autotext->texts, stringID);
+ Common::String body = autotext->texts.getValOrDefault(stringID, "");
if (surfaceID == kNotebookTabTasks && entries[i].mark != 0) {
uint16 markValue = entries[i].mark;
diff --git a/engines/nancy/util.cpp b/engines/nancy/util.cpp
index 57e3946334a..971d90a6df4 100644
--- a/engines/nancy/util.cpp
+++ b/engines/nancy/util.cpp
@@ -344,39 +344,6 @@ void assembleTextLine(char *rawCaption, Common::String &output, uint size) {
}
}
-Common::String getTextFromCaseInsensitiveKey(Common::HashMap<Common::String, Common::String> texts, const Common::String &key) {
- if (texts.contains(key)) {
- return texts[key];
- } else {
- // Nancy10+ searched keyed texts in a key insensitive way, but
- // the possible permutations involve mainly the last character
- // being upper or lower case, so just try that before giving up.
- Common::String keyCopy = key;
- if (keyCopy[keyCopy.size() - 1] == toupper(keyCopy[keyCopy.size() - 1]))
- keyCopy[keyCopy.size() - 1] = tolower(keyCopy[keyCopy.size() - 1]);
- else
- keyCopy[keyCopy.size() - 1] = toupper(keyCopy[keyCopy.size() - 1]);
-
- if (texts.contains(keyCopy))
- return texts[keyCopy];
-
- // Try all uppercase
- keyCopy.toUppercase();
-
- if (texts.contains(keyCopy))
- return texts[keyCopy];
-
- // Check for lowercase cases for the second to last character, for Nancy11+
- keyCopy[keyCopy.size() - 2] = tolower(keyCopy[keyCopy.size() - 2]);
-
- if (texts.contains(keyCopy))
- return texts[keyCopy];
- }
-
- warning("Key not found: %s", key.c_str());
- return "";
-}
-
bool DeferredLoader::load(uint32 endTime) {
uint32 loopStartTime = g_system->getMillis();
uint32 loopTime = 0; // Stores the loop that took the longest time to complete
diff --git a/engines/nancy/util.h b/engines/nancy/util.h
index c60752e1a84..2d62fde1269 100644
--- a/engines/nancy/util.h
+++ b/engines/nancy/util.h
@@ -65,8 +65,6 @@ void readUISlider(Common::SeekableReadStream &stream, UISliderRecord &dst);
void readUIPopupHeader(Common::SeekableReadStream &stream, UIPopupHeader &dst);
void readUIButtonSlot(Common::SeekableReadStream &stream, UIButtonSlot &dst);
-Common::String getTextFromCaseInsensitiveKey(Common::HashMap<Common::String, Common::String> texts, const Common::String &key);
-
// Abstract base class used for loading data that would take too much time in a single frame
class DeferredLoader {
public:
More information about the Scummvm-git-logs
mailing list