2021-02-19 02:17:41 +02:00
|
|
|
/* ScummVM - Graphic Adventure Engine
|
|
|
|
*
|
|
|
|
* ScummVM is the legal property of its developers, whose names
|
|
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
|
|
* file distributed with this source distribution.
|
|
|
|
*
|
2021-12-26 18:47:58 +01:00
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
2021-02-19 02:17:41 +02:00
|
|
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
2021-12-26 18:47:58 +01:00
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2021-02-19 02:17:41 +02:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2021-04-16 17:20:47 +03:00
|
|
|
#include "common/system.h"
|
|
|
|
|
2021-02-19 02:17:41 +02:00
|
|
|
#include "engines/nancy/nancy.h"
|
|
|
|
#include "engines/nancy/graphics.h"
|
|
|
|
#include "engines/nancy/cursor.h"
|
2021-03-26 01:17:07 +02:00
|
|
|
#include "engines/nancy/input.h"
|
2021-04-01 21:04:38 +03:00
|
|
|
#include "engines/nancy/util.h"
|
|
|
|
|
|
|
|
#include "engines/nancy/state/scene.h"
|
|
|
|
|
|
|
|
#include "engines/nancy/ui/viewport.h"
|
2021-03-13 02:01:26 +02:00
|
|
|
|
2023-09-17 01:24:34 +03:00
|
|
|
#include "common/config-manager.h"
|
|
|
|
|
2021-02-19 02:17:41 +02:00
|
|
|
namespace Nancy {
|
|
|
|
namespace UI {
|
|
|
|
|
|
|
|
// does NOT put the object in a valid state until loadVideo is called
|
|
|
|
void Viewport::init() {
|
2023-10-08 00:39:36 +03:00
|
|
|
auto *bootSummary = GetEngineData(BSUM);
|
2023-08-29 22:52:23 +03:00
|
|
|
assert(bootSummary);
|
2021-03-19 18:37:20 +02:00
|
|
|
|
2023-10-08 00:39:36 +03:00
|
|
|
auto *viewportData = GetEngineData(VIEW);
|
2023-08-29 22:52:23 +03:00
|
|
|
assert(viewportData);
|
|
|
|
|
|
|
|
moveTo(viewportData->screenPosition);
|
|
|
|
|
|
|
|
setEdgesSize( bootSummary->verticalEdgesSize,
|
|
|
|
bootSummary->verticalEdgesSize,
|
|
|
|
bootSummary->horizontalEdgesSize,
|
|
|
|
bootSummary->horizontalEdgesSize);
|
2021-03-19 18:37:20 +02:00
|
|
|
|
|
|
|
RenderObject::init();
|
2021-02-19 02:17:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Viewport::handleInput(NancyInput &input) {
|
2023-09-21 14:45:56 +03:00
|
|
|
const Nancy::State::Scene::SceneSummary &summary = NancySceneState.getSceneSummary();
|
2023-04-03 23:05:21 +03:00
|
|
|
Time systemTime = g_system->getMillis();
|
2021-03-19 18:37:20 +02:00
|
|
|
byte direction = 0;
|
|
|
|
|
2023-09-21 14:45:56 +03:00
|
|
|
if (summary.slowMoveTimeDelta == kNoAutoScroll) {
|
|
|
|
// Individual scenes may disable auto-move even when it's globally turned on
|
|
|
|
_autoMove = false;
|
|
|
|
} else {
|
|
|
|
_autoMove = ConfMan.getBool("auto_move", ConfMan.getActiveDomainName());
|
|
|
|
}
|
2023-09-17 01:24:34 +03:00
|
|
|
|
2021-04-16 17:20:47 +03:00
|
|
|
// Make cursor sticky when scrolling the viewport
|
2021-05-07 22:50:26 +03:00
|
|
|
if ( g_nancy->getGameType() != kGameTypeVampire &&
|
|
|
|
input.input & (NancyInput::kLeftMouseButton | NancyInput::kRightMouseButton)
|
|
|
|
&& _stickyCursorPos.x > -1) {
|
2024-02-10 16:28:07 +01:00
|
|
|
g_nancy->_cursor->warpCursor(_stickyCursorPos);
|
2021-04-16 17:20:47 +03:00
|
|
|
input.mousePos = _stickyCursorPos;
|
|
|
|
}
|
|
|
|
|
2021-04-26 16:27:29 +03:00
|
|
|
Common::Rect viewportActiveZone;
|
|
|
|
|
|
|
|
if (g_nancy->getGameType() == kGameTypeVampire) {
|
2024-02-10 16:28:07 +01:00
|
|
|
viewportActiveZone = g_nancy->_graphics->getScreen()->getBounds();
|
2021-04-26 16:27:29 +03:00
|
|
|
viewportActiveZone.bottom = _screenPosition.bottom;
|
|
|
|
} else {
|
|
|
|
viewportActiveZone = _screenPosition;
|
2021-03-19 18:37:20 +02:00
|
|
|
}
|
2021-05-04 11:45:03 +03:00
|
|
|
|
2021-04-26 16:27:29 +03:00
|
|
|
if (viewportActiveZone.contains(input.mousePos)) {
|
2024-02-10 16:28:07 +01:00
|
|
|
g_nancy->_cursor->setCursorType(CursorManager::kNormal);
|
2021-03-19 18:37:20 +02:00
|
|
|
|
2021-04-26 16:27:29 +03:00
|
|
|
if (input.mousePos.x < _nonScrollZone.left) {
|
|
|
|
direction |= kLeft;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (input.mousePos.x > _nonScrollZone.right) {
|
|
|
|
direction |= kRight;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (input.mousePos.y < _nonScrollZone.top) {
|
2021-03-19 18:37:20 +02:00
|
|
|
direction |= kUp;
|
|
|
|
}
|
2021-04-26 16:27:29 +03:00
|
|
|
|
|
|
|
if (input.mousePos.y > _nonScrollZone.bottom) {
|
|
|
|
// Handle TVD's weird behavior
|
|
|
|
if (_screenPosition.contains(input.mousePos)) {
|
2021-03-19 18:37:20 +02:00
|
|
|
direction |= kDown;
|
2021-04-26 16:27:29 +03:00
|
|
|
} else {
|
|
|
|
direction &= ~(kLeft | kRight);
|
2021-03-19 18:37:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-26 16:27:29 +03:00
|
|
|
// Handle diagonals
|
|
|
|
if (direction & (kLeft | kUp) ||
|
|
|
|
direction & (kLeft | kDown) ||
|
|
|
|
direction & (kRight | kUp) ||
|
|
|
|
direction & (kRight | kDown)) {
|
|
|
|
if (direction & _edgesMask) {
|
|
|
|
direction = 0;
|
|
|
|
}
|
2021-03-19 18:37:20 +02:00
|
|
|
}
|
2021-04-26 16:27:29 +03:00
|
|
|
|
|
|
|
direction &= ~_edgesMask;
|
2021-03-19 18:37:20 +02:00
|
|
|
}
|
|
|
|
|
2021-04-16 17:20:47 +03:00
|
|
|
// Set sticky cursor
|
|
|
|
if (input.input & (NancyInput::kLeftMouseButton | NancyInput::kRightMouseButton) && direction) {
|
|
|
|
if (_stickyCursorPos.x <= -1) {
|
|
|
|
_stickyCursorPos = input.mousePos;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
_stickyCursorPos.x = -1;
|
|
|
|
}
|
|
|
|
|
2021-03-19 18:37:20 +02:00
|
|
|
if (direction) {
|
2023-05-12 02:13:30 +03:00
|
|
|
if (direction & kLeft) {
|
2023-09-21 14:45:56 +03:00
|
|
|
if (summary.fastMoveTimeDelta == kInvertedNode) {
|
|
|
|
// Support nancy6+ inverted rotation scenes
|
2024-02-10 16:28:07 +01:00
|
|
|
g_nancy->_cursor->setCursorType(CursorManager::kInvertedRotateLeft);
|
2023-09-21 14:45:56 +03:00
|
|
|
} else {
|
2024-02-10 16:28:07 +01:00
|
|
|
g_nancy->_cursor->setCursorType(CursorManager::kRotateLeft);
|
2023-09-21 14:45:56 +03:00
|
|
|
}
|
2023-05-12 02:13:30 +03:00
|
|
|
} else if (direction & kRight) {
|
2023-09-21 14:45:56 +03:00
|
|
|
if (summary.fastMoveTimeDelta == kInvertedNode) {
|
|
|
|
// Support nancy6+ inverted rotation scenes
|
2024-02-10 16:28:07 +01:00
|
|
|
g_nancy->_cursor->setCursorType(CursorManager::kInvertedRotateRight);
|
2023-09-21 14:45:56 +03:00
|
|
|
} else {
|
2024-02-10 16:28:07 +01:00
|
|
|
g_nancy->_cursor->setCursorType(CursorManager::kRotateRight);
|
2023-09-21 14:45:56 +03:00
|
|
|
}
|
2023-08-08 00:43:51 +03:00
|
|
|
} else if (direction & kUp) {
|
2024-02-10 16:28:07 +01:00
|
|
|
g_nancy->_cursor->setCursorType(CursorManager::kMoveUp);
|
2023-08-08 00:43:51 +03:00
|
|
|
} else if (direction & kDown) {
|
2024-02-10 16:28:07 +01:00
|
|
|
g_nancy->_cursor->setCursorType(CursorManager::kMoveDown);
|
2023-05-12 02:13:30 +03:00
|
|
|
}
|
2021-03-19 18:37:20 +02:00
|
|
|
|
|
|
|
if (input.input & NancyInput::kRightMouseButton) {
|
|
|
|
direction |= kMoveFast;
|
2023-09-17 01:24:34 +03:00
|
|
|
} else if ((input.input & NancyInput::kLeftMouseButton) == 0 && _autoMove == false) {
|
2021-03-19 18:37:20 +02:00
|
|
|
direction = 0;
|
|
|
|
}
|
|
|
|
|
2024-01-09 22:50:36 +01:00
|
|
|
// Just pressed RMB down, cancel the timer (removes jank when auto move is on)
|
|
|
|
if (input.input & NancyInput::kRightMouseButtonDown) {
|
|
|
|
_nextMovementTime = 0;
|
|
|
|
}
|
|
|
|
|
2021-03-19 18:37:20 +02:00
|
|
|
// If we hover over an edge we don't want to click an element in the viewport underneath
|
|
|
|
// or to change the cursor, so we make the mouse input invalid
|
|
|
|
input.eatMouseInput();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!direction) {
|
|
|
|
if (input.input & NancyInput::kMoveUp) {
|
|
|
|
direction |= kUp;
|
|
|
|
}
|
|
|
|
if (input.input & NancyInput::kMoveDown) {
|
|
|
|
direction |= kDown;
|
|
|
|
}
|
|
|
|
if (input.input & NancyInput::kMoveLeft) {
|
|
|
|
direction |= kLeft;
|
|
|
|
}
|
|
|
|
if (input.input & NancyInput::kMoveRight) {
|
|
|
|
direction |= kRight;
|
|
|
|
}
|
|
|
|
if (input.input & NancyInput::kMoveFastModifier) {
|
|
|
|
direction |= kMoveFast;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Perform the movement
|
|
|
|
if (direction) {
|
|
|
|
Time movementDelta = NancySceneState.getMovementTimeDelta(direction & kMoveFast);
|
|
|
|
|
2023-04-03 23:05:21 +03:00
|
|
|
if (systemTime > _nextMovementTime) {
|
2021-03-19 18:37:20 +02:00
|
|
|
if (direction & kLeft) {
|
|
|
|
setNextFrame();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (direction & kRight) {
|
|
|
|
setPreviousFrame();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (direction & kUp) {
|
|
|
|
scrollUp(summary.verticalScrollDelta);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (direction & kDown) {
|
|
|
|
scrollDown(summary.verticalScrollDelta);
|
|
|
|
}
|
|
|
|
|
2023-04-03 23:05:21 +03:00
|
|
|
_nextMovementTime = systemTime + movementDelta;
|
2021-03-19 18:37:20 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_movementLastFrame = direction;
|
2021-02-19 02:17:41 +02:00
|
|
|
}
|
|
|
|
|
2023-09-17 18:23:44 +02:00
|
|
|
void Viewport::loadVideo(const Common::Path &filename, uint frameNr, uint verticalScroll, byte panningType, uint16 format, const Common::Path &palette) {
|
2021-03-19 18:37:20 +02:00
|
|
|
if (_decoder.isVideoLoaded()) {
|
|
|
|
_decoder.close();
|
|
|
|
}
|
2021-05-04 11:45:03 +03:00
|
|
|
|
2023-09-17 18:23:44 +02:00
|
|
|
if (!_decoder.loadFile(filename.append(".avf"))) {
|
|
|
|
error("Couldn't load video file %s", filename.toString().c_str());
|
2021-03-25 00:33:23 +02:00
|
|
|
}
|
2021-03-19 18:37:20 +02:00
|
|
|
|
|
|
|
_videoFormat = format;
|
|
|
|
|
|
|
|
enableEdges(kUp | kDown | kLeft | kRight);
|
2023-08-07 10:47:50 +02:00
|
|
|
|
2023-04-13 12:28:15 +03:00
|
|
|
_panningType = panningType;
|
2021-03-19 18:37:20 +02:00
|
|
|
|
|
|
|
setFrame(frameNr);
|
|
|
|
setVerticalScroll(verticalScroll);
|
|
|
|
|
2023-09-17 18:23:44 +02:00
|
|
|
if (!palette.empty()) {
|
2023-03-11 22:54:07 +02:00
|
|
|
GraphicsManager::loadSurfacePalette(_fullFrame, palette);
|
2023-03-07 00:13:02 +02:00
|
|
|
setPalette(palette);
|
2021-03-19 18:37:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
_movementLastFrame = 0;
|
|
|
|
_nextMovementTime = 0;
|
2021-02-19 02:17:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Viewport::setFrame(uint frameNr) {
|
2021-03-19 18:37:20 +02:00
|
|
|
assert(frameNr < _decoder.getFrameCount());
|
2021-02-19 02:17:41 +02:00
|
|
|
|
2021-03-19 18:37:20 +02:00
|
|
|
const Graphics::Surface *newFrame = _decoder.decodeFrame(frameNr);
|
2023-08-23 11:54:46 +03:00
|
|
|
_decoder.seek(frameNr); // Seek to take advantage of caching
|
2021-02-19 02:17:41 +02:00
|
|
|
|
2021-04-26 16:41:13 +03:00
|
|
|
// Format 1 uses quarter-size images, while format 2 uses full-size ones
|
|
|
|
// Videos in TVD are always upside-down
|
2023-03-25 19:08:20 +02:00
|
|
|
GraphicsManager::copyToManaged(*newFrame, _fullFrame, g_nancy->getGameType() == kGameTypeVampire, _videoFormat == kSmallVideoFormat);
|
2021-02-19 02:17:41 +02:00
|
|
|
|
2021-03-19 18:37:20 +02:00
|
|
|
_needsRedraw = true;
|
|
|
|
_currentFrame = frameNr;
|
2021-04-28 17:52:20 +03:00
|
|
|
|
2023-03-05 15:32:12 +02:00
|
|
|
if (_panningType == kPanLeftRight && !((_edgesMask & kLeft) && (_edgesMask & kRight))) {
|
2021-04-28 17:52:20 +03:00
|
|
|
if (_currentFrame == 0) {
|
|
|
|
disableEdges(kRight);
|
|
|
|
} else if (_currentFrame == getFrameCount() - 1) {
|
|
|
|
disableEdges(kLeft);
|
|
|
|
} else {
|
|
|
|
enableEdges(kLeft | kRight);
|
|
|
|
}
|
|
|
|
}
|
2021-02-19 02:17:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Viewport::setNextFrame() {
|
2021-04-17 00:37:48 +03:00
|
|
|
uint newFrame = getCurFrame() + 1 >= getFrameCount() ? 0 : getCurFrame() + 1;
|
|
|
|
if (newFrame != _currentFrame) {
|
|
|
|
setFrame(newFrame);
|
|
|
|
}
|
2021-02-19 02:17:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Viewport::setPreviousFrame() {
|
2021-04-17 00:37:48 +03:00
|
|
|
uint newFrame = (int)getCurFrame() - 1 < 0 ? getFrameCount() - 1 : getCurFrame() - 1;
|
|
|
|
if (newFrame != _currentFrame) {
|
|
|
|
setFrame(newFrame);
|
|
|
|
}
|
2021-02-19 02:17:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Viewport::setVerticalScroll(uint scroll) {
|
2021-07-04 17:24:30 -07:00
|
|
|
assert((int)scroll + _drawSurface.h <= _fullFrame.h);
|
2021-03-19 18:37:20 +02:00
|
|
|
|
|
|
|
Common::Rect sourceBounds = _screenPosition;
|
2021-04-13 23:06:10 +03:00
|
|
|
sourceBounds.moveTo(0, scroll);
|
2021-03-19 18:37:20 +02:00
|
|
|
_drawSurface.create(_fullFrame, sourceBounds);
|
|
|
|
_needsRedraw = true;
|
|
|
|
|
2023-03-25 14:38:55 +02:00
|
|
|
if (getMaxScroll() > 0) {
|
|
|
|
if (scroll == getMaxScroll()) {
|
|
|
|
disableEdges(kDown);
|
|
|
|
enableEdges(kUp);
|
|
|
|
} else if (scroll == 0) {
|
|
|
|
disableEdges(kUp);
|
|
|
|
enableEdges(kDown);
|
|
|
|
} else {
|
|
|
|
enableEdges(kUp | kDown);
|
|
|
|
}
|
2021-03-19 18:37:20 +02:00
|
|
|
} else {
|
2023-03-25 14:38:55 +02:00
|
|
|
disableEdges(kUp | kDown);
|
2021-03-19 18:37:20 +02:00
|
|
|
}
|
2021-02-19 02:17:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Viewport::scrollUp(uint delta) {
|
2021-04-17 00:37:48 +03:00
|
|
|
if (_drawSurface.getOffsetFromOwner().y != 0) {
|
|
|
|
setVerticalScroll(_drawSurface.getOffsetFromOwner().y < (int16)delta ? 0 : _drawSurface.getOffsetFromOwner().y - delta);
|
|
|
|
}
|
2021-02-19 02:17:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Viewport::scrollDown(uint delta) {
|
2021-04-17 00:37:48 +03:00
|
|
|
if (_drawSurface.getOffsetFromOwner().y != getMaxScroll()) {
|
|
|
|
setVerticalScroll(_drawSurface.getOffsetFromOwner().y + delta > getMaxScroll() ? getMaxScroll() : _drawSurface.getOffsetFromOwner().y + delta);
|
|
|
|
}
|
2021-02-19 02:17:41 +02:00
|
|
|
}
|
|
|
|
|
2021-04-26 19:41:04 +03:00
|
|
|
uint16 Viewport::getMaxScroll() const {
|
2021-05-04 11:45:03 +03:00
|
|
|
return _fullFrame.h - _drawSurface.h - (g_nancy->getGameType() == kGameTypeVampire ? 1 : 0);
|
2021-04-26 19:41:04 +03:00
|
|
|
}
|
|
|
|
|
2021-02-19 02:17:41 +02:00
|
|
|
// Convert a viewport-space rectangle to screen coordinates
|
|
|
|
Common::Rect Viewport::convertViewportToScreen(const Common::Rect &viewportRect) const {
|
2021-03-19 18:37:20 +02:00
|
|
|
Common::Rect ret = convertToScreen(viewportRect);
|
|
|
|
ret.translate(0, -getCurVerticalScroll());
|
|
|
|
ret.clip(_screenPosition);
|
|
|
|
return ret;
|
2021-02-19 02:17:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Convert a screen-space coordinate to viewport coordinates
|
|
|
|
Common::Rect Viewport::convertScreenToViewport(const Common::Rect &viewportRect) const {
|
2021-03-19 18:37:20 +02:00
|
|
|
Common::Rect ret = convertToLocal(viewportRect);
|
|
|
|
ret.translate(0, getCurVerticalScroll());
|
|
|
|
return ret;
|
2021-02-19 02:17:41 +02:00
|
|
|
}
|
|
|
|
|
2021-02-19 18:46:33 +02:00
|
|
|
void Viewport::setEdgesSize(uint16 upSize, uint16 downSize, uint16 leftSize, uint16 rightSize) {
|
2021-04-26 16:27:29 +03:00
|
|
|
_nonScrollZone = _screenPosition;
|
|
|
|
uint offset = g_nancy->getGameType() == kGameTypeVampire ? 0 : 1;
|
|
|
|
_nonScrollZone.top += upSize;
|
|
|
|
_nonScrollZone.left += leftSize;
|
|
|
|
_nonScrollZone.bottom -= downSize + offset;
|
|
|
|
_nonScrollZone.right -= rightSize + offset;
|
2021-02-19 18:46:33 +02:00
|
|
|
}
|
2021-02-19 02:17:41 +02:00
|
|
|
|
2021-02-19 18:46:33 +02:00
|
|
|
void Viewport::disableEdges(byte edges) {
|
2021-03-19 18:37:20 +02:00
|
|
|
_edgesMask |= edges;
|
2021-02-19 18:46:33 +02:00
|
|
|
}
|
2021-02-19 02:17:41 +02:00
|
|
|
|
2021-02-19 18:46:33 +02:00
|
|
|
void Viewport::enableEdges(byte edges) {
|
2021-03-19 18:37:20 +02:00
|
|
|
_edgesMask &= ~edges;
|
2021-02-19 02:17:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
} // End of namespace UI
|
2021-03-13 23:19:32 +02:00
|
|
|
} // End of namespace Nancy
|