/* 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ /* * Based on * WebVenture (c) 2010, Sean Kasun * https://github.com/mrkite/webventure, http://seancode.com/webventure/ * * Used with explicit permission from the author */ #include "common/file.h" #include "common/system.h" #include "common/debug-channels.h" #include "common/debug.h" #include "image/bmp.h" #include "graphics/macgui/macfontmanager.h" #include "macventure/gui.h" #include "macventure/dialog.h" namespace MacVenture { enum { kCursorWidth = 2, kCursorHeight = 2 }; enum { kExitButtonWidth = 10, kExitButtonHeight = 10 }; enum { kMenuHighLevel = -1, kMenuAbout = 0, kMenuFile = 1, kMenuEdit = 2, kMenuSpecial = 3 }; enum { kCommandNum = 8 }; enum { kDragThreshold = 5 }; const bool kLoadStaticMenus = true; static const Graphics::MacMenuData menuSubItems[] = { { kMenuHighLevel, "File", 0, 0, false }, { kMenuHighLevel, "Edit", 0, 0, false }, { kMenuHighLevel, "Special", 0, 0, false }, { kMenuHighLevel, "Font", 0, 0, false }, { kMenuHighLevel, "FontSize", 0, 0, false }, //{ kMenuAbout, "About", kMenuActionAbout, 0, true}, { kMenuFile, "New", kMenuActionNew, 0, true }, { kMenuFile, NULL, 0, 0, false }, { kMenuFile, "Open...", kMenuActionOpen, 0, true }, { kMenuFile, "Save", kMenuActionSave, 0, true }, { kMenuFile, "Save as...", kMenuActionSaveAs, 0, true }, { kMenuFile, NULL, 0, 0, false }, { kMenuFile, "Quit", kMenuActionQuit, 0, true }, { kMenuEdit, "Undo", kMenuActionUndo, 'Z', false }, { kMenuEdit, NULL, 0, 0, false }, { kMenuEdit, "Cut", kMenuActionCut, 'K', false }, { kMenuEdit, "Copy", kMenuActionCopy, 'C', false }, { kMenuEdit, "Paste", kMenuActionPaste, 'V', false }, { kMenuEdit, "Clear", kMenuActionClear, 'B', false }, { kMenuSpecial, "Clean Up", kMenuActionCleanUp, 0, false }, { kMenuSpecial, "Mess Up", kMenuActionMessUp, 0, false }, { 0, NULL, 0, 0, false } }; bool commandsWindowCallback(Graphics::WindowClick, Common::Event &event, void *gui); bool mainGameWindowCallback(Graphics::WindowClick, Common::Event &event, void *gui); bool outConsoleWindowCallback(Graphics::WindowClick, Common::Event &event, void *gui); bool selfWindowCallback(Graphics::WindowClick, Common::Event &event, void *gui); bool exitsWindowCallback(Graphics::WindowClick, Common::Event &event, void *gui); bool diplomaWindowCallback(Graphics::WindowClick, Common::Event &event, void *gui); bool inventoryWindowCallback(Graphics::WindowClick, Common::Event &event, void *gui); void menuCommandsCallback(int action, Common::String &text, void *data); Gui::Gui(MacVentureEngine *engine, Common::MacResManager *resman) { _engine = engine; _resourceManager = resman; _windowData = NULL; _controlData = NULL; _draggedObj.id = 0; _draggedObj.pos = Common::Point(0, 0); _dialog = NULL; _cursor = new Cursor(this); _consoleText = new ConsoleText(this); _graphics = NULL; initGUI(); } Gui::~Gui() { if (_windowData) delete _windowData; if (_controlData) delete _controlData; if (_exitsData) delete _exitsData; if (_cursor) delete _cursor; if (_consoleText) delete _consoleText; if (_dialog) delete _dialog; clearAssets(); if (_graphics) delete _graphics; } void Gui::initGUI() { _screen.create(kScreenWidth, kScreenHeight, Graphics::PixelFormat::createFormatCLUT8()); _wm.setScreen(&_screen); // Menu _menu = _wm.addMenu(); if (!loadMenus()) error("GUI: Could not load menus"); _menu->setCommandsCallback(menuCommandsCallback, this); _menu->calcDimensions(); loadGraphics(); if (!loadWindows()) error("GUI: Could not load windows"); initWindows(); assignObjReferences(); if (!loadControls()) error("GUI: Could not load controls"); draw(); } void Gui::reloadInternals() { clearAssets(); loadGraphics(); } void Gui::draw() { // Will be performance-improved after the milestone _wm.setFullRefresh(true); drawWindows(); _wm.draw(); drawDraggedObject(); drawDialog(); // TODO: When window titles with custom borders are in MacGui, this should be used. //drawWindowTitle(kMainGameWindow, _mainGameWindow->getSurface()); } void Gui::drawMenu() { _menu->draw(&_screen); } void Gui::drawTitle() { warning("drawTitle hasn't been tested yet"); } void Gui::clearControls() { if (!_controlData) return; Common::Array::iterator it = _controlData->begin(); for (; it != _controlData->end(); ++it) { it->unselect(); } } void Gui::initWindows() { // Game Controls Window _controlsWindow = _wm.addWindow(false, false, false); _controlsWindow->setDimensions(getWindowData(kCommandsWindow).bounds); _controlsWindow->setActive(false); _controlsWindow->setCallback(commandsWindowCallback, this); loadBorders(_controlsWindow, findWindowData(kCommandsWindow).type); // Main Game Window _mainGameWindow = _wm.addWindow(false, false, false); _mainGameWindow->setDimensions(getWindowData(kMainGameWindow).bounds); _mainGameWindow->setActive(false); _mainGameWindow->setCallback(mainGameWindowCallback, this); loadBorders(_mainGameWindow, findWindowData(kMainGameWindow).type); // In-game Output Console _outConsoleWindow = _wm.addWindow(true, true, false); // HACK We have to hand-create the dimensions, otherwise they don't fit const WindowData &wd = getWindowData(kOutConsoleWindow); Common::Rect dimensions = wd.bounds; dimensions.setWidth(dimensions.width() - borderBounds(wd.type).rightOffset); _outConsoleWindow->setDimensions(dimensions); _outConsoleWindow->setActive(false); _outConsoleWindow->setCallback(outConsoleWindowCallback, this); loadBorders(_outConsoleWindow, findWindowData(kOutConsoleWindow).type); // Self Window _selfWindow = _wm.addWindow(false, true, false); _selfWindow->setDimensions(getWindowData(kSelfWindow).bounds); _selfWindow->setActive(false); _selfWindow->setCallback(selfWindowCallback, this); loadBorders(_selfWindow, findWindowData(kSelfWindow).type); // Exits Window _exitsWindow = _wm.addWindow(false, false, false); _exitsWindow->setDimensions(getWindowData(kExitsWindow).bounds); _exitsWindow->setActive(false); _exitsWindow->setCallback(exitsWindowCallback, this); // TODO: In the original, the background is actually a clickable // object that can be used to refer to the room itself. In that case, // the background should be kPatternDarkGray. _exitsWindow->setBackgroundPattern(kPatternLightGray); loadBorders(_exitsWindow, findWindowData(kExitsWindow).type); } const WindowData &Gui::getWindowData(WindowReference reference) { return findWindowData(reference); } const Graphics::Font &Gui::getCurrentFont() { return *_wm._fontMan->getFont(Graphics::MacFont(Graphics::kMacFontChicago, 12)); } void Gui::bringToFront(WindowReference winID) { findWindow(winID)->setActive(true); } void Gui::setWindowTitle(WindowReference winID, Common::String string) { findWindowData(winID).title = string; findWindowData(winID).titleLength = string.size(); } void Gui::updateWindowInfo(WindowReference ref, ObjID objID, const Common::Array &children) { if (ref == kNoWindow) { return; } WindowData &data = findWindowData(ref); data.children.clear(); data.objRef = objID; uint32 originx = 0x7fff; uint32 originy = 0x7fff; for (uint i = 0; i < children.size(); i++) { if (children[i] != 1) { ObjID child = children[i]; if (ref != kMainGameWindow) { Common::Point childPos = _engine->getObjPosition(child); originx = originx > (uint)childPos.x ? (uint)childPos.x : originx; originy = originy > (uint)childPos.y ? (uint)childPos.y : originy; } data.children.push_back(DrawableObject(child, kBlitBIC)); } } if (originx != 0x7fff) { data.bounds.left = originx; } if (originy != 0x7fff) { data.bounds.top = originy; } if (ref != kMainGameWindow) { data.updateScroll = true; } } void Gui::addChild(WindowReference target, ObjID child) { findWindowData(target).children.push_back(DrawableObject(child, kBlitBIC)); } void Gui::removeChild(WindowReference target, ObjID child) { WindowData &data = findWindowData(target); uint index = 0; for (;index < data.children.size(); index++) { if (data.children[index].obj == child) { break; } } if (index < data.children.size()) data.children.remove_at(index); } void Gui::assignObjReferences() { findWindowData(kSelfWindow).objRef = 0; } WindowReference Gui::createInventoryWindow(ObjID objRef) { Graphics::MacWindow *newWindow = _wm.addWindow(true, true, true); WindowData newData; GlobalSettings settings = _engine->getGlobalSettings(); newData.refcon = (WindowReference)(_inventoryWindows.size() + kInventoryStart); // This is a HACK if (_windowData->back().refcon < 0x80) { // There is already another inventory window newData.bounds = _windowData->back().bounds; // Inventory windows are always last newData.bounds.translate(newData.bounds.left + settings._invOffsetX, newData.bounds.top + settings._invOffsetY); } else { BorderBounds bbs = borderBounds(kInvWindow); newData.bounds = Common::Rect( settings._invLeft - bbs.leftOffset, settings._invTop - bbs.topOffset, settings._invLeft + settings._invWidth, settings._invTop + settings._invHeight); } newData.type = kInvWindow; newData.hasCloseBox = true; newData.visible = true; newData.objRef = objRef; _windowData->push_back(newData); newWindow->setDimensions(newData.bounds); newWindow->setCallback(inventoryWindowCallback, this); newWindow->setCloseable(true); loadBorders(newWindow, newData.type); _inventoryWindows.push_back(newWindow); debugC(1, kMVDebugGUI, "Create new inventory window. Reference: %d", newData.refcon); return newData.refcon; } void Gui::loadBorders(Graphics::MacWindow *target, MVWindowType type) { loadBorder(target, type, false); loadBorder(target, type, true); } void Gui::loadBorder(Graphics::MacWindow *target, MVWindowType type, bool active) { Common::SeekableReadStream *stream = _engine->getBorderFile(type, active); if (stream) { BorderBounds bbs = borderBounds(type); target->loadBorder(*stream, active, bbs.leftOffset, bbs.rightOffset, bbs.topOffset, bbs.bottomOffset); delete stream; } } void Gui::loadGraphics() { if (_graphics) delete _graphics; _graphics = new Container(_engine->getFilePath(kGraphicPathID)); } void Gui::clearAssets() { Common::HashMap::const_iterator it = _assets.begin(); for (; it != _assets.end(); it++) { delete it->_value; } _assets.clear(); } bool Gui::loadMenus() { if (kLoadStaticMenus) { // We assume that, if there are static menus, we don't need dynamic ones _menu->addStaticMenus(menuSubItems); return true; } Common::MacResIDArray resArray; Common::SeekableReadStream *res; Common::MacResIDArray::const_iterator iter; if ((resArray = _resourceManager->getResIDArray(MKTAG('M', 'E', 'N', 'U'))).size() == 0) return false; _menu->addMenuItem(nullptr, "Abb", kMenuActionAbout, 0, 'A', true); int i = 1; for (iter = resArray.begin(); iter != resArray.end(); ++iter) { res = _resourceManager->getResource(MKTAG('M', 'E', 'N', 'U'), *iter); uint16 key; uint16 style; uint8 titleLength; char *title; /* Skip menuID, width, height, resourceID, placeholder */ for (int skip = 0; skip < 5; skip++) { res->readUint16BE(); } titleLength = res->readByte(); title = new char[titleLength + 1]; res->read(title, titleLength); title[titleLength] = '\0'; if (titleLength > 1) { _menu->addMenuItem(nullptr, title); Graphics::MacMenuSubMenu *submenu = _menu->addSubMenu(nullptr); // Read submenu items while ((titleLength = res->readByte())) { title = new char[titleLength + 1]; res->read(title, titleLength); title[titleLength] = '\0'; // Skip icon res->readUint16BE(); // Read key key = res->readUint16BE(); // Skip mark res->readUint16BE(); // Read style style = res->readUint16BE(); _menu->addMenuItem(submenu, title, 0, style, key, false); } } i++; delete res; } return true; } bool Gui::loadWindows() { Common::MacResIDArray resArray; Common::SeekableReadStream *res; Common::MacResIDArray::const_iterator iter; _windowData = new Common::List(); if ((resArray = _resourceManager->getResIDArray(MKTAG('W', 'I', 'N', 'D'))).size() == 0) return false; uint32 id = kCommandsWindow; for (iter = resArray.begin(); iter != resArray.end(); ++iter) { res = _resourceManager->getResource(MKTAG('W', 'I', 'N', 'D'), *iter); WindowData data; uint16 top, left, bottom, right; top = res->readUint16BE(); left = res->readUint16BE(); bottom = res->readUint16BE(); right = res->readUint16BE(); data.type = (MVWindowType)res->readUint16BE(); BorderBounds bbs = borderBounds(data.type); data.bounds = Common::Rect( left - bbs.leftOffset, top - bbs.topOffset, right + bbs.rightOffset, bottom + bbs.bottomOffset); data.visible = res->readUint16BE(); data.hasCloseBox = res->readUint16BE(); data.refcon = (WindowReference)id; id++; res->readUint32BE(); // Skip the true id. For some reason it's reading 0 data.titleLength = res->readByte(); if (data.titleLength) { char *newTitle = new char[data.titleLength + 1]; res->read(newTitle, data.titleLength); newTitle[data.titleLength] = '\0'; data.title = Common::String(newTitle); delete[] newTitle; } data.scrollPos = Common::Point(0, 0); debugC(1, kMVDebugGUI, "Window loaded: %s", data.title.c_str()); _windowData->push_back(data); delete res; } return true; } bool Gui::loadControls() { Common::MacResIDArray resArray; Common::SeekableReadStream *res; Common::MacResIDArray::const_iterator iter; _controlData = new Common::Array(); _exitsData = new Common::Array(); if ((resArray = _resourceManager->getResIDArray(MKTAG('C', 'N', 'T', 'L'))).size() == 0) return false; uint32 id = kControlExitBox; for (iter = resArray.begin(); iter != resArray.end(); ++iter) { res = _resourceManager->getResource(MKTAG('C', 'N', 'T', 'L'), *iter); ControlData data; uint16 top, left, bottom, right; top = res->readUint16BE(); left = res->readUint16BE(); bottom = res->readUint16BE(); right = res->readUint16BE(); data.scrollValue = res->readUint16BE(); data.visible = res->readByte(); res->readByte(); // Unused data.scrollMax = res->readUint16BE(); data.scrollMin = res->readUint16BE(); data.cdef = res->readUint16BE(); data.refcon = (ControlAction)res->readUint32BE(); data.type = (ControlType)id; id++; data.titleLength = res->readByte(); if (data.titleLength) { char *title = new char[data.titleLength + 1]; res->read(title, data.titleLength); title[data.titleLength] = '\0'; data.title = Common::String(title); delete[] title; } if (data.type != kControlExitBox) { BorderBounds bbs = borderBounds(getWindowData(kCommandsWindow).type); // We just want to move the button, not change it's size data.bounds = Common::Rect(left + bbs.leftOffset, top + bbs.topOffset, right + bbs.leftOffset, bottom + bbs.topOffset); } else { data.bounds = Common::Rect(left, top, right, bottom); } _controlData->push_back(CommandButton(data, this)); delete res; } return true; } void Gui::drawWindows() { drawCommandsWindow(); drawMainGameWindow(); drawSelfWindow(); drawInventories(); drawExitsWindow(); drawConsoleWindow(); } void Gui::drawCommandsWindow() { if (_engine->needsClickToContinue()) { Graphics::ManagedSurface *srf = _controlsWindow->getSurface(); WindowData data = getWindowData(kCommandsWindow); srf->fillRect(Common::Rect(0, 0, srf->w, srf->h), kColorWhite); getCurrentFont().drawString( srf, _engine->getCommandsPausedString(), 0, (srf->h / 2) - getCurrentFont().getFontHeight(), data.bounds.right - data.bounds.left, kColorBlack, Graphics::kTextAlignCenter); } else { Common::Array::const_iterator it = _controlData->begin(); for (; it != _controlData->end(); ++it) { CommandButton button = *it; if (button.getData().type != kControlExitBox) button.draw(*_controlsWindow->getSurface()); } } } void Gui::drawMainGameWindow() { const WindowData &data = getWindowData(kMainGameWindow); BorderBounds border = borderBounds(data.type); ObjID objRef = data.objRef; _mainGameWindow->setDirty(true); if (data.objRef > 0 && data.objRef < 2000) { ensureAssetLoaded(objRef); _assets[objRef]->blitInto( _mainGameWindow->getSurface(), border.leftOffset, border.topOffset, kBlitDirect); } drawObjectsInWindow(data, _mainGameWindow->getSurface()); if (DebugMan.isDebugChannelEnabled(kMVDebugGUI)) { Graphics::MacWindow *win = findWindow(data.refcon); Common::Rect innerDims = win->getInnerDimensions(); int x = win->getDimensions().left; int y = win->getDimensions().top; innerDims.translate(-x, -y); win->getSurface()->frameRect(innerDims, kColorGreen); } findWindow(kMainGameWindow)->setDirty(true); } void Gui::drawSelfWindow() { drawObjectsInWindow(getWindowData(kSelfWindow), _selfWindow->getSurface()); if (_engine->isObjSelected(1)) { invertWindowColors(kSelfWindow); } findWindow(kSelfWindow)->setDirty(true); } void Gui::drawInventories() { Graphics::ManagedSurface *srf; for (uint i = 0; i < _inventoryWindows.size(); i++) { const WindowData &data = getWindowData((WindowReference)(kInventoryStart + i)); Graphics::MacWindow *win = findWindow(data.refcon); srf = win->getSurface(); srf->clear(kColorGreen); srf->fillRect(srf->getBounds(), kColorWhite); drawObjectsInWindow(data, srf); if (DebugMan.isDebugChannelEnabled(kMVDebugGUI)) { Common::Rect innerDims = win->getInnerDimensions(); int x = win->getDimensions().left; int y = win->getDimensions().top; innerDims.translate(-x, -y); srf->frameRect(innerDims, kColorGreen); } findWindow(data.refcon)->setDirty(true); } } void Gui::drawExitsWindow() { _exitsWindow->setBackgroundPattern(kPatternLightGray); Graphics::ManagedSurface *srf = _exitsWindow->getSurface(); Common::Array::const_iterator it = _exitsData->begin(); for (; it != _exitsData->end(); ++it) { CommandButton button = *it; button.draw(*srf); } findWindow(kExitsWindow)->setDirty(true); } void Gui::drawConsoleWindow() { Graphics::ManagedSurface *srf = _outConsoleWindow->getSurface(); BorderBounds bounds = borderBounds(getWindowData(kOutConsoleWindow).type); _consoleText->renderInto(srf, bounds, kConsoleLeftOffset); } void Gui::drawObjectsInWindow(const WindowData &targetData, Graphics::ManagedSurface *surface) { BorderBounds border = borderBounds(targetData.type); Common::Point pos; ObjID child; BlitMode mode; if (targetData.children.size() == 0) { return; } Graphics::ManagedSurface composeSurface; createInnerSurface(&composeSurface, surface, border); assert(composeSurface.w <= surface->w && composeSurface.h <= surface->h); composeSurface.clear(kColorGreen); for (uint i = 0; i < targetData.children.size(); i++) { child = targetData.children[i].obj; mode = (BlitMode)targetData.children[i].mode; pos = _engine->getObjPosition(child); pos -= targetData.scrollPos; ensureAssetLoaded(child); _assets[child]->blitInto( &composeSurface, pos.x, pos.y, mode); if (_engine->isObjVisible(child)) { if (_engine->isObjSelected(child) || child == _draggedObj.id) { _assets[child]->blitInto( &composeSurface, pos.x, pos.y, kBlitOR); } } if (DebugMan.isDebugChannelEnabled(kMVDebugGUI)) { Common::Rect testBounds = _engine->getObjBounds(child); testBounds.translate(-targetData.scrollPos.x, -targetData.scrollPos.y); surface->frameRect(testBounds, kColorGreen); } } Common::Point composePosition = Common::Point(border.leftOffset, border.topOffset); surface->transBlitFrom(composeSurface, composePosition, kColorGreen); } void Gui::drawWindowTitle(WindowReference target, Graphics::ManagedSurface *surface) { // TODO: Implement when MacGui supports titles in windows with custom borders. } void Gui::drawDraggedObject() { if (_draggedObj.id != 0 && _engine->isObjVisible(_draggedObj.id)) { ensureAssetLoaded(_draggedObj.id); ImageAsset *asset = _assets[_draggedObj.id]; // In case of overflow from the right/top uint w = asset->getWidth() + MIN((int16)0, _draggedObj.pos.x); uint h = asset->getHeight() + MIN((int16)0, _draggedObj.pos.y); // In case of overflow from the bottom/left if (_draggedObj.pos.x > 0 && _draggedObj.pos.x + w > kScreenWidth) { w = kScreenWidth - _draggedObj.pos.x; } if (_draggedObj.pos.y > 0 && _draggedObj.pos.y + h > kScreenHeight) { h = kScreenHeight - _draggedObj.pos.y; } Common::Point target = _draggedObj.pos; if (target.x < 0) { target.x = 0; } if (target.y < 0) { target.y = 0; } _draggedSurface.create(w, h, _screen.format); _draggedSurface.blitFrom( _screen, Common::Rect( target.x, target.y, target.x + _draggedSurface.w, target.y + _draggedSurface.h), Common::Point(0, 0)); asset->blitInto(&_draggedSurface, MIN((int16)0, _draggedObj.pos.x), MIN((int16)0, _draggedObj.pos.y), kBlitBIC); g_system->copyRectToScreen( _draggedSurface.getBasePtr(0, 0), _draggedSurface.pitch, target.x, target.y, _draggedSurface.w, _draggedSurface.h ); } } void Gui::drawDialog() { if (_dialog) { _dialog->draw(); } } void Gui::updateWindow(WindowReference winID, bool containerOpen) { if (winID == kNoWindow) { return; } if (winID == kSelfWindow || containerOpen) { WindowData &data = findWindowData(winID); if (winID == kCommandsWindow) { Common::Array::iterator it = _controlData->begin(); for (; it != _controlData->end(); ++it) { it->unselect(); } } Common::Array &children = data.children; for (uint i = 0; i < children.size(); i++) { uint flag = 0; ObjID child = children[i].obj; BlitMode mode = kBlitDirect; bool off = !_engine->isObjVisible(child); // CHECKME: Since flag = 0, this always evaluates to false if (flag || !off || !_engine->isObjClickable(child)) { mode = kBlitBIC; if (off || flag) { mode = kBlitXOR; } else if (!off && _engine->isObjSelected(child)) { mode = kBlitOR; } children[i] = DrawableObject(child, mode); } else { children[i] = DrawableObject(child, kBlitXOR); } } if (winID == kMainGameWindow) { drawMainGameWindow(); } else { Graphics::MacWindow *winRef = findWindow(winID); winRef->getSurface()->fillRect(data.bounds, kColorGray); } if (data.type == kZoomDoc && data.updateScroll) { warning("Unimplemented: update scroll"); } } } void Gui::clearExits() { _exitsData->clear(); } void Gui::unselectExits() { Common::Array::const_iterator it = _exitsData->begin(); for (; it != _exitsData->end(); ++it) { CommandButton button = *it; button.unselect(); } } void Gui::updateExit(ObjID obj) { if (!_engine->isObjExit(obj)) { return; } BorderBounds border = borderBounds(getWindowData(kExitsWindow).type); int ctl = -1; int i = 0; Common::Array::const_iterator it = _exitsData->begin(); for (;it != _exitsData->end(); it++) { if ((ObjID)it->getData().refcon == obj) ctl = i; else i++; } if (ctl != -1) _exitsData->remove_at(ctl); if (!_engine->isHiddenExit(obj) && _engine->getParent(obj) == _engine->getParent(1)) { ControlData data; data.titleLength = 0; data.refcon = (ControlAction)obj; // Objects can be exits (actions) Common::Point pos = _engine->getObjExitPosition(obj); pos.x += border.leftOffset; pos.y += border.topOffset; data.bounds = Common::Rect(pos.x, pos.y, pos.x + kExitButtonWidth, pos.y + kExitButtonHeight); data.visible = true; _exitsData->push_back(CommandButton(data, this)); } } void Gui::printText(const Common::String &text) { debugC(1, kMVDebugGUI, "Print Text: %s", text.c_str()); _consoleText->printLine(text, _outConsoleWindow->getDimensions().width()); } void Gui::showPrebuiltDialog(PrebuiltDialogs type) { closeDialog(); _dialog = new Dialog(this, type); } bool Gui::isDialogOpen() { return _dialog != NULL; } void Gui::setTextInput(Common::String str) { _engine->setTextInput(str); } void Gui::closeDialog() { delete _dialog; _dialog = NULL; } void Gui::getTextFromUser() { if (_dialog) { delete _dialog; } showPrebuiltDialog(kSpeakDialog); } void Gui::loadGame() { _engine->scummVMSaveLoadDialog(false); } void Gui::saveGame() { _engine->scummVMSaveLoadDialog(true); } void Gui::newGame() { _engine->newGame(); } void Gui::quitGame() { _engine->requestQuit(); } void Gui::createInnerSurface(Graphics::ManagedSurface *innerSurface, Graphics::ManagedSurface *outerSurface, const BorderBounds &borders) { innerSurface->create( outerSurface->w - borders.leftOffset - borders.rightOffset, outerSurface->h - borders.topOffset - borders.bottomOffset, outerSurface->format); } void Gui::moveDraggedObject(Common::Point target) { ensureAssetLoaded(_draggedObj.id); _draggedObj.pos = target + _draggedObj.mouseOffset; // TODO FInd more elegant way of making pow2 _draggedObj.hasMoved = (_draggedObj.startPos.sqrDist(_draggedObj.pos) >= (kDragThreshold * kDragThreshold)); debugC(4, kMVDebugGUI, "Dragged obj position: (%d, %d), mouse offset: (%d, %d), hasMoved: %d, dist: %d, threshold: %d", _draggedObj.pos.x, _draggedObj.pos.y, _draggedObj.mouseOffset.x, _draggedObj.mouseOffset.y, _draggedObj.hasMoved, _draggedObj.startPos.sqrDist(_draggedObj.pos), kDragThreshold * kDragThreshold ); } WindowReference Gui::findWindowAtPoint(Common::Point point) { Common::List::iterator it; Graphics::MacWindow *win; for (it = _windowData->begin(); it != _windowData->end(); it++) { win = findWindow(it->refcon); if (win && it->refcon != kDiplomaWindow) { //HACK, diploma should be cosnidered if (win->getDimensions().contains(point)) { return it->refcon; } } } return kNoWindow; } Common::Point Gui::getGlobalScrolledSurfacePosition(WindowReference reference) { const WindowData &data = getWindowData(reference); BorderBounds border = borderBounds(data.type); Graphics::MacWindow *win = findWindow(reference); if (!win) { return Common::Point(0, 0); } return Common::Point( win->getDimensions().left + border.leftOffset - data.scrollPos.x, win->getDimensions().top + border.topOffset - data.scrollPos.y); } WindowData &Gui::findWindowData(WindowReference reference) { assert(_windowData); Common::List::iterator iter = _windowData->begin(); while (iter->refcon != reference && iter != _windowData->end()) { iter++; } if (iter->refcon == reference) return *iter; error("GUI: Could not locate the desired window data"); } Graphics::MacWindow *Gui::findWindow(WindowReference reference) { if (reference < 0x80 && reference >= kInventoryStart) { // It's an inventory window return _inventoryWindows[reference - kInventoryStart]; } switch (reference) { case MacVenture::kNoWindow: return NULL; case MacVenture::kCommandsWindow: return _controlsWindow; case MacVenture::kMainGameWindow: return _mainGameWindow; case MacVenture::kOutConsoleWindow: return _outConsoleWindow; case MacVenture::kSelfWindow: return _selfWindow; case MacVenture::kExitsWindow: return _exitsWindow; case MacVenture::kDiplomaWindow: return _diplomaWindow; default: return NULL; } return NULL; } void Gui::ensureInventoryOpen(WindowReference reference, ObjID id) { assert(reference < 0x80 && reference >= kInventoryStart); if (reference - kInventoryStart == (int)_inventoryWindows.size()) { createInventoryWindow(id); } } WindowReference Gui::getObjWindow(ObjID objID) { switch (objID) { case 0xfffc: return kExitsWindow; case 0xfffd: return kSelfWindow; case 0xfffe: return kOutConsoleWindow; case 0xffff: return kCommandsWindow; default: return findObjWindow(objID); } } WindowReference Gui::findObjWindow(ObjID objID) { // This is a bit of a HACK, we take advantage of the consecutive nature of references for (uint i = kCommandsWindow; i <= kDiplomaWindow; i++) { const WindowData &data = getWindowData((WindowReference)i); if (data.objRef == objID) { return data.refcon; } } for (uint i = kInventoryStart; i < _inventoryWindows.size() + kInventoryStart; i++) { const WindowData &data = getWindowData((WindowReference)i); if (data.objRef == objID) { return data.refcon; } } return kNoWindow; } void Gui::checkSelect(const WindowData &data, Common::Point pos, const Common::Rect &clickRect, WindowReference ref) { ObjID child = 0; for (Common::Array::const_iterator it = data.children.begin(); it != data.children.end(); it++) { if (canBeSelected((*it).obj, clickRect, ref)) { child = (*it).obj; } } if (child != 0) { selectDraggable(child, ref, pos); bringToFront(ref); } } bool Gui::canBeSelected(ObjID obj, const Common::Rect &clickRect, WindowReference ref) { return (_engine->isObjClickable(obj) && isRectInsideObject(clickRect, obj)); } bool Gui::isRectInsideObject(Common::Rect target, ObjID obj) { ensureAssetLoaded(obj); Common::Rect bounds = _engine->getObjBounds(obj); Common::Rect intersection = bounds.findIntersectingRect(target); // We translate it to the image's coord system intersection = Common::Rect( intersection.left - bounds.left, intersection.top - bounds.top, intersection.left - bounds.left + intersection.width(), intersection.top - bounds.top + intersection.height()); return _assets[obj]->isRectInside(intersection); } void Gui::selectDraggable(ObjID child, WindowReference origin, Common::Point click) { if (_engine->isObjClickable(child) && _draggedObj.id == 0) { _draggedObj.hasMoved = false; _draggedObj.id = child; _draggedObj.startWin = origin; Common::Point localizedClick = click - getGlobalScrolledSurfacePosition(origin); _draggedObj.mouseOffset = _engine->getObjPosition(child) - localizedClick; _draggedObj.pos = click + _draggedObj.mouseOffset; _draggedObj.startPos = _draggedObj.pos; } } void Gui::handleDragRelease(bool shiftPressed, bool isDoubleClick) { if (_draggedObj.id != 0) { WindowReference destinationWindow = findWindowAtPoint(_draggedObj.pos); if (destinationWindow == kNoWindow) { return; } if (_draggedObj.hasMoved) { const WindowData &destinationWindowData = getWindowData(destinationWindow); ObjID destObject = destinationWindowData.objRef; Common::Point dropPosition = _draggedObj.pos - _draggedObj.startPos; dropPosition = localizeTravelledDistance(dropPosition, _draggedObj.startWin, destinationWindow); debugC(3, kMVDebugGUI, "Drop the object %d at obj %d, pos (%d, %d)", _draggedObj.id, destObject, dropPosition.x, dropPosition.y); _engine->handleObjectDrop(_draggedObj.id, dropPosition, destObject); } _engine->handleObjectSelect(_draggedObj.id, destinationWindow, shiftPressed, isDoubleClick); _draggedObj.id = 0; _draggedObj.hasMoved = false; } } Common::Rect Gui::calculateClickRect(Common::Point clickPos, Common::Rect windowBounds) { int left = clickPos.x - windowBounds.left; int top = clickPos.y - windowBounds.top; return Common::Rect(left - kCursorWidth, top - kCursorHeight, left + kCursorWidth, top + kCursorHeight); } Common::Point Gui::localizeTravelledDistance(Common::Point point, WindowReference origin, WindowReference target) { if (origin != target) { // ori.local to global point += getGlobalScrolledSurfacePosition(origin); if (findWindow(target)) { // dest.globalToLocal point -= getGlobalScrolledSurfacePosition(target); } } return point; } void Gui::removeInventoryWindow(WindowReference ref) { _inventoryWindows.remove_at(ref - kInventoryStart); Common::List::iterator it; for (it = _windowData->begin(); it != _windowData->end(); it++) { if (it->refcon == ref) { _windowData->erase(it); break; } } } /* HANDLERS */ void Gui::handleMenuAction(MenuAction action) { switch (action) { case MacVenture::kMenuActionAbout: warning("Unimplemented MacVenture Menu Action: About"); break; case MacVenture::kMenuActionNew: _engine->newGame(); break; case MacVenture::kMenuActionOpen: loadGame(); break; case MacVenture::kMenuActionSave: saveGame(); break; case MacVenture::kMenuActionSaveAs: saveGame(); break; case MacVenture::kMenuActionQuit: _engine->requestQuit(); break; case MacVenture::kMenuActionUndo: warning("Unimplemented MacVenture Menu Action: Undo"); break; case MacVenture::kMenuActionCut: warning("Unimplemented MacVenture Menu Action: Cut"); break; case MacVenture::kMenuActionCopy: warning("Unimplemented MacVenture Menu Action: Copy"); break; case MacVenture::kMenuActionPaste: warning("Unimplemented MacVenture Menu Action: Paste"); break; case MacVenture::kMenuActionClear: warning("Unimplemented MacVenture Menu Action: Clear"); break; case MacVenture::kMenuActionCleanUp: warning("Unimplemented MacVenture Menu Action: Clean Up"); break; case MacVenture::kMenuActionMessUp: warning("Unimplemented MacVenture Menu Action: Mess Up"); break; case MacVenture::kMenuActionCommand: warning("Unimplemented MacVenture Menu Action: GENERIC"); break; default: break; } } /* CALLBACKS */ bool commandsWindowCallback(Graphics::WindowClick click, Common::Event &event, void *gui) { Gui *g = (Gui*)gui; return g->processCommandEvents(click, event); } bool mainGameWindowCallback(Graphics::WindowClick click, Common::Event &event, void *gui) { Gui *g = (Gui*)gui; return g->processMainGameEvents(click, event); } bool outConsoleWindowCallback(Graphics::WindowClick click, Common::Event &event, void *gui) { Gui *g = (Gui*)gui; return g->processOutConsoleEvents(click, event); } bool selfWindowCallback(Graphics::WindowClick click, Common::Event &event, void *gui) { Gui *g = (Gui*)gui; return g->processSelfEvents(click, event); } bool exitsWindowCallback(Graphics::WindowClick click, Common::Event &event, void *gui) { Gui *g = (Gui*)gui; return g->processExitsEvents(click, event); } bool diplomaWindowCallback(Graphics::WindowClick click, Common::Event &event, void *gui) { Gui *g = (Gui*)gui; return g->processDiplomaEvents(click, event); } bool inventoryWindowCallback(Graphics::WindowClick click, Common::Event &event, void *gui) { Gui *g = (Gui*)gui; return g->processInventoryEvents(click, event); } void menuCommandsCallback(int action, Common::String &text, void *data) { Gui *g = (Gui *)data; g->handleMenuAction((MenuAction)action); } void Gui::invertWindowColors(WindowReference winID) { Graphics::ManagedSurface *srf = findWindow(winID)->getSurface(); for (uint y = 0; y < srf->h; y++) { for (uint x = 0; x < srf->w; x++) { byte p = *(byte *)srf->getBasePtr(x, y); *(byte *)srf->getBasePtr(x, y) = (p == kColorWhite) ? kColorBlack : kColorGray; } } } bool Gui::tryCloseWindow(WindowReference winID) { //WindowData data = findWindowData(winID); Graphics::MacWindow *win = findWindow(winID); _wm.removeWindow(win); if (winID < 0x80) { removeInventoryWindow(winID); } return true; } Common::Point Gui::getObjMeasures(ObjID obj) { ensureAssetLoaded(obj); int w = _assets[obj]->getWidth(); int h = _assets[obj]->getHeight(); return Common::Point(w, h); } bool Gui::processEvent(Common::Event &event) { bool processed = false; processed |= _cursor->processEvent(event); if (_dialog && _dialog->processEvent(event)) { return true; } if (event.type == Common::EVENT_MOUSEMOVE) { if (_draggedObj.id != 0) { moveDraggedObject(event.mouse); } processed = true; } processed |= _wm.processEvent(event); return (processed); } bool Gui::processCommandEvents(WindowClick click, Common::Event &event) { if (event.type == Common::EVENT_LBUTTONUP) { if (_engine->needsClickToContinue()) { _engine->selectControl(kClickToContinue); return true; } Common::Point position( event.mouse.x - _controlsWindow->getDimensions().left, event.mouse.y - _controlsWindow->getDimensions().top); CommandButton data; if (!_controlData) return false; Common::Array::iterator it = _controlData->begin(); for (; it != _controlData->end(); ++it) { if (it->isInsideBounds(position)) { it->select(); data = *it; } else { it->unselect(); } } _engine->selectControl(data.getData().refcon); _engine->refreshReady(); _engine->preparedToRun(); } return false; } bool MacVenture::Gui::processMainGameEvents(WindowClick click, Common::Event &event) { if (_engine->needsClickToContinue()) return true; return false; } bool MacVenture::Gui::processOutConsoleEvents(WindowClick click, Common::Event &event) { if (_engine->needsClickToContinue()) return true; if (click == kBorderScrollUp && event.type == Common::EVENT_LBUTTONDOWN) { _consoleText->scrollUp(); return true; } if (click == kBorderScrollDown && event.type == Common::EVENT_LBUTTONDOWN) { _consoleText->scrollDown(); return true; } return getWindowData(kOutConsoleWindow).visible; } bool MacVenture::Gui::processSelfEvents(WindowClick click, Common::Event &event) { if (_engine->needsClickToContinue()) return true; if (event.type == Common::EVENT_LBUTTONUP) { _engine->handleObjectSelect(1, kSelfWindow, false, false); } return true; } bool MacVenture::Gui::processExitsEvents(WindowClick click, Common::Event &event) { if (event.type == Common::EVENT_LBUTTONUP) { if (_engine->needsClickToContinue()) { return true; } Common::Point position( event.mouse.x - _exitsWindow->getDimensions().left, event.mouse.y - _exitsWindow->getDimensions().top); CommandButton button; if (!_exitsData) return false; Common::Array::iterator it = _exitsData->begin(); for (; it != _exitsData->end(); ++it) { if (it->isInsideBounds(position)) { it->select(); button = *it; _engine->handleObjectSelect(button.getData().refcon, kExitsWindow, false, false); return true; } else { it->unselect(); } } } return getWindowData(kExitsWindow).visible; } bool MacVenture::Gui::processDiplomaEvents(WindowClick click, Common::Event &event) { if (_engine->needsClickToContinue()) return true; return getWindowData(kDiplomaWindow).visible; } bool Gui::processInventoryEvents(WindowClick click, Common::Event &event) { if (event.type == Common::EVENT_LBUTTONDOWN && click == kBorderCloseButton) { WindowReference ref = findWindowAtPoint(event.mouse); if (ref == kNoWindow) { return false; } if (click == kBorderCloseButton) { removeInventoryWindow(ref); return true; } } if (_engine->needsClickToContinue()) return true; if (event.type == Common::EVENT_LBUTTONDOWN) { // Find the appropriate window WindowReference ref = findWindowAtPoint(event.mouse); if (ref == kNoWindow) { return false; } WindowData &data = findWindowData((WindowReference) ref); if (click == kBorderScrollUp) { data.scrollPos.y = MAX(0, data.scrollPos.y - kScrollAmount); } if (click == kBorderScrollDown) { data.scrollPos.y += kScrollAmount; } if (click == kBorderScrollLeft) { data.scrollPos.x = MAX(0, data.scrollPos.x - kScrollAmount); } if (click == kBorderScrollRight) { data.scrollPos.x += kScrollAmount; } } return true; } void Gui::selectForDrag(Common::Point cursorPosition) { WindowReference ref = findWindowAtPoint(cursorPosition); if (ref == kNoWindow) { return; } Graphics::MacWindow *win = findWindow(ref); WindowData &data = findWindowData((WindowReference) ref); Common::Rect clickRect = calculateClickRect(cursorPosition + data.scrollPos, win->getDimensions()); checkSelect(data, cursorPosition, clickRect, (WindowReference)ref); } void Gui::handleSingleClick() { debugC(2, kMVDebugGUI, "Registered Single Click"); // HACK THERE HAS TO BE A MORE ELEGANT WAY if (_dialog) { return; } handleDragRelease(false, false); } void Gui::handleDoubleClick() { debugC(2, kMVDebugGUI, "Registered Double Click"); if (_dialog) { return; } handleDragRelease(false, true); } void Gui::ensureAssetLoaded(ObjID obj) { if (!_assets.contains(obj)) { _assets[obj] = new ImageAsset(obj, _graphics); } } } // End of namespace MacVenture