[Scummvm-git-logs] scummvm master -> cb9177ecac780178317f79bf3f20fa94d16a58b5
bluegr
noreply at scummvm.org
Thu May 7 00:56:06 UTC 2026
This automated email contains information about 4 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .
Summary:
1a8d9ceb2f NANCY: Make sure that SecondayMovie video decoder is always initialized
c7f8d942b7 NANCY: Update cursor in multibuildpuzzle if a piece can't be dropped
1a3f9a14cb NANCY: Use getters to access single and combo player table values
cb9177ecac NANCY: More work on the new inventory popup used in Nancy10+
Commit: 1a8d9ceb2fe47aff1b6c4040c496a21b7917e412
https://github.com/scummvm/scummvm/commit/1a8d9ceb2fe47aff1b6c4040c496a21b7917e412
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-05-07T03:55:35+03:00
Commit Message:
NANCY: Make sure that SecondayMovie video decoder is always initialized
It may not be initialized when onPause() is called after loading.
Fix #16768
Changed paths:
engines/nancy/action/secondarymovie.cpp
diff --git a/engines/nancy/action/secondarymovie.cpp b/engines/nancy/action/secondarymovie.cpp
index 8059bb28d58..6dcf803c4b7 100644
--- a/engines/nancy/action/secondarymovie.cpp
+++ b/engines/nancy/action/secondarymovie.cpp
@@ -104,11 +104,10 @@ void PlaySecondaryMovie::readData(Common::SeekableReadStream &stream) {
void PlaySecondaryMovie::init() {
if (!_decoder) {
- if (_videoType == kVideoPlaytypeAVF) {
+ if (_videoType == kVideoPlaytypeAVF)
_decoder.reset(new AVFDecoder());
- } else {
+ else
_decoder.reset(new Video::BinkDecoder());
- }
}
if (!_decoder->isVideoLoaded()) {
@@ -137,6 +136,13 @@ void PlaySecondaryMovie::init() {
}
void PlaySecondaryMovie::onPause(bool pause) {
+ if (!_decoder) {
+ if (_videoType == kVideoPlaytypeAVF)
+ _decoder.reset(new AVFDecoder());
+ else
+ _decoder.reset(new Video::BinkDecoder());
+ }
+
_decoder->pauseVideo(pause);
RenderActionRecord::onPause(pause);
}
Commit: c7f8d942b724fd6ab506c2d573ca463dedaa62d5
https://github.com/scummvm/scummvm/commit/c7f8d942b724fd6ab506c2d573ca463dedaa62d5
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-05-07T03:55:37+03:00
Commit Message:
NANCY: Update cursor in multibuildpuzzle if a piece can't be dropped
Fix #16751
Changed paths:
engines/nancy/action/puzzle/multibuildpuzzle.cpp
engines/nancy/action/puzzle/multibuildpuzzle.h
diff --git a/engines/nancy/action/puzzle/multibuildpuzzle.cpp b/engines/nancy/action/puzzle/multibuildpuzzle.cpp
index ac0426b79d7..9bba84fd8e0 100644
--- a/engines/nancy/action/puzzle/multibuildpuzzle.cpp
+++ b/engines/nancy/action/puzzle/multibuildpuzzle.cpp
@@ -244,6 +244,31 @@ void MultiBuildPuzzle::execute() {
}
}
+bool MultiBuildPuzzle::isValidDrop() const {
+ const Piece &pp = _pieces[_pickedUpPiece];
+
+ // Geometric checks apply only to non-closeup puzzles with a win condition (e.g. books).
+ // Sand castle (no closeup image, _requiredPieces=0) allows free stacking.
+ // Sandwich puzzle (has closeup image) allows free-form placement.
+ if (!_hasCloseupImage && _requiredPieces > 0) {
+ // Boundary check: drop center must be inside the target zone
+ Common::Point dropCenter((pp.gameRect.left + pp.gameRect.right) / 2,
+ (pp.gameRect.top + pp.gameRect.bottom) / 2);
+ if (!_targetZone.isEmpty() && !_targetZone.contains(dropCenter))
+ return false;
+
+ // Overlap check: piece must not overlap any already-placed piece
+ for (uint i = 0; i < _pieces.size(); ++i) {
+ if ((int)i != _pickedUpPiece && _pieces[i].isPlaced &&
+ pp.gameRect.intersects(_pieces[i].gameRect)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
void MultiBuildPuzzle::handleInput(NancyInput &input) {
if (_state != kRun || _solveState != kIdle || _isSolved || _isCancelled)
return;
@@ -268,8 +293,9 @@ void MultiBuildPuzzle::handleInput(NancyInput &input) {
pp.gameRect.right = newLeft + _pickedUpWidth;
pp.gameRect.bottom = newTop + _pickedUpHeight;
updatePieceRender(_pickedUpPiece);
+ bool validDrop = isValidDrop();
- g_nancy->_cursor->setCursorType(CursorManager::kCustom1);
+ g_nancy->_cursor->setCursorType(validDrop ? CursorManager::kCustom1 : CursorManager::kNormal);
// Right click: rotate the carried piece
if ((input.input & NancyInput::kRightMouseButtonUp) && pp.hasSurface[1]) {
@@ -286,30 +312,6 @@ void MultiBuildPuzzle::handleInput(NancyInput &input) {
// the piece overlaps an already-placed piece; piece returns to shelf on rejection.
// For closeup puzzles: no geometric checks â free-form placement.
if (input.input & NancyInput::kLeftMouseButtonUp) {
- bool validDrop = true;
-
- // Geometric checks apply only to non-closeup puzzles with a win condition (e.g. books).
- // Sand castle (no closeup image, _requiredPieces=0) allows free stacking.
- // Sandwich puzzle (has closeup image) allows free-form placement.
- if (!_hasCloseupImage && _requiredPieces > 0) {
- // Boundary check: drop center must be inside the target zone
- Common::Point dropCenter((pp.gameRect.left + pp.gameRect.right) / 2,
- (pp.gameRect.top + pp.gameRect.bottom) / 2);
- if (!_targetZone.isEmpty() && !_targetZone.contains(dropCenter))
- validDrop = false;
-
- // Overlap check: piece must not overlap any already-placed piece
- if (validDrop) {
- for (uint i = 0; i < _pieces.size(); ++i) {
- if ((int)i != _pickedUpPiece && _pieces[i].isPlaced &&
- pp.gameRect.intersects(_pieces[i].gameRect)) {
- validDrop = false;
- break;
- }
- }
- }
- }
-
// Clear drag state BEFORE updatePieceRender so the correct visual is chosen:
// - valid drop: isPlaced=true, isDragging=false -> shows rotation surface at drop pos
// - invalid drop: isPlaced=false, isDragging=false -> shows shelf srcRect at homeRect
diff --git a/engines/nancy/action/puzzle/multibuildpuzzle.h b/engines/nancy/action/puzzle/multibuildpuzzle.h
index 37439bd82d5..918c0e6c5d5 100644
--- a/engines/nancy/action/puzzle/multibuildpuzzle.h
+++ b/engines/nancy/action/puzzle/multibuildpuzzle.h
@@ -51,6 +51,7 @@ public:
protected:
Common::String getRecordTypeName() const override { return "MultiBuildPuzzle"; }
+ bool isValidDrop() const;
// A single puzzle piece. Each piece is its own RenderObject.
// Unplaced: _drawSurface shows srcRect from primary image.
Commit: 1a3f9a14cb99760ef0d15036083b520b5a8b5140
https://github.com/scummvm/scummvm/commit/1a3f9a14cb99760ef0d15036083b520b5a8b5140
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-05-07T03:55:39+03:00
Commit Message:
NANCY: Use getters to access single and combo player table values
There are cases where these values aren't set, so a default value
should be returned. Happens sometimes when returning a crab to Holt
Scotto in Nancy 9.
Fix #16766
Changed paths:
engines/nancy/action/datarecords.cpp
diff --git a/engines/nancy/action/datarecords.cpp b/engines/nancy/action/datarecords.cpp
index ee01633f6e9..68fcb42da13 100644
--- a/engines/nancy/action/datarecords.cpp
+++ b/engines/nancy/action/datarecords.cpp
@@ -176,14 +176,14 @@ void SetValueCombo::execute() {
} else {
if (_indices[i] < numSingleValues) {
// Add a single value
- if (playerTable->singleValues[_indices[i]] != kNoTableValue) {
- valueToAdd = playerTable->singleValues[_indices[i]];
+ if (playerTable->getSingleValue(_indices[i]) != kNoTableValue) {
+ valueToAdd = playerTable->getSingleValue(_indices[i]);
valueToAdd = valueToAdd * ((float)_percentages[i] / 100.f);
}
} else {
// Add another combo value
- if (playerTable->comboValues[_indices[i] - numSingleValues] != kNoTableValue) {
- valueToAdd = playerTable->comboValues[_indices[i] - numSingleValues];
+ if (playerTable->getComboValue(_indices[i] - numSingleValues) != (float)kNoTableValue) {
+ valueToAdd = playerTable->getComboValue(_indices[i] - numSingleValues);
valueToAdd = valueToAdd * ((float)_percentages[i] / 100.f);
}
}
Commit: cb9177ecac780178317f79bf3f20fa94d16a58b5
https://github.com/scummvm/scummvm/commit/cb9177ecac780178317f79bf3f20fa94d16a58b5
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2026-05-07T03:55:40+03:00
Commit Message:
NANCY: More work on the new inventory popup used in Nancy10+
- Implement filtering and filter tabs
- Implement the filter caption above the window
- Implement the scrollbar and paging
- Implement the close button
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 0ccdea94dbf..a545ad1d5ab 100644
--- a/engines/nancy/ui/inventorypopup.cpp
+++ b/engines/nancy/ui/inventorypopup.cpp
@@ -36,15 +36,9 @@ namespace UI {
InventoryPopup::InventoryPopup() :
// z=12: above the viewport (6) and the taskbar (7). All Nancy
// 10+ taskbar popups render on top of the entire scene UI.
- RenderObject(12),
- _uiivData(nullptr),
- _invData(nullptr),
- _isOpen(false),
- _activeFilter(kFilterAll),
- _currentPage(0) {
- for (uint i = 0; i < kSlotsPerPage; ++i) {
+ RenderObject(12) {
+ for (uint i = 0; i < kSlotsPerPage; ++i)
_slotItemIDs[i] = -1;
- }
}
void InventoryPopup::init() {
@@ -54,28 +48,26 @@ void InventoryPopup::init() {
_invData = GetEngineData(INV);
assert(_invData);
- // Popup background image (e.g. "UIInv_OVL").
g_nancy->_resource->loadImage(_uiivData->header.imageName, _overlayImage);
-
- // Per-item icon sheet shared with the legacy InventoryBox.
g_nancy->_resource->loadImage(_invData->inventoryBoxIconsImageName, _itemIcons);
- // Position the popup using the "normal-size" rects from the popup
- // header (popup state 2).
Common::Rect popupRect = _uiivData->header.normalDestRect;
if (_uiivData->header.overlayInGameFrame) {
const VIEW *view = GetEngineData(VIEW);
- if (view) {
+ if (view)
popupRect.translate(view->screenPosition.left, view->screenPosition.top);
- }
}
moveTo(popupRect);
+
Common::Rect bounds = _screenPosition;
bounds.moveTo(0, 0);
_drawSurface.create(bounds.width(), bounds.height(), g_nancy->_graphics->getInputPixelFormat());
+ setActiveFilterIndex(0);
+
drawBackground();
drawFilterTabs();
+ drawFilterCaption();
setTransparent(false);
setVisible(false);
@@ -83,15 +75,10 @@ void InventoryPopup::init() {
RenderObject::init();
}
-void InventoryPopup::registerGraphics() {
- RenderObject::registerGraphics();
-}
-
void InventoryPopup::open() {
- if (_isOpen) {
+ if (_isVisible)
return;
- }
- _isOpen = true;
+
_currentPage = 0;
rebuildVisibleList();
@@ -106,10 +93,8 @@ void InventoryPopup::open() {
}
void InventoryPopup::close() {
- if (!_isOpen) {
+ if (!_isVisible)
return;
- }
- _isOpen = false;
setVisible(false);
@@ -122,9 +107,9 @@ void InventoryPopup::close() {
void InventoryPopup::refreshGrid() {
rebuildVisibleList();
- // Re-blit the popup background; this also wipes any previous slot icons.
drawBackground();
drawFilterTabs();
+ drawFilterCaption();
for (uint i = 0; i < kSlotsPerPage; ++i) {
const uint listIndex = _currentPage * kSlotsPerPage + i;
@@ -143,35 +128,24 @@ void InventoryPopup::rebuildVisibleList() {
_invData->itemDescriptions.size());
for (uint16 id = 0; id < numItems; ++id) {
- if (NancySceneState.hasItem(id) != g_nancy->_true) {
+ if (NancySceneState.hasItem(id) != g_nancy->_true)
continue;
- }
const INV::ItemDescription &desc = _invData->itemDescriptions[id];
- bool include;
- switch (_activeFilter) {
- case kFilterDocuments:
- include = (desc.keepItem == 3);
- break;
- case kFilterUsable:
- include = (desc.keepItem <= 2);
+ switch (_activeFilterIndex) {
+ case kFilterViewable:
+ if (desc.keepItem == 3)
+ _visibleItems.push_back(id);
break;
- case kFilterSpecial1:
- case kFilterSpecial2:
- case kFilterSpecial3:
- // Specialty filters
- // TODO: Show nothing, for now
- include = false;
+ case kFilterPortable:
+ if (desc.keepItem <= 2)
+ _visibleItems.push_back(id);
break;
case kFilterAll:
default:
- include = true;
- break;
- }
-
- if (include) {
_visibleItems.push_back(id);
+ break;
}
}
}
@@ -179,41 +153,135 @@ void InventoryPopup::rebuildVisibleList() {
void InventoryPopup::drawBackground() {
Common::Rect src = _uiivData->header.normalSrcRect;
_drawSurface.blitFrom(_overlayImage, src, Common::Point(0, 0));
+
+ drawCloseButton(_closeButtonHovered ? kStateHover : kStateIdle);
+
+ WidgetState sliderState = kStateIdle;
+ if (_scrollbarDragging)
+ sliderState = kStatePressed;
+ else if (_scrollbarHovered)
+ sliderState = kStateHover;
+
+ drawScrollbar(sliderState);
}
-void InventoryPopup::drawFilterTabs() {
- // Sub-rects in the chunk are stored relative to header.normalDestRect.
- // After we translate the popup by the viewport offset, _screenPosition
- // no longer matches that origin, so popup-local conversions must
- // subtract chunk.normalDestRect.topLeft, not _screenPosition.
+Common::Rect InventoryPopup::computeSliderRect() const {
+ const UISliderRecord &sl = _uiivData->header.slider;
+ if (!_uiivData->header.sliderEnabled)
+ return Common::Rect();
+
+ const int trackHeight = sl.destRect.height();
+ const int thumbHeight = sl.sourceRects[0].height();
+ const int travel = MAX(0, trackHeight - thumbHeight);
+ const int thumbY = sl.destRect.top + (int)(_scrollPos * travel);
+
const Common::Point chunkOrigin(_uiivData->header.normalDestRect.left,
_uiivData->header.normalDestRect.top);
- for (const auto &filter : _uiivData->filters) {
- if (!filter.enabled || filter.button.sourceRects[0].isEmpty()) {
- continue;
- }
+ Common::Rect r(sl.destRect.left, thumbY,
+ sl.destRect.left + sl.sourceRects[0].width(),
+ thumbY + thumbHeight);
+ r.translate(-chunkOrigin.x, -chunkOrigin.y);
+ return r;
+}
- _drawSurface.blitFrom(_overlayImage, filter.button.sourceRects[0],
- Common::Point(filter.button.destRect.left - chunkOrigin.x,
- filter.button.destRect.top - chunkOrigin.y));
- }
+void InventoryPopup::drawScrollbar(WidgetState state) {
+ const UISliderRecord &sl = _uiivData->header.slider;
+ if (!_uiivData->header.sliderEnabled)
+ return;
+
+ Common::Rect spr = sl.sourceRects[state];
+ if (spr.isEmpty())
+ spr = sl.sourceRects[0];
+ if (spr.isEmpty())
+ return;
+
+ const Common::Rect thumb = computeSliderRect();
+ if (thumb.isEmpty())
+ return;
+
+ _drawSurface.blitFrom(_overlayImage, spr, Common::Point(thumb.left, thumb.top));
}
-void InventoryPopup::drawSlot(uint slotIndex, int16 itemID) {
- if (slotIndex >= _uiivData->slotDestRects.size()) {
+void InventoryPopup::updatePageFromScroll() {
+ const uint numPages = (_visibleItems.size() + kSlotsPerPage - 1) / kSlotsPerPage;
+ if (numPages <= 1) {
+ _currentPage = 0;
+ _scrollPos = 0.0f;
return;
}
- if (itemID < 0 || itemID >= (int16)_invData->itemDescriptions.size()) {
- // Empty slot â leave the popup-background pixels in place.
- return;
+ const uint maxPage = numPages - 1;
+ uint page = (uint)(_scrollPos * maxPage + 0.5f);
+ _currentPage = MIN<uint>(page, maxPage);
+}
+
+void InventoryPopup::drawCloseButton(WidgetState state) {
+ const UIButtonRecord &btn = _uiivData->header.secondaryButton;
+ Common::Rect spr = btn.sourceRects[state];
+ const Common::Point chunkOrigin(_uiivData->header.normalDestRect.left,
+ _uiivData->header.normalDestRect.top);
+ const Common::Point dst(btn.destRect.left - chunkOrigin.x,
+ btn.destRect.top - chunkOrigin.y);
+
+ _drawSurface.blitFrom(_overlayImage, spr, dst);
+}
+
+void InventoryPopup::drawFilterTabs() {
+ for (uint i = 0; i < kNumFilters; ++i) {
+ drawFilterTab(i);
}
+}
+
+void InventoryPopup::drawFilterTab(uint index, bool drawHover) {
+ const Common::Point chunkOrigin(_uiivData->header.normalDestRect.left,
+ _uiivData->header.normalDestRect.top);
- const INV::ItemDescription &desc = _invData->itemDescriptions[itemID];
- if (desc.sourceRect.isEmpty()) {
+ const UIButtonSlot &filter = _uiivData->filters[index];
+ if (!filter.enabled)
+ return;
+
+ const bool isActive = (index == _activeFilterIndex);
+ uint rectIndex = isActive ? kStatePressed : kStateIdle;
+ if (drawHover)
+ rectIndex = kStateHover;
+ Common::Rect spr = filter.button.sourceRects[rectIndex];
+
+ _drawSurface.blitFrom(_overlayImage, spr,
+ Common::Point(filter.button.destRect.left - chunkOrigin.x,
+ filter.button.destRect.top - chunkOrigin.y));
+}
+
+
+void InventoryPopup::drawFilterCaption() {
+ if (_activeFilterIndex >= _uiivData->tabCaptionSrcRects.size())
+ return;
+
+ const Common::Rect &spr = _uiivData->tabCaptionSrcRects[_activeFilterIndex];
+ const Common::Point chunkOrigin(_uiivData->header.normalDestRect.left,
+ _uiivData->header.normalDestRect.top);
+ _drawSurface.blitFrom(_overlayImage, spr,
+ Common::Point(_uiivData->tabCaptionDestRect.left - chunkOrigin.x,
+ _uiivData->tabCaptionDestRect.top - chunkOrigin.y));
+}
+
+void InventoryPopup::setActiveFilterIndex(uint index) {
+ if (index >= kNumFilters)
+ return;
+
+ _activeFilterIndex = index;
+}
+
+void InventoryPopup::drawSlot(uint slotIndex, int16 itemId) {
+ if (slotIndex >= _uiivData->slotDestRects.size())
+ return;
+
+ if (itemId < 0 || itemId >= (int16)_invData->itemDescriptions.size())
+ return;
+
+ const INV::ItemDescription &desc = _invData->itemDescriptions[itemId];
+ if (desc.sourceRect.isEmpty())
return;
- }
const Common::Point chunkOrigin(_uiivData->header.normalDestRect.left,
_uiivData->header.normalDestRect.top);
@@ -224,25 +292,90 @@ void InventoryPopup::drawSlot(uint slotIndex, int16 itemID) {
}
void InventoryPopup::handleInput(NancyInput &input) {
- if (!_isOpen) {
+ if (!_isVisible)
return;
- }
- // Bring the mouse into the chunk's coordinate system so hit-tests
- // against the chunk's destRects work after we translated the popup
- // by the viewport offset.
const Common::Point chunkMouse(
input.mousePos.x - _screenPosition.left + _uiivData->header.normalDestRect.left,
input.mousePos.y - _screenPosition.top + _uiivData->header.normalDestRect.top);
+ // Scrollbar interaction takes priority while dragging.
+ const UISliderRecord &slider = _uiivData->header.slider;
+ if (_uiivData->header.sliderEnabled) {
+ const Common::Rect &track = slider.destRect;
+ // The thumb rect sits inside the track; build it in chunk coords
+ // for hit-testing against `chunkMouse` (which is also in chunk
+ // coords since we did the conversion above).
+ const int trackHeight = track.height();
+ const int thumbHeight = slider.sourceRects[0].height();
+ const int travel = MAX(0, trackHeight - thumbHeight);
+ const int thumbY = track.top + (int)(_scrollPos * travel);
+ Common::Rect thumbInChunk(track.left, thumbY,
+ track.left + slider.sourceRects[0].width(),
+ thumbY + thumbHeight);
+
+ const bool overScrollbar = thumbInChunk.contains(chunkMouse);
+
+ if (_scrollbarDragging) {
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
+
+ const int newThumbTop = chunkMouse.y - _scrollbarGrabOffset;
+ const int clamped = CLIP<int>(newThumbTop, track.top, track.top + travel);
+ _scrollPos = travel > 0 ? (float)(clamped - track.top) / (float)travel : 0.0f;
+ updatePageFromScroll();
+ refreshGrid();
+
+ if (input.input & NancyInput::kLeftMouseButtonUp) {
+ _scrollbarDragging = false;
+ drawScrollbar(overScrollbar ? kStateHover : kStateIdle);
+ _needsRedraw = true;
+ }
+ input.eatMouseInput();
+ return;
+ }
+
+ if (overScrollbar != _scrollbarHovered) {
+ _scrollbarHovered = overScrollbar;
+ drawScrollbar(overScrollbar ? kStateHover : kStateIdle);
+ _needsRedraw = true;
+ }
+ if (overScrollbar) {
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
+ if (slider.isDraggable && (input.input & NancyInput::kLeftMouseButtonDown)) {
+ _scrollbarDragging = true;
+ _scrollbarGrabOffset = chunkMouse.y - thumbY;
+ drawScrollbar(kStatePressed);
+ _needsRedraw = true;
+ input.eatMouseInput();
+ return;
+ }
+ }
+ }
+
+ if (_uiivData->header.secondaryButtonEnabled) {
+ const Common::Rect &closeRect = _uiivData->header.secondaryButton.destRect;
+ const bool overClose = closeRect.contains(chunkMouse);
+ if (overClose != _closeButtonHovered) {
+ _closeButtonHovered = overClose;
+ drawCloseButton(overClose ? kStateHover : kStateIdle);
+ _needsRedraw = true;
+ }
+ if (overClose) {
+ g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
+ if (input.input & NancyInput::kLeftMouseButtonUp) {
+ input.eatMouseInput();
+ close();
+ return;
+ }
+ }
+ }
+
int newHovered = -1;
for (uint i = 0; i < kSlotsPerPage; ++i) {
- if (i >= _uiivData->slotDestRects.size()) {
+ if (i >= _uiivData->slotDestRects.size())
break;
- }
- if (_slotItemIDs[i] < 0) {
+ if (_slotItemIDs[i] < 0)
continue;
- }
if (_uiivData->slotDestRects[i].contains(chunkMouse)) {
newHovered = (int)i;
break;
@@ -266,20 +399,37 @@ void InventoryPopup::handleInput(NancyInput &input) {
}
}
- // Filter tabs: clicking switches the active filter.
- for (const auto &filter : _uiivData->filters) {
- if (!filter.enabled) {
- continue;
+ bool wasHovered = _filterHovered;
+ _filterHovered = false;
+
+ for (uint i = 0; i < kNumFilters; ++i) {
+ const UIButtonSlot &filter = _uiivData->filters[i];
+ if (filter.button.destRect.contains(chunkMouse)) {
+ _filterHovered = true;
+ break;
}
+ }
+
+ for (uint i = 0; i < kNumFilters; ++i) {
+ const UIButtonSlot &filter = _uiivData->filters[i];
+ if (!filter.enabled)
+ continue;
+
if (!filter.button.destRect.contains(chunkMouse)) {
+ if (_filterHovered || wasHovered)
+ drawFilterTab(i);
continue;
}
g_nancy->_cursor->setCursorType(CursorManager::kHotspotArrow);
+ drawFilterTab(i, true);
+
if (input.input & NancyInput::kLeftMouseButtonUp) {
- _activeFilter = static_cast<FilterID>(filter.id);
+ setActiveFilterIndex(i);
_currentPage = 0;
+ _scrollPos = 0.0f;
+ _scrollbarDragging = false;
refreshGrid();
input.eatMouseInput();
return;
@@ -287,11 +437,13 @@ void InventoryPopup::handleInput(NancyInput &input) {
break;
}
+ if (_filterHovered || wasHovered)
+ _needsRedraw = true;
+
// While the popup is open, swallow clicks that fall on the popup so
// the underlying scene/viewport doesn't react.
- if (_screenPosition.contains(input.mousePos)) {
+ if (_screenPosition.contains(input.mousePos))
input.eatMouseInput();
- }
}
} // End of namespace UI
diff --git a/engines/nancy/ui/inventorypopup.h b/engines/nancy/ui/inventorypopup.h
index a2b340640aa..97c7193c185 100644
--- a/engines/nancy/ui/inventorypopup.h
+++ b/engines/nancy/ui/inventorypopup.h
@@ -45,50 +45,66 @@ public:
~InventoryPopup() override = default;
void init() override;
- void registerGraphics() override;
void handleInput(NancyInput &input);
- bool isOpen() const { return _isOpen; }
+ bool isOpen() const { return _isVisible; }
void open();
void close();
// The taskbar's inventory button toggles the popup; convenience helper.
- void toggle() { if (_isOpen) close(); else open(); }
+ void toggle() { if (_isVisible) close(); else open(); }
// Re-render the slot grid. Called by Scene whenever the inventory
// contents change while the popup is open.
void refreshGrid();
- enum FilterID {
- kFilterAll = 0x64, // default branch â every owned item
- kFilterDocuments = 0x65, // keepItem == 3
- kFilterUsable = 0x66, // keepItem == 0/1/2
- kFilterSpecial1 = 0x67,
- kFilterSpecial2 = 0x68,
- kFilterSpecial3 = 0x69
+ enum FilterType {
+ kFilterAll = 0,
+ kFilterViewable = 1,
+ kFilterPortable = 2,
+ };
+
+ enum WidgetState {
+ kStatePressed = 0,
+ kStateHover = 1,
+ kStateIdle = 2,
+ kStateDisabled = 3
};
private:
static const uint kSlotsPerPage = 16;
- static const uint kNumFilters = 6;
+ static const uint kNumFilters = 3;
void drawBackground();
- void drawSlot(uint slotIndex, int16 itemID);
+ void drawSlot(uint slotIndex, int16 itemId);
void drawFilterTabs();
+ void drawFilterTab(uint index, bool drawHover = false);
+ void drawFilterCaption();
+ void drawCloseButton(WidgetState state);
+ void drawScrollbar(WidgetState state);
void rebuildVisibleList();
+ void setActiveFilterIndex(uint index);
+
+ // Apply the current scrollbar position to the page index, clamping
+ // to the number of pages required by the active filter.
+ void updatePageFromScroll();
- const UIIV *_uiivData;
- const INV *_invData;
+ // Returns the on-popup-surface bounding rect of the slider thumb at
+ // the current scroll position (in popup-local coords).
+ Common::Rect computeSliderRect() const;
- Graphics::ManagedSurface _overlayImage; // popup background image
- Graphics::ManagedSurface _itemIcons; // per-item icon sheet
+ const UIIV *_uiivData = nullptr;
+ const INV *_invData = nullptr;
- bool _isOpen;
+ Graphics::ManagedSurface _overlayImage; // popup background image
+ Graphics::ManagedSurface _itemIcons; // per-item icon sheet
- FilterID _activeFilter;
+ bool _closeButtonHovered = false;
+
+ uint _activeFilterIndex = 0;
// Page index within the active filter (0-based).
- uint _currentPage;
+ uint _currentPage = 0;
// Items the player owns that match the active filter, in inventory
// order. The on-screen grid is a 16-item window into this array.
@@ -96,6 +112,14 @@ private:
// Item ID currently shown in each of the 16 slots (-1 if empty).
int16 _slotItemIDs[kSlotsPerPage];
+
+ // Slider state. Driven by header.slider.
+ float _scrollPos = 0.0f; // [0, 1]: 0 = top page, 1 = bottom page
+ bool _scrollbarDragging = false;
+ bool _scrollbarHovered = false;
+ int _scrollbarGrabOffset = 0; // mouse-y minus thumb-top at click time
+
+ bool _filterHovered = false;
};
} // End of namespace UI
More information about the Scummvm-git-logs
mailing list