/* 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. * * 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. * 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 * along with this program. If not, see . * */ #include "common/system.h" #include "engines/nancy/nancy.h" #include "engines/nancy/graphics.h" #include "engines/nancy/cursor.h" #include "engines/nancy/input.h" #include "engines/nancy/util.h" #include "engines/nancy/state/scene.h" #include "engines/nancy/ui/viewport.h" #include "common/config-manager.h" namespace Nancy { namespace UI { // does NOT put the object in a valid state until loadVideo is called void Viewport::init() { auto *bootSummary = GetEngineData(BSUM); assert(bootSummary); auto *viewportData = GetEngineData(VIEW); assert(viewportData); moveTo(viewportData->screenPosition); setEdgesSize( bootSummary->verticalEdgesSize, bootSummary->verticalEdgesSize, bootSummary->horizontalEdgesSize, bootSummary->horizontalEdgesSize); RenderObject::init(); } void Viewport::handleInput(NancyInput &input) { const Nancy::State::Scene::SceneSummary &summary = NancySceneState.getSceneSummary(); Time systemTime = g_system->getMillis(); byte direction = 0; 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()); } // Make cursor sticky when scrolling the viewport if ( g_nancy->getGameType() != kGameTypeVampire && input.input & (NancyInput::kLeftMouseButton | NancyInput::kRightMouseButton) && _stickyCursorPos.x > -1) { g_nancy->_cursor->warpCursor(_stickyCursorPos); input.mousePos = _stickyCursorPos; } Common::Rect viewportActiveZone; if (g_nancy->getGameType() == kGameTypeVampire) { viewportActiveZone = g_nancy->_graphics->getScreen()->getBounds(); viewportActiveZone.bottom = _screenPosition.bottom; } else { viewportActiveZone = _screenPosition; } if (viewportActiveZone.contains(input.mousePos)) { g_nancy->_cursor->setCursorType(CursorManager::kNormal); if (input.mousePos.x < _nonScrollZone.left) { direction |= kLeft; } if (input.mousePos.x > _nonScrollZone.right) { direction |= kRight; } if (input.mousePos.y < _nonScrollZone.top) { direction |= kUp; } if (input.mousePos.y > _nonScrollZone.bottom) { // Handle TVD's weird behavior if (_screenPosition.contains(input.mousePos)) { direction |= kDown; } else { direction &= ~(kLeft | kRight); } } // Handle diagonals if (direction & (kLeft | kUp) || direction & (kLeft | kDown) || direction & (kRight | kUp) || direction & (kRight | kDown)) { if (direction & _edgesMask) { direction = 0; } } direction &= ~_edgesMask; } // Set sticky cursor if (input.input & (NancyInput::kLeftMouseButton | NancyInput::kRightMouseButton) && direction) { if (_stickyCursorPos.x <= -1) { _stickyCursorPos = input.mousePos; } } else { _stickyCursorPos.x = -1; } if (direction) { if (direction & kLeft) { if (summary.fastMoveTimeDelta == kInvertedNode) { // Support nancy6+ inverted rotation scenes g_nancy->_cursor->setCursorType(CursorManager::kInvertedRotateLeft); } else { g_nancy->_cursor->setCursorType(CursorManager::kRotateLeft); } } else if (direction & kRight) { if (summary.fastMoveTimeDelta == kInvertedNode) { // Support nancy6+ inverted rotation scenes g_nancy->_cursor->setCursorType(CursorManager::kInvertedRotateRight); } else { g_nancy->_cursor->setCursorType(CursorManager::kRotateRight); } } else if (direction & kUp) { g_nancy->_cursor->setCursorType(CursorManager::kMoveUp); } else if (direction & kDown) { g_nancy->_cursor->setCursorType(CursorManager::kMoveDown); } if (input.input & NancyInput::kRightMouseButton) { direction |= kMoveFast; } else if ((input.input & NancyInput::kLeftMouseButton) == 0 && _autoMove == false) { direction = 0; } // Just pressed RMB down, cancel the timer (removes jank when auto move is on) if (input.input & NancyInput::kRightMouseButtonDown) { _nextMovementTime = 0; } // 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); if (systemTime > _nextMovementTime) { if (direction & kLeft) { setNextFrame(); } if (direction & kRight) { setPreviousFrame(); } if (direction & kUp) { scrollUp(summary.verticalScrollDelta); } if (direction & kDown) { scrollDown(summary.verticalScrollDelta); } _nextMovementTime = systemTime + movementDelta; } } _movementLastFrame = direction; } void Viewport::loadVideo(const Common::Path &filename, uint frameNr, uint verticalScroll, byte panningType, uint16 format, const Common::Path &palette) { if (_decoder.isVideoLoaded()) { _decoder.close(); } if (!_decoder.loadFile(filename.append(".avf"))) { error("Couldn't load video file %s", filename.toString().c_str()); } _videoFormat = format; enableEdges(kUp | kDown | kLeft | kRight); _panningType = panningType; setFrame(frameNr); setVerticalScroll(verticalScroll); if (!palette.empty()) { GraphicsManager::loadSurfacePalette(_fullFrame, palette); setPalette(palette); } _movementLastFrame = 0; _nextMovementTime = 0; } void Viewport::setFrame(uint frameNr) { assert(frameNr < _decoder.getFrameCount()); const Graphics::Surface *newFrame = _decoder.decodeFrame(frameNr); _decoder.seek(frameNr); // Seek to take advantage of caching // Format 1 uses quarter-size images, while format 2 uses full-size ones // Videos in TVD are always upside-down GraphicsManager::copyToManaged(*newFrame, _fullFrame, g_nancy->getGameType() == kGameTypeVampire, _videoFormat == kSmallVideoFormat); _needsRedraw = true; _currentFrame = frameNr; if (_panningType == kPanLeftRight && !((_edgesMask & kLeft) && (_edgesMask & kRight))) { if (_currentFrame == 0) { disableEdges(kRight); } else if (_currentFrame == getFrameCount() - 1) { disableEdges(kLeft); } else { enableEdges(kLeft | kRight); } } } void Viewport::setNextFrame() { uint newFrame = getCurFrame() + 1 >= getFrameCount() ? 0 : getCurFrame() + 1; if (newFrame != _currentFrame) { setFrame(newFrame); } } void Viewport::setPreviousFrame() { uint newFrame = (int)getCurFrame() - 1 < 0 ? getFrameCount() - 1 : getCurFrame() - 1; if (newFrame != _currentFrame) { setFrame(newFrame); } } void Viewport::setVerticalScroll(uint scroll) { assert((int)scroll + _drawSurface.h <= _fullFrame.h); Common::Rect sourceBounds = _screenPosition; sourceBounds.moveTo(0, scroll); _drawSurface.create(_fullFrame, sourceBounds); _needsRedraw = true; if (getMaxScroll() > 0) { if (scroll == getMaxScroll()) { disableEdges(kDown); enableEdges(kUp); } else if (scroll == 0) { disableEdges(kUp); enableEdges(kDown); } else { enableEdges(kUp | kDown); } } else { disableEdges(kUp | kDown); } } void Viewport::scrollUp(uint delta) { if (_drawSurface.getOffsetFromOwner().y != 0) { setVerticalScroll(_drawSurface.getOffsetFromOwner().y < (int16)delta ? 0 : _drawSurface.getOffsetFromOwner().y - delta); } } void Viewport::scrollDown(uint delta) { if (_drawSurface.getOffsetFromOwner().y != getMaxScroll()) { setVerticalScroll(_drawSurface.getOffsetFromOwner().y + delta > getMaxScroll() ? getMaxScroll() : _drawSurface.getOffsetFromOwner().y + delta); } } uint16 Viewport::getMaxScroll() const { return _fullFrame.h - _drawSurface.h - (g_nancy->getGameType() == kGameTypeVampire ? 1 : 0); } // Convert a viewport-space rectangle to screen coordinates Common::Rect Viewport::convertViewportToScreen(const Common::Rect &viewportRect) const { Common::Rect ret = convertToScreen(viewportRect); ret.translate(0, -getCurVerticalScroll()); ret.clip(_screenPosition); return ret; } // Convert a screen-space coordinate to viewport coordinates Common::Rect Viewport::convertScreenToViewport(const Common::Rect &viewportRect) const { Common::Rect ret = convertToLocal(viewportRect); ret.translate(0, getCurVerticalScroll()); return ret; } void Viewport::setEdgesSize(uint16 upSize, uint16 downSize, uint16 leftSize, uint16 rightSize) { _nonScrollZone = _screenPosition; uint offset = g_nancy->getGameType() == kGameTypeVampire ? 0 : 1; _nonScrollZone.top += upSize; _nonScrollZone.left += leftSize; _nonScrollZone.bottom -= downSize + offset; _nonScrollZone.right -= rightSize + offset; } void Viewport::disableEdges(byte edges) { _edgesMask |= edges; } void Viewport::enableEdges(byte edges) { _edgesMask &= ~edges; } } // End of namespace UI } // End of namespace Nancy