scummvm/engines/cryomni3d/versailles/toolbar.cpp
2021-12-26 18:48:43 +01:00

602 lines
18 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "common/system.h"
#include "cryomni3d/cryomni3d.h"
#include "cryomni3d/versailles/toolbar.h"
namespace CryOmni3D {
namespace Versailles {
void Toolbar::init(const Sprites *sprites, FontManager *fontManager,
const Common::Array<Common::String> *messages, Inventory *inventory,
CryOmni3DEngine *engine) {
_sprites = sprites;
_fontManager = fontManager;
_messages = messages;
_inventory = inventory;
_engine = engine;
_bgSurface.create(640, 60, Graphics::PixelFormat::createFormatCLUT8());
_destSurface.create(640, 60, Graphics::PixelFormat::createFormatCLUT8());
// Inventory
addZone(51, 56, Common::Point(211, 8), &Toolbar::callbackInventory<0>);
addZone(51, 56, Common::Point(258, 8), &Toolbar::callbackInventory<1>);
addZone(51, 56, Common::Point(305, 8), &Toolbar::callbackInventory<2>);
addZone(51, 56, Common::Point(352, 8), &Toolbar::callbackInventory<3>);
addZone(51, 56, Common::Point(399, 8), &Toolbar::callbackInventory<4>);
addZone(51, 56, Common::Point(446, 8), &Toolbar::callbackInventory<5>);
addZone(51, 56, Common::Point(493, 8), &Toolbar::callbackInventory<6>);
addZone(51, 56, Common::Point(540, 8), &Toolbar::callbackInventory<7>);
// Documentation
const Graphics::Cursor &cursorDoc = _sprites->getCursor(133);
Common::Point docPos(627 - cursorDoc.getWidth(), 42 - cursorDoc.getHeight());
addZone(133, 137, docPos, &Toolbar::callbackDocumentation);
// Options
const Graphics::Cursor &cursorOpt = _sprites->getCursor(225);
Common::Point optPos(0, 60 - cursorOpt.getHeight());
addZone(225, 225, optPos, &Toolbar::callbackOptions);
// Previous or next
addZone(183, uint16(-1), Common::Point(190, 18), &Toolbar::callbackInventoryPrev);
addZone(240, uint16(-1), Common::Point(574, 18), &Toolbar::callbackInventoryNext);
// View
addZone(142, uint16(-1), Common::Point(158, 12), &Toolbar::callbackViewObject);
}
Toolbar::~Toolbar() {
_bgSurface.free();
_destSurface.free();
}
void Toolbar::inventoryChanged(uint newPosition) {
if (newPosition != uint(-1) && newPosition > _inventoryOffset) {
_inventoryOffset = newPosition - 7;
}
// Refresh
updateZones();
}
void Toolbar::addZone(uint16 cursorMainId, uint16 cursorSecondaryId, Common::Point position,
ZoneCallback callback) {
const Graphics::Cursor &cursorMain = _sprites->getCursor(cursorMainId);
Common::Rect rct(cursorMain.getWidth(), cursorMain.getHeight());
rct.moveTo(position);
// By default it's the secondary image
Zone zone = { rct, cursorMainId, cursorSecondaryId, callback, true, false };
_zones.push_back(zone);
}
Common::Array<Toolbar::Zone>::const_iterator Toolbar::hitTestZones(const Common::Point &mousePos)
const {
Common::Array<Zone>::const_iterator it;
for (it = _zones.begin(); it != _zones.end(); it++) {
if (!it->hidden && it->rect.contains(mousePos) && it->callback) {
break;
}
}
return it;
}
uint Toolbar::captureEvent(const Common::Point &mousePos, uint dragStatus) {
uint result = 0;
Common::Array<Zone>::const_iterator it = hitTestZones(mousePos);
if (it != _zones.end()) {
result = (this->*(it->callback))(dragStatus);
}
return result;
}
void Toolbar::updateZones() {
_zones[8].secondary = !_engine->hasPlaceDocumentation();
Inventory::const_iterator inventoryIt, inventorySelectedIt;
if (!_inventoryEnabled) {
_inventoryMaxOffset = 0;
_inventoryOffset = 0;
_zones[10].secondary = true;
_zones[11].secondary = true;
inventoryIt = _inventory->end();
inventorySelectedIt = _inventory->end();
} else {
_inventoryMaxOffset = 0;
// Find an object in inventory after the 8 first
for (inventoryIt = _inventory->begin() + 8; inventoryIt != _inventory->end(); inventoryIt++) {
if (*inventoryIt != nullptr) {
_inventoryMaxOffset = (inventoryIt - _inventory->begin()) - 7;
}
}
_zones[10].secondary = !_inventoryMaxOffset;
_zones[11].secondary = !_inventoryMaxOffset;
if (_inventoryOffset > _inventoryMaxOffset) {
// Clamp inventory offset to its max
_inventoryOffset = _inventoryMaxOffset;
}
inventoryIt = _inventory->begin() + _inventoryOffset;
inventorySelectedIt = _inventory->begin() + _inventorySelected;
}
// Inventory zones are from 0 to 7
for (Common::Array<Zone>::iterator zoneIt = _zones.begin(); zoneIt != _zones.begin() + 8;
zoneIt++, inventoryIt++) {
if (!_inventoryEnabled) {
zoneIt->hidden = true;
zoneIt->imageMain = 0;
zoneIt->imageSecondary = 0;
zoneIt->secondary = false;
} else if (inventoryIt >= _inventory->end() || *inventoryIt == nullptr) {
// Nothing in inventory at this position
zoneIt->hidden = false;
zoneIt->imageMain = 51;
zoneIt->imageSecondary = 56;
zoneIt->secondary = true;
} else {
// Setup inventory icon
zoneIt->hidden = false;
zoneIt->imageMain = (*inventoryIt)->idCA();
zoneIt->imageSecondary = (*inventoryIt)->idCl();
zoneIt->secondary = (inventorySelectedIt != inventoryIt);
}
}
}
uint Toolbar::callbackInventory(uint invId, uint dragStatus) {
if (!_inventoryEnabled) {
return 0;
}
invId += _inventoryOffset;
Object *obj = nullptr;
if (invId < _inventory->size()) {
obj = (*_inventory)[invId];
}
if (obj == nullptr) {
return 0;
}
if (!obj->valid()) {
return 0;
}
switch (dragStatus) {
case kDragStatus_Pressed:
_inventorySelected = invId;
_engine->setCursor(181);
_zones[12].secondary = (obj->viewCallback() == nullptr);
_inventoryButtonDragging = true;
return 1;
case kDragStatus_Dragging:
if (_inventorySelected == invId) {
return 0;
}
_inventorySelected = invId;
_zones[12].secondary = (obj->viewCallback() == nullptr);
_inventoryButtonDragging = true;
return 1;
case kDragStatus_Finished:
_engine->setCursor(obj->idSl());
_inventory->setSelectedObject(obj);
_inventorySelected = invId;
return 1;
default:
return 0;
}
}
uint Toolbar::callbackInventoryPrev(uint dragStatus) {
if (!_inventoryEnabled) {
return 0;
}
if (dragStatus == kDragStatus_Pressed && _inventoryOffset > 0) {
// Restart auto repeat only if there could be something
_engine->setAutoRepeatClick(150);
_inventoryOffset--;
return 1;
}
// In any other case we didn't do anything
return 0;
}
uint Toolbar::callbackInventoryNext(uint dragStatus) {
if (!_inventoryEnabled) {
return 0;
}
if (dragStatus == kDragStatus_Pressed && _inventoryOffset < _inventoryMaxOffset) {
_engine->setAutoRepeatClick(150);
_inventoryOffset++;
return 1;
}
// In any other case we didn't do anything
return 0;
}
uint Toolbar::callbackViewObject(uint dragStatus) {
if (!_inventoryEnabled) {
return 0;
}
_mouseInViewObject = true;
if (_inventorySelected == uint(-1)) {
// Nothing selected in toolbar
return 0;
}
Inventory::const_iterator inventorySelectedIt = _inventory->begin() + _inventorySelected;
Object *selectedObject = *inventorySelectedIt;
if (selectedObject == nullptr || selectedObject->viewCallback() == nullptr) {
// Nothing to view, the sprite isn't even displayed
return 0;
}
switch (dragStatus) {
case kDragStatus_NoDrag:
_backupSelectedObject = selectedObject;
_engine->setCursor(181);
return 0;
case kDragStatus_Pressed:
case kDragStatus_Dragging:
return 1;
case kDragStatus_Finished:
// Just clicked
_engine->showMouse(false);
(*selectedObject->viewCallback())();
_engine->showMouse(true);
_parentMustRedraw = true;
_shortExit = true;
return 1;
default:
return 0;
}
}
uint Toolbar::callbackOptions(uint dragStatus) {
_mouseInOptions = true;
switch (dragStatus) {
case kDragStatus_NoDrag:
_backupSelectedObject = _inventory->selectedObject();
_engine->setCursor(181);
return 0;
case kDragStatus_Pressed:
case kDragStatus_Dragging:
// Nothing to do, we wait release
return 0;
case kDragStatus_Finished:
// Just clicked
_engine->displayOptions();
_parentMustRedraw = true;
_shortExit = true;
_engine->setMousePos(Common::Point(320, 240)); // Center of screen
// Displaying options hides the mouse
_engine->showMouse(true);
return 0;
default:
return 0;
}
}
uint Toolbar::callbackDocumentation(uint dragStatus) {
_mouseInOptions = true;
switch (dragStatus) {
case kDragStatus_NoDrag:
case kDragStatus_Pressed:
case kDragStatus_Dragging:
// Nothing to do, we wait release
return 0;
case kDragStatus_Finished:
// Just clicked
if (_engine->displayPlaceDocumentation()) {
_parentMustRedraw = true;
_shortExit = true;
_engine->setMousePos(Common::Point(320, 240)); // Center of screen
}
return 0;
default:
return 0;
}
}
void Toolbar::drawToolbar(const Graphics::Surface *original) {
if (_position > 60) {
_position = 60;
}
if (_position != 0) {
// Not entirely drawn, we must copy a part of the original image
Common::Rect rct(0, 420, 640, 420 + _position);
_destSurface.copyRectToSurface(*original, 0, 0, rct);
}
if (_position == 60) {
// Entirely hidden, just stop there, we have nothing to draw
return;
}
// Not entirely hidden, we must display the transparent background prepared for us
Common::Rect rct(0, _position, 640, 60);
_destSurface.copyRectToSurface(_bgSurface, 0, _position, rct);
// Now draw the various zones on the surface
for (Common::Array<Zone>::const_iterator it = _zones.begin(); it != _zones.end(); it++) {
if (it->hidden) {
continue;
}
uint16 spriteId = it->secondary ? it->imageSecondary : it->imageMain;
if (spriteId == uint16(-1)) {
continue;
}
Common::Rect dst = it->rect;
dst.translate(0, _position);
// Clip the rectangle to fit inside the surface
dst.clip(Common::Rect(_destSurface.w, _destSurface.h));
if (dst.isEmpty()) {
continue;
}
const Graphics::Surface &sprite = _sprites->getSurface(spriteId);
_destSurface.transBlitFrom(sprite, Common::Rect(dst.width(), dst.height()), dst,
_sprites->getKeyColor(spriteId));
}
// And now draw the object description if needed
if (_inventoryEnabled && _inventoryHovered != uint(-1)) {
Object *obj = (*_inventory)[_inventoryHovered];
uint zoneId = _inventoryHovered - _inventoryOffset;
if (zoneId >= 8) {
// The object is hidden: huh?
return;
}
_fontManager->setSurface(&_destSurface);
_fontManager->setForeColor(243);
_fontManager->setCurrentFont(5);
_fontManager->setTransparentBackground(true);
const Common::String &objName = (*_messages)[obj->idOBJ()];
uint x = 195 - _fontManager->getStrWidth(objName);
uint startX = _zones[zoneId].rect.left + kTextOffset;
_fontManager->displayStr(x, 38 + _position, objName);
_destSurface.hLine(x, 54 + _position, startX - 1, 243); // minus 1 because hLine draws inclusive
_destSurface.vLine(startX, 42 + _position, 54 + _position, 243);
}
}
bool Toolbar::displayToolbar(const Graphics::Surface *original) {
/**
* In game there are 2 functions to handle toolbar: one in warp and one in fixed images
* This one is the warp one and fixed images have a more asynchronous one during pop-up/down phases
* Let's make it simple for now
*/
// WORKAROUND: Set cursor here to be more consistent: it's thumb cursor just before showing until just after showed
_engine->setCursor(181);
_parentMustRedraw = false;
_shortExit = false;
// Prepare the background of the toolbar by making it translucent
// Get the lowest part of the image
const Graphics::Surface subset = original->getSubArea(Common::Rect(0, original->h - _bgSurface.h,
_bgSurface.w, original->h));
_engine->makeTranslucent(_bgSurface, subset);
// WORKAROUND: Reset the inventory status at init to let sprites highlighted until toolbar is hidden
_inventorySelected = uint(-1);
_inventoryHovered = uint(-1);
_zones[12].secondary = true;
updateZones();
for (_position = 60; _position > 0; _position--) {
// Make the toolbar go up
drawToolbar(original);
g_system->copyRectToScreen(_destSurface.getPixels(), _destSurface.pitch, 0,
original->h - _destSurface.h, _destSurface.w, _destSurface.h);
g_system->updateScreen();
// Slow down animation
g_system->delayMillis(10);
_engine->pollEvents();
if (_engine->shouldAbort()) {
return false;
}
}
// Flush events
_engine->clearKeys();
_engine->waitMouseRelease();
handleToolbarEvents(original);
if (_engine->shouldAbort()) {
return false;
}
if (_shortExit) {
return _parentMustRedraw;
}
for (_position = 0; _position <= 60; _position++) {
// Make the toolbar go up
drawToolbar(original);
g_system->copyRectToScreen(_destSurface.getPixels(), _destSurface.pitch, 0,
original->h - _destSurface.h, _destSurface.w, _destSurface.h);
g_system->updateScreen();
// Slow down animation
g_system->delayMillis(10);
_engine->pollEvents();
if (_engine->shouldAbort()) {
return false;
}
}
return _parentMustRedraw;
}
void Toolbar::handleToolbarEvents(const Graphics::Surface *original) {
bool mouseInsideToolbar;
bool exitToolbar = false;
bool redrawToolbar;
// Don't have anything hovered for now
_inventoryHovered = uint(-1);
_inventorySelected = uint(-1);
_inventory->setSelectedObject(nullptr);
_backupSelectedObject = nullptr;
// Refresh zones because we erased selected object
updateZones();
// No need of original surface because the toolbar is fully displayed
drawToolbar(original);
g_system->copyRectToScreen(_destSurface.getPixels(), _destSurface.pitch, 0,
original->h - _destSurface.h, _destSurface.w, _destSurface.h);
g_system->updateScreen();
_engine->setCursor(181);
mouseInsideToolbar = (_engine->getMousePos().y > 388);
while (!exitToolbar) {
_mouseInOptions = false;
_mouseInViewObject = false;
_engine->pollEvents();
if (_engine->shouldAbort()) {
exitToolbar = true;
break;
}
redrawToolbar = false;
if (_engine->checkKeysPressed(2, Common::KEYCODE_ESCAPE, Common::KEYCODE_SPACE) ||
_engine->getCurrentMouseButton() == 2) {
_engine->waitMouseRelease();
exitToolbar = true;
break;
}
Common::Point mousePosInToolbar = _engine->getMousePos();
mousePosInToolbar -= Common::Point(0, 420);
if (captureEvent(mousePosInToolbar, _engine->getDragStatus())) {
// Something has changed with the zones handling, update zones
updateZones();
redrawToolbar = true;
} else if (_engine->getDragStatus() == kDragStatus_Pressed) {
// A click happened and wasn't handled, deselect object
_inventorySelected = uint(-1);
_inventory->setSelectedObject(nullptr);
_engine->setCursor(181);
// Reset view object
_zones[12].secondary = true;
updateZones();
redrawToolbar = true;
}
if (!mouseInsideToolbar) {
mouseInsideToolbar = (_engine->getMousePos().y > 388);
} else if (_engine->getMousePos().y <= 388) {
// mouseInsideToolbar is true and the mouse is outside the toolbar
exitToolbar = true;
break;
}
if (_engine->getCurrentMouseButton() == 1) {
// When the mouse button is down, nothing is selected
// It's selected on release
_inventory->setSelectedObject(nullptr);
}
if (_backupSelectedObject != nullptr && !(_mouseInOptions || _mouseInViewObject) &&
!_engine->getCurrentMouseButton()) {
_inventory->setSelectedObject(_backupSelectedObject);
_engine->setCursor(_backupSelectedObject->idSl());
_backupSelectedObject = nullptr;
}
// Hover the inventory objects
if (_inventory->selectedObject() == nullptr /* || _inventoryButtonDragging */) {
// The 2nd above condition is maybe useless because when the mouse button is down the selected object is always null
bool shouldHover = false;
Common::Array<Zone>::const_iterator zoneIt = hitTestZones(mousePosInToolbar);
uint zoneId = zoneIt - _zones.begin();
uint inventoryId = zoneId + _inventoryOffset;
if (zoneId < 8 && inventoryId < _inventory->size() && (*_inventory)[inventoryId] != nullptr) {
// It's the inventory
shouldHover = true;
if (_inventoryHovered != inventoryId && (*_inventory)[inventoryId]->valid()) {
// It's not the one currently hovered and it's a valid object
_inventoryHovered = inventoryId;
redrawToolbar = true;
}
}
if (!shouldHover && _inventoryHovered != uint(-1) && !_mouseInViewObject) {
// Remove hovering
_inventoryHovered = uint(-1);
_inventorySelected = uint(-1);
updateZones();
if (!_inventory->selectedObject()) {
// Reset back the cursor if nothing is selected
_engine->setCursor(181);
}
// Remove view
_zones[12].secondary = true;
redrawToolbar = true;
}
_inventoryButtonDragging = false;
}
if (_parentMustRedraw) {
break;
}
if (redrawToolbar) {
drawToolbar(original);
g_system->copyRectToScreen(_destSurface.getPixels(), _destSurface.pitch, 0,
original->h - _destSurface.h, _destSurface.w, _destSurface.h);
}
g_system->updateScreen();
g_system->delayMillis(10);
}
// Hide description when finished and selected object
// WORKAROUND: moved to the start to keep the selected object hilighted until the toolbar disappearance
}
} // End of namespace Versailles
} // End of namespace CryOmni3D