[Scummvm-git-logs] scummvm master -> a46f421625b28d4877a3f35e02b394ed1d321bad
bluegr
noreply at scummvm.org
Sun Jul 5 22:15:59 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:
bebf0971aa NANCY: NANCY10: Play click sound when clicking on inventory buttons
fd77304ce5 NANCY: NANCY10: Cellphone interface fixes
e71d9b4bf5 NANCY: NANCY10: Disable taskbar buttons when a popup is open
11efa8821f NANCY: NANCY10: Cleanup comments
a46f421625 NANCY: NANCY10: Notebook fixes
Commit: bebf0971aa8f3db80411b7f5de423dc8a89d6144
https://github.com/scummvm/scummvm/commit/bebf0971aa8f3db80411b7f5de423dc8a89d6144
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-06T01:15:42+03:00
Commit Message:
NANCY: NANCY10: Play click sound when clicking on inventory buttons
Fix #16901
Changed paths:
engines/nancy/ui/inventorypopup.cpp
engines/nancy/ui/inventorypopup.h
diff --git a/engines/nancy/ui/inventorypopup.cpp b/engines/nancy/ui/inventorypopup.cpp
index 1aafab3899b..5ceeb619ac2 100644
--- a/engines/nancy/ui/inventorypopup.cpp
+++ b/engines/nancy/ui/inventorypopup.cpp
@@ -281,6 +281,15 @@ void InventoryPopup::setActiveFilterIndex(uint index) {
_activeFilterIndex = index;
}
+void InventoryPopup::playButtonClickSound(const UIButtonRecord &button) {
+ SoundDescription sound = button.clickSound;
+ if (sound.name.empty() || sound.name.equalsIgnoreCase("NO SOUND"))
+ return;
+
+ g_nancy->_sound->loadSound(sound);
+ g_nancy->_sound->playSound(sound);
+}
+
void InventoryPopup::drawSlot(uint slotIndex, int16 itemId) {
if (slotIndex >= _uiivData->slotDestRects.size())
return;
@@ -386,6 +395,7 @@ void InventoryPopup::handleInput(NancyInput &input) {
if (overClose) {
g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
if (input.input & NancyInput::kLeftMouseButtonUp) {
+ playButtonClickSound(closeBtn);
input.eatMouseInput();
close();
return;
@@ -489,6 +499,7 @@ void InventoryPopup::handleInput(NancyInput &input) {
drawFilterTab(i, true);
if (input.input & NancyInput::kLeftMouseButtonUp) {
+ playButtonClickSound(filter.button);
setActiveFilterIndex(i);
_currentPage = 0;
_scrollPos = 0.0f;
diff --git a/engines/nancy/ui/inventorypopup.h b/engines/nancy/ui/inventorypopup.h
index e3d065e5d92..e18896e7a5e 100644
--- a/engines/nancy/ui/inventorypopup.h
+++ b/engines/nancy/ui/inventorypopup.h
@@ -86,6 +86,12 @@ private:
void rebuildVisibleList();
void setActiveFilterIndex(uint index);
+ // Play a popup button's click sound (the filter tabs and the close X),
+ // mirroring the original's per-CUIButton click cue. Falls back to the
+ // shared button-click slot in the popup header when the button carries no
+ // sound of its own.
+ void playButtonClickSound(const UIButtonRecord &button);
+
// Apply the current scrollbar position to the page index, clamping
// to the number of pages required by the active filter.
void updatePageFromScroll();
Commit: fd77304ce5ccf03ca89ea09d01b5fe57f4511ba1
https://github.com/scummvm/scummvm/commit/fd77304ce5ccf03ca89ea09d01b5fe57f4511ba1
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-06T01:15:43+03:00
Commit Message:
NANCY: NANCY10: Cellphone interface fixes
- Fix margin and add scroll buttons to help screen - fix #16902
- Hide help button during calls - fix #16903
- Implement auto-dial - fix #16904
- Add missing keypad sounds - fix #16905
- Add back button when reading e-mail messages - fix #16906
- Play click sound when clicking the close button
Changed paths:
engines/nancy/ui/cellphonepopup.cpp
engines/nancy/ui/cellphonepopup.h
diff --git a/engines/nancy/ui/cellphonepopup.cpp b/engines/nancy/ui/cellphonepopup.cpp
index 6db57cb5804..1a421580b4e 100644
--- a/engines/nancy/ui/cellphonepopup.cpp
+++ b/engines/nancy/ui/cellphonepopup.cpp
@@ -410,7 +410,10 @@ void CellPhonePopup::drawChrome() {
: _uiclData->header.normalSrcRect;
_drawSurface.blitFrom(_overlayImage, chromeSrc, Common::Point(0, 0));
drawCloseButton(_closeButtonHovered);
- if (!isSubScreenState()) {
+ // The help "?" button lives on the dialer face only. The original hides
+ // it once a call is being placed (the connecting / "We're sorry" screens)
+ // and on every sub-screen that shows its own heading.
+ if (_screenState == kWelcome || _screenState == kDialing) {
drawHelpButton(0);
}
_needsRedraw = true;
@@ -511,14 +514,11 @@ void CellPhonePopup::drawScreenContent() {
drawHeading(*_contentHeading);
}
drawContentView();
- // The help page is a static small-window blurb with no scroll arrows;
- // browser / email articles keep the big-screen arrows. Help shows the
- // Back button in the lower ribbon (returns to the main screen).
- if (!isHelpContentView()) {
- drawDirectoryArrows();
- } else {
- drawBackLabel();
- }
+ // Help's Back sits in the lower ribbon (subButtons[0]); the zoomed
+ // articles' Back sits at the bottom of the screen (subButtons[7]).
+ // drawDirectoryArrows() blits whichever scroll pair applies.
+ drawDirectoryArrows();
+ drawBackButton(isHelpContentView() ? 0 : 7);
break;
}
@@ -835,7 +835,11 @@ void CellPhonePopup::drawContentView() {
const int lcdTop = ws.top - _screenPosition.top;
const int lcdW = ws.width();
const int lcdH = ws.height();
- const int textTop = 22; // clear the heading sprite
+ // The email / browser heading sits inside the zoomed LCD, so the article
+ // text starts below it. The help page's heading is in the title-bar strip
+ // above the small LCD, so its text starts flush with the LCD top (matching
+ // the original's "Help info in small window" placement).
+ const int textTop = isHelpContentView() ? 2 : 22;
const int viewH = MAX(0, lcdH - textTop);
const int rowH = MAX(font->getFontHeight() + 1, 12);
@@ -999,10 +1003,11 @@ void CellPhonePopup::drawWelcomeScreen() {
ws.destRect.top - chunkOrigin.y));
}
-void CellPhonePopup::drawBackLabel() {
+void CellPhonePopup::drawBackButton(uint subButtonIndex) {
// subButtons[0] (original CUIButton 0x10) is the Back button that returns a
- // sub-screen to the main view; it sits at the left of the lower ribbon.
- const UICL::ThreeRectWidget &back = _uiclData->subButtons[0];
+ // sub-screen to the main view; subButtons[7] (0x17) is the equivalent Back
+ // button at the bottom of the zoomed email / browser content view.
+ const UICL::ThreeRectWidget &back = _uiclData->subButtons[subButtonIndex];
if (back.srcRectIdle.isEmpty() || back.destRect.isEmpty()) {
return;
}
@@ -1013,9 +1018,9 @@ void CellPhonePopup::drawBackLabel() {
back.destRect.top - chunkOrigin.y));
}
-Common::Rect CellPhonePopup::backButtonHitRect() const {
- // Popup-local hit rect for the Back button (subButtons[0]).
- Common::Rect r = _uiclData->subButtons[0].destRect;
+Common::Rect CellPhonePopup::backButtonHitRect(uint subButtonIndex) const {
+ // Popup-local hit rect for a Back sub-button.
+ Common::Rect r = _uiclData->subButtons[subButtonIndex].destRect;
if (r.isEmpty()) {
return r;
}
@@ -1024,15 +1029,15 @@ Common::Rect CellPhonePopup::backButtonHitRect() const {
}
const UICL::ThreeRectWidget &CellPhonePopup::scrollUpButton() const {
- // Directory uses subButtons[1]; search / email / browser content all
- // use subButtons[5] (which sits above the taller list LCD area).
- return _screenState == kDirectory
+ // Directory and help both scroll with the small-LCD arrow pair
+ // (subButtons[1]/[2]); the zoomed email / browser articles use [5]/[6].
+ return (_screenState == kDirectory || isHelpContentView())
? _uiclData->subButtons[1]
: _uiclData->subButtons[5];
}
const UICL::ThreeRectWidget &CellPhonePopup::scrollDownButton() const {
- return _screenState == kDirectory
+ return (_screenState == kDirectory || isHelpContentView())
? _uiclData->subButtons[2]
: _uiclData->subButtons[6];
}
@@ -1109,6 +1114,45 @@ void CellPhonePopup::appendDigit(byte slotIndex) {
}
_dialedNumber += (char)('0' + slotIndex);
enterScreenState(kDialing);
+
+ // Auto-dial without a Talk press: a leading '1' is a full 11-digit number;
+ // anything else is a 7-digit local number that dials as soon as it matches
+ // a contact (or reaches 7 digits, ringing through to "We're sorry").
+ if (_noSignal) {
+ return;
+ }
+ const bool longDistance = (_dialedNumber[0] == '1');
+ if (longDistance) {
+ if (_dialedNumber.size() >= 11) {
+ enterScreenState(kPlaceCall);
+ }
+ } else if (findContactByDialBuffer() != -1 || _dialedNumber.size() >= 7) {
+ enterScreenState(kPlaceCall);
+ }
+}
+
+void CellPhonePopup::playDialPadSound(const Common::String &name) {
+ if (name.empty() || name.equalsIgnoreCase("NO SOUND")) {
+ return;
+ }
+ // Dial-pad tones are raw sound filenames, so play them through the phone's
+ // call-sound channel (a single, non-looping cue) instead of the common
+ // sound table, which only holds boot-registered sounds.
+ SoundDescription sound = _uiclData->callSoundTemplate;
+ sound.name = name;
+ sound.numLoops = 1;
+ g_nancy->_sound->loadSound(sound);
+ g_nancy->_sound->playSound(sound);
+}
+
+void CellPhonePopup::playButtonClickSound(const UIButtonRecord &button) {
+ SoundDescription sound = button.clickSound;
+ if (sound.name.empty() || sound.name.equalsIgnoreCase("NO SOUND"))
+ return;
+
+ sound.numLoops = 1;
+ g_nancy->_sound->loadSound(sound);
+ g_nancy->_sound->playSound(sound);
}
bool CellPhonePopup::playSoundIfPresent(const Common::Path &soundName) {
@@ -1526,6 +1570,7 @@ void CellPhonePopup::handleInput(NancyInput &input) {
if (overClose) {
g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
if (input.input & NancyInput::kLeftMouseButtonUp) {
+ playButtonClickSound(closeBtn);
input.eatMouseInput();
close();
return;
@@ -1541,7 +1586,9 @@ void CellPhonePopup::handleInput(NancyInput &input) {
const Common::Point chunkMouse = mouseToChunkCoords(input.mousePos);
- // Light the up/down arrows on hover in any state that uses them.
+ // Light the up/down arrows on hover in any state that uses them (directory,
+ // link lists, and the content view â help included, which scrolls via the
+ // small-LCD arrow pair).
const bool arrowsActive = _screenState == kDirectory || isLinkListMode() ||
_screenState == kContentView;
const bool overUp = arrowsActive &&
@@ -1782,6 +1829,8 @@ void CellPhonePopup::handleInput(NancyInput &input) {
}
}
+ // scrollUpButton()/scrollDownButton() return the right pair for help
+ // (subButtons[1]/[2]) or the zoomed articles ([5]/[6]).
const Common::Rect &upDst = scrollUpButton().destRect;
const Common::Rect &downDst = scrollDownButton().destRect;
@@ -1804,15 +1853,16 @@ void CellPhonePopup::handleInput(NancyInput &input) {
return;
}
}
+ const bool overUpDown =
+ upDst.contains(chunkMouse) || downDst.contains(chunkMouse);
- // Help draws a real Back button (subButtons[0]); hit-test it so the
- // visible button and the hotspot line up. Browser / email articles keep
- // using the wider ribbon-area hotspot.
- const Common::Rect backHit = isHelpContentView() ? backButtonHitRect() : backLabelHitRect();
+ // Help draws its Back button in the lower ribbon (subButtons[0]); the
+ // zoomed email / browser view draws it at the bottom of the screen
+ // (subButtons[7]). Hit-test the matching button so it lines up with the
+ // visible sprite.
+ const Common::Rect backHit = backButtonHitRect(isHelpContentView() ? 0 : 7);
const Common::Point popupMouse(chunkMouse.x - _screenPosition.left,
chunkMouse.y - _screenPosition.top);
- const bool overUpDown =
- upDst.contains(chunkMouse) || downDst.contains(chunkMouse);
if (!overUpDown && !backHit.isEmpty() && backHit.contains(popupMouse)) {
g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
if (input.input & NancyInput::kLeftMouseButtonUp) {
@@ -1831,6 +1881,7 @@ void CellPhonePopup::handleInput(NancyInput &input) {
g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
if (input.input & NancyInput::kLeftMouseButtonUp) {
+ playDialPadSound(_uiclData->dialPadSlots[12].soundName);
if (!_noSignal) {
if (_screenState == kDirectory) {
const int contactIdx =
@@ -1872,9 +1923,7 @@ void CellPhonePopup::handleInput(NancyInput &input) {
if (input.input & NancyInput::kLeftMouseButtonUp) {
const UICL::DialPadSlot &slot = _uiclData->dialPadSlots[newHovered];
- if (!slot.soundName.empty()) {
- g_nancy->_sound->playSound(slot.soundName);
- }
+ playDialPadSound(slot.soundName);
if (newHovered < 10) {
if (_screenState == kDirectory || isLinkListMode()) {
diff --git a/engines/nancy/ui/cellphonepopup.h b/engines/nancy/ui/cellphonepopup.h
index 2e3506027e1..92a7963ed9f 100644
--- a/engines/nancy/ui/cellphonepopup.h
+++ b/engines/nancy/ui/cellphonepopup.h
@@ -110,7 +110,10 @@ private:
void drawDirectoryList();
void drawDirectoryArrows();
void drawWelcomeScreen();
- void drawBackLabel();
+ // Blit a sub-button's idle sprite at its chunk dest (used for the visible
+ // Back buttons: subButtons[0] on the help page, subButtons[7] in the
+ // zoomed email / browser content view).
+ void drawBackButton(uint subButtonIndex);
// Generic list renderer used by web / email modes.
void drawLinkList();
@@ -157,6 +160,13 @@ private:
void resetDialPad();
void enterScreenState(ScreenState newState);
void appendDigit(byte slotIndex);
+ // Play a dial-pad key's DTMF tone. The name is a raw sound filename, so it
+ // is played through the phone's call-sound channel rather than the common
+ // (boot-registered) sound table.
+ void playDialPadSound(const Common::String &name);
+ // Play a popup button's click sound (the close X), like the inventory
+ // popup. Falls back to the shared button-click slot in the popup header.
+ void playButtonClickSound(const UIButtonRecord &button);
bool playSoundIfPresent(const Common::Path &soundName);
bool callSoundIsStillPlaying() const;
void triggerContactCallSceneChange(uint contactIndex);
@@ -193,8 +203,8 @@ private:
bool isContactVisible(const UICL::Contact &c) const;
// Popup-local rect of the Back hotspot in directory mode.
Common::Rect backLabelHitRect() const;
- // Popup-local rect of the visible Back button (subButtons[0]).
- Common::Rect backButtonHitRect() const;
+ // Popup-local rect of a visible Back sub-button (subButtons[index]).
+ Common::Rect backButtonHitRect(uint subButtonIndex) const;
// Move the directory selection by delta, scrolling as needed.
void moveDirectorySelection(int delta);
Commit: e71d9b4bf5f6d32c672cb7e91cd09739bb31c172
https://github.com/scummvm/scummvm/commit/e71d9b4bf5f6d32c672cb7e91cd09739bb31c172
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-06T01:15:45+03:00
Commit Message:
NANCY: NANCY10: Disable taskbar buttons when a popup is open
Fix #16910
Changed paths:
engines/nancy/state/scene.cpp
engines/nancy/ui/taskbar.cpp
engines/nancy/ui/taskbar.h
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index fd29e0d44f9..a361dec0cb9 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -1420,7 +1420,18 @@ void Scene::handleInput() {
// skipped while the textbox is in open mode (it visually covers the
// buttons, so they should not receive hover/clicks).
if (!_activeMovie) {
- if (_taskbar && !_textbox.isFullMode()) {
+ // While a Nancy 10+ popup (inventory / notebook / cellphone /
+ // conversation) is open, the original disables the entire taskbar â
+ // every button, including MENU and HELP. Skip the taskbar input so it
+ // neither hovers nor reacts to clicks until the popup is closed.
+ const bool popupOpen = g_nancy->getGameType() >= kGameTypeNancy10 &&
+ !activePopupConfinement().isEmpty();
+ if (_taskbar) {
+ // Grey out the whole taskbar while a popup is open (matches the
+ // original); restored automatically once the popup closes.
+ _taskbar->setPopupLockout(popupOpen);
+ }
+ if (_taskbar && !_textbox.isFullMode() && !popupOpen) {
// MENU and HELP leave gameplay entirely, which would cut off the
// taskbar click sound. The original defers the transition until that
// sound finishes, so we hold the click here and only switch state
diff --git a/engines/nancy/ui/taskbar.cpp b/engines/nancy/ui/taskbar.cpp
index 7cb2fce445b..bdca7818376 100644
--- a/engines/nancy/ui/taskbar.cpp
+++ b/engines/nancy/ui/taskbar.cpp
@@ -128,6 +128,10 @@ Taskbar::ButtonState Taskbar::restingState(uint index) const {
if (index >= TASK::kNumButtons) {
return kButtonIdle;
}
+ // While a popup is open every button is disabled.
+ if (_popupLockout) {
+ return kButtonDisabled;
+ }
if (!_enabled[index]) {
return kButtonDisabled;
}
@@ -203,6 +207,26 @@ void Taskbar::toggleButton(uint index, bool enabled) {
}
}
+void Taskbar::setPopupLockout(bool locked) {
+ if (_popupLockout == locked) {
+ return;
+ }
+ _popupLockout = locked;
+
+ // Repaint every button in its new resting state (all disabled while locked,
+ // back to idle/badge when the popup closes) and drop any lingering hover.
+ _hoveredButton = -1;
+ auto *taskData = GetEngineData(TASK);
+ if (!taskData) {
+ return;
+ }
+ for (uint i = 0; i < TASK::kNumButtons; ++i) {
+ if (isButtonSlotUsed(taskData->buttons[i])) {
+ drawButton(i, restingState(i));
+ }
+ }
+}
+
void Taskbar::setNotification(uint buttonIndex, uint subCategory) {
if (buttonIndex >= TASK::kNumButtons || subCategory >= kNumNotificationSubCategories) {
return;
diff --git a/engines/nancy/ui/taskbar.h b/engines/nancy/ui/taskbar.h
index 2ea0310e5bc..8536d3e90a5 100644
--- a/engines/nancy/ui/taskbar.h
+++ b/engines/nancy/ui/taskbar.h
@@ -76,6 +76,10 @@ public:
// if none. Cleared on the next call to handleInput().
int getClickedButton() const { return _clickedButton; }
+ // Grey out and disable every taskbar button while a popup (inventory /
+ // notebook / cellphone / conversation) is open, matching the original.
+ void setPopupLockout(bool locked);
+
private:
enum ButtonState {
kButtonIdle = 0,
@@ -138,6 +142,8 @@ private:
ButtonState _buttonStates[6];
ButtonOverride _overrides[6];
bool _notifications[6][kNumNotificationSubCategories];
+ // True while a popup is open: every button renders disabled and ignores input.
+ bool _popupLockout = false;
};
} // End of namespace UI
Commit: 11efa8821fdddd15156dd6a77ef371647b5c3385
https://github.com/scummvm/scummvm/commit/11efa8821fdddd15156dd6a77ef371647b5c3385
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-06T01:15:46+03:00
Commit Message:
NANCY: NANCY10: Cleanup comments
Changed paths:
engines/nancy/enginedata.h
engines/nancy/ui/cellphonepopup.cpp
engines/nancy/ui/inventorypopup.h
diff --git a/engines/nancy/enginedata.h b/engines/nancy/enginedata.h
index 0f07136cee9..40d56017f4c 100644
--- a/engines/nancy/enginedata.h
+++ b/engines/nancy/enginedata.h
@@ -686,9 +686,9 @@ struct UICL : public EngineData {
uint16 fontId1 = 0;
uint16 fontId2 = 0;
- Common::Path outgoingRingSound; // Process case 2 (post-dial ring)
- Common::Path pickupSound; // Process cases 0/4 (call connect)
- Common::Path invalidNumberSound; // Process case 7 (try again)
+ Common::Path outgoingRingSound; // post-dial ring
+ Common::Path pickupSound; // call connect
+ Common::Path invalidNumberSound; // try again
uint16 contactCount = 0;
Common::Array<Contact> contacts;
diff --git a/engines/nancy/ui/cellphonepopup.cpp b/engines/nancy/ui/cellphonepopup.cpp
index 1a421580b4e..d03bdd5e232 100644
--- a/engines/nancy/ui/cellphonepopup.cpp
+++ b/engines/nancy/ui/cellphonepopup.cpp
@@ -1004,9 +1004,8 @@ void CellPhonePopup::drawWelcomeScreen() {
}
void CellPhonePopup::drawBackButton(uint subButtonIndex) {
- // subButtons[0] (original CUIButton 0x10) is the Back button that returns a
- // sub-screen to the main view; subButtons[7] (0x17) is the equivalent Back
- // button at the bottom of the zoomed email / browser content view.
+ // subButtons[0] is the Back button in the lower ribbon (help / sub-screens);
+ // subButtons[7] is the Back button at the bottom of the zoomed content view.
const UICL::ThreeRectWidget &back = _uiclData->subButtons[subButtonIndex];
if (back.srcRectIdle.isEmpty() || back.destRect.isEmpty()) {
return;
diff --git a/engines/nancy/ui/inventorypopup.h b/engines/nancy/ui/inventorypopup.h
index e18896e7a5e..7162e96e628 100644
--- a/engines/nancy/ui/inventorypopup.h
+++ b/engines/nancy/ui/inventorypopup.h
@@ -87,9 +87,8 @@ private:
void setActiveFilterIndex(uint index);
// Play a popup button's click sound (the filter tabs and the close X),
- // mirroring the original's per-CUIButton click cue. Falls back to the
- // shared button-click slot in the popup header when the button carries no
- // sound of its own.
+ // falling back to the shared button-click slot in the popup header when the
+ // button carries no sound of its own.
void playButtonClickSound(const UIButtonRecord &button);
// Apply the current scrollbar position to the page index, clamping
Commit: a46f421625b28d4877a3f35e02b394ed1d321bad
https://github.com/scummvm/scummvm/commit/a46f421625b28d4877a3f35e02b394ed1d321bad
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-06T01:15:47+03:00
Commit Message:
NANCY: NANCY10: Notebook fixes
- Add missing journal and tasklist header - fix #16907
- Correct text layout - fix #16909
- Implement clicking on tasklist checkboxes (the ones that are done and
can be checked), and the ones that aren't done yet - fix #16911
Changed paths:
engines/nancy/misc/hypertext.cpp
engines/nancy/misc/hypertext.h
engines/nancy/ui/notebookpopup.cpp
engines/nancy/ui/notebookpopup.h
diff --git a/engines/nancy/misc/hypertext.cpp b/engines/nancy/misc/hypertext.cpp
index 56f7073eea1..6e737641559 100644
--- a/engines/nancy/misc/hypertext.cpp
+++ b/engines/nancy/misc/hypertext.cpp
@@ -76,6 +76,10 @@ void HypertextParser::drawAllText(const Common::Rect &textBounds, uint leftOffse
_numDrawnLines = 0;
+ if (_recordMarkHotspots) {
+ _markHotspots.clear();
+ }
+
if (!_imageName.empty()) {
g_nancy->_resource->loadImage(_imageName, image);
}
@@ -351,6 +355,10 @@ void HypertextParser::drawAllText(const Common::Rect &textBounds, uint leftOffse
// For now we do not check if we need to go to new line; neither does the original
_fullSurface.blitFrom(g_nancy->_graphics->_object0, markSrc, markDest);
+ if (_recordMarkHotspots) {
+ _markHotspots.push_back(markDest);
+ }
+
horizontalOffset += markDest.width() + 2;
break;
}
diff --git a/engines/nancy/misc/hypertext.h b/engines/nancy/misc/hypertext.h
index 3e683ea8560..c7565dd6c45 100644
--- a/engines/nancy/misc/hypertext.h
+++ b/engines/nancy/misc/hypertext.h
@@ -64,6 +64,12 @@ protected:
Common::Array<Common::String> _textLines;
Common::Array<Common::Rect> _hotspots;
+ // When set, drawAllText records the dest rect of every drawn mark glyph
+ // (in _fullSurface coords, in draw order). The notebook uses this to hit-
+ // test the tasklist's clickable checkboxes.
+ bool _recordMarkHotspots = false;
+ Common::Array<Common::Rect> _markHotspots;
+
// Data for displaying images inside text; used in Hypertext
Common::Path _imageName;
Common::Array<uint16> _imageLineIDs;
diff --git a/engines/nancy/ui/notebookpopup.cpp b/engines/nancy/ui/notebookpopup.cpp
index 7895b960474..167b54530fb 100644
--- a/engines/nancy/ui/notebookpopup.cpp
+++ b/engines/nancy/ui/notebookpopup.cpp
@@ -19,6 +19,9 @@
*
*/
+#include "common/random.h"
+#include "common/system.h"
+
#include "engines/nancy/cursor.h"
#include "engines/nancy/font.h"
#include "engines/nancy/graphics.h"
@@ -93,6 +96,7 @@ void NotebookPopup::init() {
drawBackground();
drawTabs();
+ drawCaption();
drawContent();
drawForeground();
@@ -106,6 +110,14 @@ void NotebookPopup::registerGraphics() {
RenderObject::registerGraphics();
}
+void NotebookPopup::updateGraphics() {
+ // Fire the deferred "I'm finished with that" line once its delay elapses.
+ if (_completeVoiceTime != 0 && g_system->getMillis() >= _completeVoiceTime) {
+ _completeVoiceTime = 0;
+ playCheckboxSound(true);
+ }
+}
+
void NotebookPopup::open() {
if (_isVisible)
return;
@@ -219,6 +231,21 @@ void NotebookPopup::drawTabs() {
_needsRedraw = true;
}
+void NotebookPopup::drawCaption() {
+ if ((uint)_activeTab >= _uinbData->tabCaptionSrcRects.size()) {
+ return;
+ }
+ const Common::Rect &spr = _uinbData->tabCaptionSrcRects[_activeTab];
+ if (spr.isEmpty() || _uinbData->tabCaptionDestRect.isEmpty()) {
+ return;
+ }
+
+ // Use the same game-frame-aware conversion as the tabs / close button so
+ // the caption lines up with them when the popup overlays the game frame.
+ const Common::Rect dstLocal = toPopupLocal(_uinbData->tabCaptionDestRect, false);
+ _drawSurface.blitFrom(_overlayImage, spr, Common::Point(dstLocal.left, dstLocal.top));
+}
+
void NotebookPopup::drawTab(uint index, bool drawHover) {
const UIButtonSlot &tab = _uinbData->tabs[index];
if (!tab.enabled)
@@ -310,6 +337,7 @@ void NotebookPopup::handleInput(NancyInput &input) {
if (overClose) {
g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
if (input.input & NancyInput::kLeftMouseButtonUp) {
+ playButtonClickSound(closeBtn);
input.eatMouseInput();
close();
return;
@@ -317,6 +345,21 @@ void NotebookPopup::handleInput(NancyInput &input) {
}
}
+ // Tasklist checkboxes: an unchecked box gets a hotspot cursor and, on
+ // click, either checks off (event flag satisfied) or plays the rejection
+ // line. Checked before the tabs since the boxes sit inside the text area.
+ for (uint k = 0; k < _checkboxRects.size(); ++k) {
+ if (!_checkboxRects[k].contains(localMouse)) {
+ continue;
+ }
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
+ if (input.input & NancyInput::kLeftMouseButtonUp) {
+ toggleCheckbox(_checkboxEntryIndices[k]);
+ input.eatMouseInput();
+ }
+ return;
+ }
+
// Tab hover + click. Mirrors the inventory popup's filter-tab
// handling so the same enter/exit redraw semantics apply: track
// whether any tab is hovered, restore non-hovered tabs to their
@@ -361,12 +404,7 @@ void NotebookPopup::handleInput(NancyInput &input) {
_scrollPos = 0.0f;
_scrollbarDragging = false;
- // Play the page-flip sound (first slot of either
- // actionable or no-action set; both have 3 alternates).
- const Common::Path &soundName = _uinbData->noActionClickSounds[0];
- if (!soundName.empty()) {
- g_nancy->_sound->playSound(soundName.toString());
- }
+ playButtonClickSound(tab.button);
refreshContent();
}
@@ -389,6 +427,7 @@ void NotebookPopup::refreshContent() {
// always sit visually above the text layer.
drawBackground();
drawTabs();
+ drawCaption();
drawContent();
drawForeground();
}
@@ -442,12 +481,14 @@ void NotebookPopup::buildTextLines() {
}
if (markValue >= 1 && markValue <= 5) {
body = Common::String::format("<%u>", markValue) + body;
+ // Record which entry this mark belongs to so the recorded
+ // mark hotspots (in the same draw order) map back to entries.
+ _markEntryIndices.push_back((uint)i);
}
}
- if (i > 0) {
- body += "<n>";
- }
+ // Entries concatenate directly; each entry's text already ends with its
+ // own newline, so an extra <n> here would double the inter-entry gap.
combined += body;
}
@@ -461,13 +502,21 @@ void NotebookPopup::drawContent() {
return;
}
- // textRect from UINB is in chunk coords (relative to normalDestRect);
- // convert to popup-local for the on-surface blit destination.
- Common::Rect localTextRect = _uinbData->textRect;
- localTextRect.translate(-_uinbData->header.normalDestRect.left,
- -_uinbData->header.normalDestRect.top);
+ // Convert the text rect to popup-local with the same game-frame-aware
+ // conversion the tabs / close button use, so text and caption stay aligned
+ // with them when the popup overlays the game frame.
+ const Common::Rect localTextRect = toPopupLocal(_uinbData->textRect, false);
HypertextParser::clear();
+ _checkboxRects.clear();
+ _checkboxEntryIndices.clear();
+ _markEntryIndices.clear();
+
+ // Only the Tasklist has clickable checkboxes; record their glyph rects.
+ const UIButtonSlot &activeTab = _uinbData->tabs[_activeTab];
+ const bool tasksTab = activeTab.enabled && activeTab.id != 1;
+ _recordMarkHotspots = tasksTab;
+
buildTextLines();
// Chunk's textRect already provides top padding from the chrome.
@@ -495,8 +544,109 @@ void NotebookPopup::drawContent() {
_drawSurface.blitFrom(_fullSurface, srcSlice,
Common::Point(localTextRect.left, localTextRect.top));
+ if (tasksTab) {
+ buildCheckboxRects(localTextRect, scrollY, visibleH);
+ }
+
_needsRedraw = true;
}
+void NotebookPopup::buildCheckboxRects(const Common::Rect &localTextRect, int scrollY, int visibleH) {
+ JournalData *journalData = (JournalData *)NancySceneState.getPuzzleData(JournalData::getTag());
+ if (!journalData || !journalData->journalEntries.contains(kNotebookTabTasks)) {
+ return;
+ }
+ const Common::Array<JournalData::Entry> &entries = journalData->journalEntries[kNotebookTabTasks];
+
+ const Common::Rect visibleWindow(localTextRect.left, localTextRect.top,
+ localTextRect.left + localTextRect.width(),
+ localTextRect.top + visibleH);
+
+ const uint count = MIN(_markHotspots.size(), _markEntryIndices.size());
+ for (uint k = 0; k < count; ++k) {
+ const uint entryIndex = _markEntryIndices[k];
+ // Only unchecked boxes (mark 7) are clickable.
+ if (entryIndex >= entries.size() || entries[entryIndex].mark != 7) {
+ continue;
+ }
+
+ // Mark rects are in _fullSurface coords; map to popup-local (offset by
+ // the text rect, minus the scroll) and widen the hit area to the right.
+ Common::Rect box = _markHotspots[k];
+ box.translate(localTextRect.left, localTextRect.top - scrollY);
+ box.right += 20;
+
+ const Common::Rect clipped = box.findIntersectingRect(visibleWindow);
+ if (clipped.isEmpty()) {
+ continue;
+ }
+ _checkboxRects.push_back(clipped);
+ _checkboxEntryIndices.push_back(entryIndex);
+ }
+}
+
+void NotebookPopup::toggleCheckbox(uint entryIndex) {
+ JournalData *journalData = (JournalData *)NancySceneState.getPuzzleData(JournalData::getTag());
+ if (!journalData || !journalData->journalEntries.contains(kNotebookTabTasks)) {
+ return;
+ }
+ Common::Array<JournalData::Entry> &entries = journalData->journalEntries[kNotebookTabTasks];
+ if (entryIndex >= entries.size() || entries[entryIndex].mark != 7) {
+ return;
+ }
+
+ // For a clickable task, sceneID doubles as the completion event-flag index
+ // (-1 = no requirement). The box can be checked off only once that flag is
+ // set; otherwise Nancy says she isn't finished yet.
+ const int16 flag = (int16)entries[entryIndex].sceneID;
+ const bool canComplete = (flag == -1) || NancySceneState.getEventFlag(flag, g_nancy->_true);
+ if (canComplete) {
+ entries[entryIndex].mark = 8;
+ refreshContent();
+ // A check-off plays an immediate click, then Nancy's spoken line a beat
+ // later (deferred so the two cues don't step on each other).
+ playButtonClickSound(_uinbData->header.secondaryButton);
+ _completeVoiceTime = g_system->getMillis() + 400;
+ } else {
+ playCheckboxSound(false);
+ }
+}
+
+void NotebookPopup::playButtonClickSound(const UIButtonRecord &button) {
+ SoundDescription sound = button.clickSound;
+ if (sound.name.empty() || sound.name.equalsIgnoreCase("NO SOUND")) {
+ // Fall back to the header's shared button-click slot (2; 0/1 = open/close).
+ sound = _uinbData->header.sounds[2];
+ }
+ if (sound.name.empty() || sound.name.equalsIgnoreCase("NO SOUND")) {
+ return;
+ }
+ g_nancy->_sound->loadSound(sound);
+ g_nancy->_sound->playSound(sound);
+}
+
+void NotebookPopup::playCheckboxSound(bool actionable) {
+ const Common::Path *set = actionable ? _uinbData->actionableClickSounds
+ : _uinbData->noActionClickSounds;
+
+ // Pick a random variant, falling back to any valid one.
+ const uint start = g_nancy->_randomSource->getRandomNumber(UINB::kNumPageSoundsPerSet - 1);
+ for (uint n = 0; n < UINB::kNumPageSoundsPerSet; ++n) {
+ const Common::String name = set[(start + n) % UINB::kNumPageSoundsPerSet].toString();
+ if (name.empty() || name.equalsIgnoreCase("NO SOUND")) {
+ continue;
+ }
+ // The names are raw filenames; play them on the checkbox sound's
+ // channel / volume, taken from the close button's click sound (the
+ // header sound slots are all "NO SOUND" for the notebook).
+ SoundDescription sound = _uinbData->header.secondaryButton.clickSound;
+ sound.name = name;
+ sound.numLoops = 1;
+ g_nancy->_sound->loadSound(sound);
+ g_nancy->_sound->playSound(sound);
+ return;
+ }
+}
+
} // End of namespace UI
} // End of namespace Nancy
diff --git a/engines/nancy/ui/notebookpopup.h b/engines/nancy/ui/notebookpopup.h
index e1eead411d2..78f2f1a57ae 100644
--- a/engines/nancy/ui/notebookpopup.h
+++ b/engines/nancy/ui/notebookpopup.h
@@ -42,6 +42,7 @@ public:
void init() override;
void registerGraphics() override;
+ void updateGraphics() override;
void handleInput(NancyInput &input);
void open();
@@ -66,6 +67,9 @@ private:
void drawBackground();
void drawTabs();
void drawTab(uint index, bool drawHover = false);
+ // Blit the active tab's title image ("CASE JOURNAL" / "TASKS") into the
+ // header strip above the text area.
+ void drawCaption();
void drawContent();
// Paint foreground widgets (close button, scrollbar) on top of the
// already-drawn background + content layers.
@@ -73,6 +77,10 @@ private:
void drawCloseButton(bool hovered);
void drawScrollbar(UIButtonState state);
+ // Play a popup button's click sound (close X and the tab buttons), like the
+ // inventory popup. Falls back to the shared button-click slot in the header.
+ void playButtonClickSound(const UIButtonRecord &button);
+
// Returns the on-popup-surface bounding rect of the slider thumb at
// the current scroll position (in popup-local coords).
Common::Rect computeThumbRect() const;
@@ -85,6 +93,16 @@ private:
// entries.
void buildTextLines();
+ // Tasklist checkboxes. Rebuild the popup-local hit rects for the clickable
+ // (unchecked) checkboxes from the mark hotspots recorded during drawContent.
+ void buildCheckboxRects(const Common::Rect &localTextRect, int scrollY, int visibleH);
+ // Check off a task if its completion event flag is set, else play the
+ // "not finished yet" line.
+ void toggleCheckbox(uint entryIndex);
+ // Play a random checkbox voice line: actionable = "finished with that",
+ // !actionable = "can't check that off yet".
+ void playCheckboxSound(bool actionable);
+
const UINB *_uinbData;
Graphics::ManagedSurface _overlayImage; // popup background image
@@ -100,6 +118,18 @@ private:
bool _scrollbarHovered = false;
int _scrollbarGrabOffset = 0;
+ // Deferred "I'm finished with that" voice: checking a task off plays an
+ // immediate click, then this fires the spoken line a beat later (0 = none).
+ uint32 _completeVoiceTime = 0;
+
+ // Tasklist checkboxes: popup-local hit rects for the clickable (unchecked)
+ // boxes and the task-entry index each maps to. Rebuilt every drawContent().
+ Common::Array<Common::Rect> _checkboxRects;
+ Common::Array<uint> _checkboxEntryIndices;
+ // Task-entry index for each mark buildTextLines emits, in draw order, so
+ // the recorded mark hotspots can be mapped back to their entries.
+ Common::Array<uint> _markEntryIndices;
+
// journalEntries HashMap keys: _surfaceID = 3 holds task entries,
// _surfaceID = 4 holds journal entries.
enum NotebookTab {
More information about the Scummvm-git-logs
mailing list