[Scummvm-git-logs] scummvm master -> 9842c1e48da40ffa88f0023a58dac467c7b25253
bluegr
noreply at scummvm.org
Thu Jul 9 19:04:57 UTC 2026
This automated email contains information about 6 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
b8c688ca63 NANCY: NANCY10: Add debug info to some ARs
08f0cb5d4a NANCY: NANCY10: Inventory UI popup fixes
2cfdf36d97 NANCY: Clear held item when starting new game
da1a1172d2 NANCY: NANCY10: Fix cellphone close sound after another sound starts
c429a02d2c NANCY: NANCY10: Fix target for cellphone browser homepage back button
9842c1e48d NANCY: NANCY10: Implement text underline
Commit: b8c688ca63884f693962fa69674bc59c72a124b8
https://github.com/scummvm/scummvm/commit/b8c688ca63884f693962fa69674bc59c72a124b8
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-09T22:04:42+03:00
Commit Message:
NANCY: NANCY10: Add debug info to some ARs
Changed paths:
engines/nancy/action/miscrecords.h
diff --git a/engines/nancy/action/miscrecords.h b/engines/nancy/action/miscrecords.h
index 3ef861c8462..8a0fd3030c2 100644
--- a/engines/nancy/action/miscrecords.h
+++ b/engines/nancy/action/miscrecords.h
@@ -155,6 +155,11 @@ public:
int16 _startScene = 0; // start scene id (9999 = none); also the auto-open cell phone's call target
int16 _endScene = 0; // end scene id (9999 = none)
+ Common::String getRecordExtraInfo() const override {
+ return Common::String::format("uiButton: %d, autoOpenOrBadgeSound: %d, flagB: %d, startScene: %d, endScene: %d",
+ _uiButton, _autoOpenOrBadgeSound, _flagB, _startScene, _endScene);
+ }
+
protected:
Common::String getRecordTypeName() const override { return "ControlUIItems"; }
};
@@ -186,6 +191,11 @@ public:
int16 _flag = 0; // stored but unused by the original; reserved
int16 _eventFlag = 0; // event-flag index set when the entry is opened
+ Common::String getRecordExtraInfo() const override {
+ return Common::String::format("Key: %s, Value: %s, Mode: %d, Extra: %d, Flag: %d, EventFlag: %d",
+ _key.c_str(), _value.c_str(), _mode, _extra, _flag, _eventFlag);
+ }
+
protected:
Common::String getRecordTypeName() const override { return "AddSearchLink"; }
};
@@ -213,6 +223,10 @@ public:
UICL::Contact _contact;
+ Common::String getRecordExtraInfo() const override {
+ return Common::String::format("Contact: %s", _contact.name.c_str());
+ }
+
protected:
Common::String getRecordTypeName() const override { return "ChangeCellPhoneInfo"; }
};
Commit: 08f0cb5d4ae919bc5fbd8c8a6aa26fe064594147
https://github.com/scummvm/scummvm/commit/08f0cb5d4ae919bc5fbd8c8a6aa26fe064594147
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-09T22:04:44+03:00
Commit Message:
NANCY: NANCY10: Inventory UI popup fixes
Fix #16932
- Don't close the inventory popup when picking an item
- Drop held item when clicking, instead of picking another one
- Make it easier to drop items in the inventory popup, by increasing
its hitbox
- Most recently dropped item should be last in the list of items
Changed paths:
engines/nancy/enginedata.cpp
engines/nancy/enginedata.h
engines/nancy/state/scene.cpp
engines/nancy/ui/inventorypopup.cpp
engines/nancy/ui/inventorypopup.h
diff --git a/engines/nancy/enginedata.cpp b/engines/nancy/enginedata.cpp
index 4d98522ef2d..61137052c73 100644
--- a/engines/nancy/enginedata.cpp
+++ b/engines/nancy/enginedata.cpp
@@ -1090,7 +1090,11 @@ UIIV::UIIV(Common::SeekableReadStream *chunkStream) : EngineData(chunkStream) {
if (g_nancy->getGameType() >= kGameTypeNancy13)
readRect(*chunkStream, slotsHotspot);
- chunkStream->skip(2);
+ // Two byte flags. The first controls where items added while the popup is
+ // open land in the inventory order (see appendItemsWhileOpen); the second
+ // is unused here.
+ appendItemsWhileOpen = chunkStream->readByte();
+ chunkStream->skip(1);
for (uint i = 0; i < kNumFilters; ++i) {
readUIButtonSlot(*chunkStream, filters[i]);
diff --git a/engines/nancy/enginedata.h b/engines/nancy/enginedata.h
index 680964e8d7b..2388d5967ae 100644
--- a/engines/nancy/enginedata.h
+++ b/engines/nancy/enginedata.h
@@ -717,6 +717,10 @@ struct UIIV : public EngineData {
Common::Array<Common::Rect> slotSrcRects; // 16 entries (image coords)
Common::Array<Common::Rect> slotDestRects; // 16 entries (screen coords)
Common::Rect slotsHotspot; // Nancy13+: clickable region of the item slots
+ // When nonzero, items added while the popup is open are appended to the end
+ // of the inventory order instead of being inserted at the front (so the most
+ // recently dropped item ends up last). See Scene::addItemToInventory.
+ byte appendItemsWhileOpen = 0;
UIButtonSlot filters[kNumFilters]; // 6 entries
Common::Array<Common::Rect> tabCaptionSrcRects; // 6 entries
Common::Rect tabCaptionDestRect; // on-screen target
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index eb3b4c59d6a..ca313397375 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -398,6 +398,30 @@ void Scene::addItemToInventory(int16 id) {
if (g_nancy->getGameType() <= kGameTypeNancy9) {
_inventoryBox.addItem(id);
} else {
+ // Nancy 10+ has no always-visible inventory box; the popup renders
+ // from the shared, save-persisted order list instead. Items are
+ // inserted at the front, except that when the UIIV chunk opts in
+ // (appendItemsWhileOpen) an item added while the popup is open goes
+ // to the end. That's how the most recently dropped item ends up last.
+ bool addToBack = false;
+ if (_inventoryPopup.isOpen()) {
+ const UIIV *uiivData = GetEngineData(UIIV);
+ addToBack = uiivData && uiivData->appendItemsWhileOpen;
+ }
+
+ Common::Array<int16> &order = _inventoryBox.getOrder();
+ for (uint i = 0; i < order.size(); ++i) {
+ if (order[i] == id) {
+ order.remove_at(i);
+ break;
+ }
+ }
+ if (addToBack) {
+ order.push_back(id);
+ } else {
+ order.insert_at(0, id);
+ }
+
if (_inventoryPopup.isOpen()) {
_inventoryPopup.refreshGrid();
} else if (_taskbar) {
@@ -425,8 +449,18 @@ void Scene::removeItemFromInventory(int16 id, bool pickUp) {
if (g_nancy->getGameType() <= kGameTypeNancy9) {
_inventoryBox.removeItem(id);
- } else if (_inventoryPopup.isOpen()) {
- _inventoryPopup.refreshGrid();
+ } else {
+ Common::Array<int16> &order = _inventoryBox.getOrder();
+ for (uint i = 0; i < order.size(); ++i) {
+ if (order[i] == id) {
+ order.remove_at(i);
+ break;
+ }
+ }
+
+ if (_inventoryPopup.isOpen()) {
+ _inventoryPopup.refreshGrid();
+ }
}
}
}
diff --git a/engines/nancy/ui/inventorypopup.cpp b/engines/nancy/ui/inventorypopup.cpp
index 6a6a066260a..b5568d8834f 100644
--- a/engines/nancy/ui/inventorypopup.cpp
+++ b/engines/nancy/ui/inventorypopup.cpp
@@ -87,6 +87,15 @@ void InventoryPopup::open() {
setVisible(true);
+ // If the player opens the popup while carrying an item, move the cursor
+ // into the toolbox so the whole popup is immediately usable as a drop
+ // zone (e.g. double-clicking the taskbar icon opens the popup and lands
+ // the cursor over it, ready to drop on the next click).
+ if (NancySceneState.getHeldItem() != -1) {
+ g_nancy->_cursor->warpCursor(Common::Point(_screenPosition.left + _screenPosition.width() / 2,
+ _screenPosition.top + _screenPosition.height() / 2));
+ }
+
NancySceneState.getTaskbar()->clearAllNotifications(kTaskButtonInventory);
if (!_uiivData->header.sounds[0].name.empty()) {
@@ -124,39 +133,93 @@ void InventoryPopup::refreshGrid() {
_needsRedraw = true;
}
+bool InventoryPopup::itemMatchesFilter(int16 itemID) const {
+ const INV::ItemDescription &desc = _invData->itemDescriptions[itemID];
+
+ switch (_activeFilterIndex) {
+ case kFilterViewable:
+ return desc.keepItem == 3;
+ case kFilterPortable:
+ return desc.keepItem <= 2;
+ case kFilterAll:
+ default:
+ return true;
+ }
+}
+
+bool InventoryPopup::syncOrderWithInventory() {
+ const uint16 numItems = MIN<uint16>(g_nancy->getStaticData().numItems,
+ _invData->itemDescriptions.size());
+ const int16 heldItem = NancySceneState.getHeldItem();
+ Common::Array<int16> &order = NancySceneState.getInventoryBox().getOrder();
+
+ bool changed = false;
+
+ // Drop entries that are out of range, not owned, or being carried
+ // (`hasItem` reports the held item as owned, so exclude it explicitly).
+ for (uint i = 0; i < order.size();) {
+ const int16 id = order[i];
+ if (id < 0 || id >= (int16)numItems || id == heldItem ||
+ NancySceneState.hasItem(id) != g_nancy->_true) {
+ order.remove_at(i);
+ changed = true;
+ } else {
+ ++i;
+ }
+ }
+
+ // Append any owned items missing from the list, in item-ID order.
+ for (uint16 id = 0; id < numItems; ++id) {
+ if ((int16)id == heldItem || NancySceneState.hasItem(id) != g_nancy->_true)
+ continue;
+
+ bool inOrder = false;
+ for (uint i = 0; i < order.size(); ++i) {
+ if (order[i] == (int16)id) {
+ inOrder = true;
+ break;
+ }
+ }
+ if (!inOrder) {
+ order.push_back(id);
+ changed = true;
+ }
+ }
+
+ return changed;
+}
+
void InventoryPopup::rebuildVisibleList() {
_visibleItems.clear();
+ // Make sure the persisted order list holds exactly the items the player
+ // owns before reading from it. This heals older saves (or any desync) in
+ // one pass, instead of waiting for the player to pick up and drop each
+ // item individually.
+ syncOrderWithInventory();
+
const uint16 numItems = MIN<uint16>(g_nancy->getStaticData().numItems,
_invData->itemDescriptions.size());
const int16 heldItem = NancySceneState.getHeldItem();
- for (uint16 id = 0; id < numItems; ++id) {
- if (NancySceneState.hasItem(id) != g_nancy->_true)
+ // The grid follows the shared, save-persisted inventory order (see
+ // Scene::addItemToInventory), so the sequence in which items are picked
+ // up and dropped is preserved rather than always sorting by item ID.
+ const Common::Array<int16> &order = NancySceneState.getInventoryBox().getOrder();
+
+ for (uint i = 0; i < order.size(); ++i) {
+ const int16 id = order[i];
+ if (id < 0 || id >= (int16)numItems)
continue;
// `hasItem` reports the held item as owned; the player is already
// carrying it, so don't list it in the grid too.
- if ((int16)id == heldItem)
+ if (id == heldItem || NancySceneState.hasItem(id) != g_nancy->_true)
continue;
- const INV::ItemDescription &desc = _invData->itemDescriptions[id];
-
- switch (_activeFilterIndex) {
- case kFilterViewable:
- if (desc.keepItem == 3)
- _visibleItems.push_back(id);
- break;
- case kFilterPortable:
- if (desc.keepItem <= 2)
- _visibleItems.push_back(id);
- break;
- case kFilterAll:
- default:
+ if (itemMatchesFilter(id))
_visibleItems.push_back(id);
- break;
- }
}
}
@@ -396,18 +459,36 @@ void InventoryPopup::handleInput(NancyInput &input) {
}
}
- // If the player is already holding an item, any click on the slot grid
- // puts it back into the inventory. Otherwise a click on an occupied
- // slot picks that item up (or navigates to its close-up scene).
const int16 heldItem = NancySceneState.getHeldItem();
+ // While the player is carrying an item, the whole popup acts as a single
+ // drop zone: a click anywhere inside it drops the held item back into the
+ // inventory (appended to the end of the list) and keeps the popup open, so
+ // the player never has to carefully target one of the slot squares. The
+ // close button, handled above, still closes without dropping.
+ if (heldItem != -1) {
+ if (_screenPosition.contains(input.mousePos)) {
+ // kHotspot keeps the carried-item sprite showing on Nancy 10+.
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspot);
+
+ if (input.input & NancyInput::kLeftMouseButtonUp) {
+ NancySceneState.addItemToInventory(heldItem);
+ }
+ input.eatMouseInput();
+ }
+ return;
+ }
+
+ // Empty-handed: a click on an occupied slot picks that item up (or
+ // navigates to its close-up scene). Hovering an item uses the yellow
+ // hotspot arrow rather than the magnifier.
int hoveredSlot = -1;
for (uint i = 0; i < kSlotsPerPage; ++i) {
if (i >= _uiivData->slotDestRects.size())
break;
if (!_uiivData->slotDestRects[i].contains(chunkMouse))
continue;
- if (heldItem == -1 && _slotItemIDs[i] < 0)
+ if (_slotItemIDs[i] < 0)
continue;
hoveredSlot = (int)i;
break;
@@ -417,51 +498,34 @@ void InventoryPopup::handleInput(NancyInput &input) {
g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
if (input.input & NancyInput::kLeftMouseButtonUp) {
- if (heldItem != -1) {
- const int16 slotItem = _slotItemIDs[hoveredSlot];
+ const int16 itemID = _slotItemIDs[hoveredSlot];
+ const INV::ItemDescription &item = _invData->itemDescriptions[itemID];
+ const byte disabled = NancySceneState.getItemDisabledState(itemID);
- // Empty slot: drop the held item back into the inventory
- // and keep the popup open. Occupied slot: swap - held
- // item goes into the inventory, the clicked item becomes
- // the new held item.
- NancySceneState.addItemToInventory(heldItem);
- if (slotItem >= 0 && slotItem != heldItem) {
- NancySceneState.removeItemFromInventory(slotItem, true);
+ if (disabled) {
+ if (disabled == 2) {
+ NancySceneState.playItemCantSound(itemID);
}
- refreshGrid();
input.eatMouseInput();
return;
}
- const int16 itemID = _slotItemIDs[hoveredSlot];
- if (itemID >= 0) {
- const INV::ItemDescription &item = _invData->itemDescriptions[itemID];
- const byte disabled = NancySceneState.getItemDisabledState(itemID);
-
- if (disabled) {
- if (disabled == 2) {
- NancySceneState.playItemCantSound(itemID);
- }
- input.eatMouseInput();
- return;
- }
-
- const bool pickUp = item.keepItem != kInvItemNewSceneView;
- NancySceneState.removeItemFromInventory(itemID, pickUp);
-
- if (item.keepItem == kInvItemNewSceneView) {
- // Close-up view: stash the item and warp to its scene.
- NancySceneState.pushScene(itemID);
- SceneChangeDescription sceneChange;
- sceneChange.sceneID = item.sceneID;
- sceneChange.continueSceneSound = item.sceneSoundFlag;
- NancySceneState.changeScene(sceneChange);
- }
-
+ const bool pickUp = item.keepItem != kInvItemNewSceneView;
+ NancySceneState.removeItemFromInventory(itemID, pickUp);
+
+ if (item.keepItem == kInvItemNewSceneView) {
+ // Close-up view: stash the item and warp to its scene, which
+ // dismisses the popup. A normal pickup keeps the popup open.
+ NancySceneState.pushScene(itemID);
+ SceneChangeDescription sceneChange;
+ sceneChange.sceneID = item.sceneID;
+ sceneChange.continueSceneSound = item.sceneSoundFlag;
+ NancySceneState.changeScene(sceneChange);
close();
- input.eatMouseInput();
- return;
}
+
+ input.eatMouseInput();
+ return;
}
}
diff --git a/engines/nancy/ui/inventorypopup.h b/engines/nancy/ui/inventorypopup.h
index 7162e96e628..383354bd4d5 100644
--- a/engines/nancy/ui/inventorypopup.h
+++ b/engines/nancy/ui/inventorypopup.h
@@ -84,6 +84,15 @@ private:
void drawCloseButton(bool hovered);
void drawScrollbar(UIButtonState state);
void rebuildVisibleList();
+
+ // Reconcile the shared, save-persisted inventory order list with the items
+ // the player actually owns: drop stale entries and append any owned items
+ // missing from it (in item-ID order). Fixes up saves made before ordering
+ // was tracked in one pass, instead of one item at a time. Returns true if
+ // the order list was changed.
+ bool syncOrderWithInventory();
+
+ bool itemMatchesFilter(int16 itemID) const;
void setActiveFilterIndex(uint index);
// Play a popup button's click sound (the filter tabs and the close X),
Commit: 2cfdf36d97024615d0182d21d148c2309cf3a509
https://github.com/scummvm/scummvm/commit/2cfdf36d97024615d0182d21d148c2309cf3a509
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-09T22:04:44+03:00
Commit Message:
NANCY: Clear held item when starting new game
Fix #16937
Changed paths:
engines/nancy/state/scene.cpp
diff --git a/engines/nancy/state/scene.cpp b/engines/nancy/state/scene.cpp
index ca313397375..a15f95cffe3 100644
--- a/engines/nancy/state/scene.cpp
+++ b/engines/nancy/state/scene.cpp
@@ -1006,6 +1006,11 @@ void Scene::init() {
_flags.items.resize(g_nancy->getStaticData().numItems, g_nancy->_false);
_flags.disabledItems.resize(_flags.items.size(), 0);
+ // The CursorManager is owned by the engine and survives a New Game (which
+ // destroys and recreates the Scene). Clear any held-item cursor left over
+ // from a previous playthrough so a fresh game starts with the normal cursor.
+ g_nancy->_cursor->setCursorItemID(-1);
+
_timers.lastTotalTime = 0;
_timers.playerTime = bootSummary->startTimeHours * 3600000;
_timers.sceneTime = 0;
Commit: da1a1172d23f03ada1dd6c236c85789534798750
https://github.com/scummvm/scummvm/commit/da1a1172d23f03ada1dd6c236c85789534798750
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-09T22:04:45+03:00
Commit Message:
NANCY: NANCY10: Fix cellphone close sound after another sound starts
Fix #16916
Changed paths:
engines/nancy/ui/cellphonepopup.cpp
diff --git a/engines/nancy/ui/cellphonepopup.cpp b/engines/nancy/ui/cellphonepopup.cpp
index ec2e7c319fd..82b1ff3ab15 100644
--- a/engines/nancy/ui/cellphonepopup.cpp
+++ b/engines/nancy/ui/cellphonepopup.cpp
@@ -1679,9 +1679,12 @@ void CellPhonePopup::handleInput(NancyInput &input) {
if (overClose) {
g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
if (input.input & NancyInput::kLeftMouseButtonUp) {
- playButtonClickSound(closeBtn);
input.eatMouseInput();
+ // close() stops the call-sound channel, which the X's click
+ // sound may share; close first so the click sound isn't cut off
+ // once a call / dial / web tone has occupied that channel.
close();
+ playButtonClickSound(closeBtn);
return;
}
}
Commit: c429a02d2c4c08c99da9aaccb0a613077a9daeae
https://github.com/scummvm/scummvm/commit/c429a02d2c4c08c99da9aaccb0a613077a9daeae
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-09T22:04:46+03:00
Commit Message:
NANCY: NANCY10: Fix target for cellphone browser homepage back button
Fix #16940
Changed paths:
engines/nancy/ui/cellphonepopup.cpp
diff --git a/engines/nancy/ui/cellphonepopup.cpp b/engines/nancy/ui/cellphonepopup.cpp
index 82b1ff3ab15..36f19132b99 100644
--- a/engines/nancy/ui/cellphonepopup.cpp
+++ b/engines/nancy/ui/cellphonepopup.cpp
@@ -868,6 +868,12 @@ void CellPhonePopup::openBrowserHome() {
const UIBW *browserData = GetEngineData(UIBW);
if (browserData && !browserData->pages.empty()) {
openContentView(browserData->pages[0].imageName.toString(), _uiclData->browserHeading);
+ // The homepage's Back button always returns to the main phone (welcome)
+ // screen, regardless of whether the browser was opened from the online
+ // hub or reached via the search list's HOME button. openContentView
+ // otherwise records whichever screen we came from, which could send Back
+ // to the search list.
+ _contentReturnState = kWelcome;
} else {
enterScreenState(kWebList);
}
Commit: 9842c1e48da40ffa88f0023a58dac467c7b25253
https://github.com/scummvm/scummvm/commit/9842c1e48da40ffa88f0023a58dac467c7b25253
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-07-09T22:04:47+03:00
Commit Message:
NANCY: NANCY10: Implement text underline
Fix #16943
Changed paths:
engines/nancy/font.cpp
engines/nancy/font.h
engines/nancy/misc/hypertext.cpp
diff --git a/engines/nancy/font.cpp b/engines/nancy/font.cpp
index abfa2179292..2f4fcd63bed 100644
--- a/engines/nancy/font.cpp
+++ b/engines/nancy/font.cpp
@@ -183,6 +183,35 @@ void Font::drawChar(Graphics::Surface *dst, uint32 chr, int x, int y, uint32 col
dest.blitFrom(_image, srcRect, Common::Point(x, y + yOffset));
}
+uint32 Font::getColorPixel(uint color) const {
+ // The glyphs are pre-colored in the atlas (two color variants selected by
+ // the color-coordinate offsets, mirroring drawChar). Scan a solid glyph for
+ // the first non-transparent pixel to recover the color an underline should
+ // use to match the text.
+ Common::Rect src = getCharacterSourceRect('l');
+ if (color == 0) {
+ src.translate(_color0CoordsOffset.x, _color0CoordsOffset.y);
+ } else if (color == 1) {
+ src.translate(_color1CoordsOffset.x, _color1CoordsOffset.y);
+ }
+
+ const Graphics::Surface &surf = _image.rawSurface();
+ for (int yy = src.top; yy < src.bottom; ++yy) {
+ for (int xx = src.left; xx < src.right; ++xx) {
+ if (xx < 0 || yy < 0 || xx >= surf.w || yy >= surf.h) {
+ continue;
+ }
+ const void *p = surf.getBasePtr(xx, yy);
+ const uint32 px = surf.format.bytesPerPixel == 2 ? *(const uint16 *)p : *(const uint32 *)p;
+ if (px != _transColor) {
+ return px;
+ }
+ }
+ }
+
+ return _transColor;
+}
+
void Font::wordWrap(const Common::String &str, int maxWidth, Common::Array<Common::String> &lines, int initWidth) const {
Common::String temp;
for (const char *c = str.begin(); c != str.end(); ++c) {
diff --git a/engines/nancy/font.h b/engines/nancy/font.h
index c3d73477efe..60c4b6301cb 100644
--- a/engines/nancy/font.h
+++ b/engines/nancy/font.h
@@ -52,6 +52,11 @@ public:
const Graphics::ManagedSurface &getImageSurface() const { return _image; }
+ // Samples an opaque pixel from a solid glyph rendered in the given color
+ // variant, so callers can match the exact text color. Used to draw
+ // underlines (the <u> markup toggle), for which the atlas has no glyph.
+ uint32 getColorPixel(uint color) const;
+
// Custom word wrapping function to fix an edge case with overflowing whitespaces
void wordWrap(const Common::String &str, int maxWidth, Common::Array<Common::String> &lines, int initWidth = 0) const;
diff --git a/engines/nancy/misc/hypertext.cpp b/engines/nancy/misc/hypertext.cpp
index c43eb941456..a01671b29e0 100644
--- a/engines/nancy/misc/hypertext.cpp
+++ b/engines/nancy/misc/hypertext.cpp
@@ -30,7 +30,7 @@ namespace Nancy {
namespace Misc {
struct MetaInfo {
- enum Type { kColor, kFont, kMark, kHotspot };
+ enum Type { kColor, kFont, kMark, kHotspot, kUnderline };
Type type;
uint numChars;
@@ -171,6 +171,15 @@ void HypertextParser::drawAllText(const Common::Rect &textBounds, uint leftOffse
currentLine += '\t';
continue;
+ case 'u' :
+ // Underline toggle. Paired <u> tags bracket underlined text
+ // (e.g. journal cross-references).
+ if (curToken.size() != 1) {
+ break;
+ }
+
+ metaInfo.push({MetaInfo::kUnderline, numNonSpaceChars, 0});
+ continue;
case 'c' :
// Color tokens
// We keep the positions (excluding spaces) and colors of the color tokens in a queue
@@ -259,6 +268,7 @@ void HypertextParser::drawAllText(const Common::Rect &textBounds, uint leftOffse
// respect color tokens
uint totalCharsDrawn = 0;
byte colorID = _defaultTextColor;
+ bool underline = false;
uint numNewlineTokens = 0;
uint horizontalOffset = 0;
bool newLineStart = false;
@@ -327,6 +337,9 @@ void HypertextParser::drawAllText(const Common::Rect &textBounds, uint leftOffse
case MetaInfo::kColor:
colorID = change.index;
break;
+ case MetaInfo::kUnderline:
+ underline = !underline;
+ break;
case MetaInfo::kMark: {
auto *mark = GetEngineData(MARK);
assert(mark);
@@ -407,13 +420,23 @@ void HypertextParser::drawAllText(const Common::Rect &textBounds, uint leftOffse
Common::String &stringToDraw = subLine.size() ? subLine : line;
// Draw the normal text
+ const int drawX = textBounds.left + horizontalOffset + (newLineStart ? 0 : leftOffsetNonNewline);
+ const int drawY = textBounds.top + _numDrawnLines * lineStep(font) + _imageVerticalOffset;
font->drawString( &_fullSurface,
stringToDraw,
- textBounds.left + horizontalOffset + (newLineStart ? 0 : leftOffsetNonNewline),
- textBounds.top + _numDrawnLines * lineStep(font) + _imageVerticalOffset,
+ drawX,
+ drawY,
textBounds.width(),
colorID);
+ // Underline the segment (the <u> markup toggle) in the text color.
+ if (underline && !stringToDraw.empty()) {
+ const int underlineWidth = font->getStringWidth(stringToDraw);
+ const int underlineY = drawY + font->getFontHeight() - 1;
+ _fullSurface.fillRect(Common::Rect(drawX, underlineY, drawX + underlineWidth, underlineY + 1),
+ font->getColorPixel(colorID));
+ }
+
// Then, draw the highlight
if (hasHotspot && !_textHighlightSurface.empty()) {
highlightFont->drawString( &_textHighlightSurface,
More information about the Scummvm-git-logs
mailing list