From 05a5fc1b65a089f3c12025107f745f6bb748526e Mon Sep 17 00:00:00 2001 From: Martin Kiewitz Date: Tue, 2 Feb 2016 17:28:58 +0100 Subject: AGI: mouse support for menu --- engines/agi/agi.h | 5 +- engines/agi/cycle.cpp | 6 +- engines/agi/keyboard.cpp | 60 ++++++------ engines/agi/menu.cpp | 244 ++++++++++++++++++++++++++++++++++++++++------- engines/agi/menu.h | 97 +++++-------------- engines/agi/op_cmd.cpp | 2 +- 6 files changed, 274 insertions(+), 140 deletions(-) diff --git a/engines/agi/agi.h b/engines/agi/agi.h index 48df0aefb0..155e5d5c10 100644 --- a/engines/agi/agi.h +++ b/engines/agi/agi.h @@ -392,8 +392,9 @@ enum CycleInnerLoopType { CYCLE_INNERLOOP_GETSTRING = 0, CYCLE_INNERLOOP_GETNUMBER = 1, CYCLE_INNERLOOP_INVENTORY = 2, - CYCLE_INNERLOOP_MENU = 3, - CYCLE_INNERLOOP_SYSTEMUI_SELECTSAVEDGAMESLOT = 4 + CYCLE_INNERLOOP_MENU_VIA_KEYBOARD = 3, + CYCLE_INNERLOOP_MENU_VIA_MOUSE = 4, + CYCLE_INNERLOOP_SYSTEMUI_SELECTSAVEDGAMESLOT = 5 }; enum State { diff --git a/engines/agi/cycle.cpp b/engines/agi/cycle.cpp index 1747452b11..0151de638e 100644 --- a/engines/agi/cycle.cpp +++ b/engines/agi/cycle.cpp @@ -287,12 +287,16 @@ int AgiEngine::mainCycle(bool onlyCheckForEvents) { } break; - case CYCLE_INNERLOOP_MENU: + case CYCLE_INNERLOOP_MENU_VIA_KEYBOARD: if (key) { _menu->charPress(key); } return false; + case CYCLE_INNERLOOP_MENU_VIA_MOUSE: + _menu->mouseEvent(key); + return false; + case CYCLE_INNERLOOP_SYSTEMUI_SELECTSAVEDGAMESLOT: if (key) { _systemUI->savedGameSlot_CharPress(key); diff --git a/engines/agi/keyboard.cpp b/engines/agi/keyboard.cpp index 5a2e165d13..627e5021d1 100644 --- a/engines/agi/keyboard.cpp +++ b/engines/agi/keyboard.cpp @@ -285,39 +285,34 @@ bool AgiEngine::handleMouseClicks(uint16 &key) { if (key != AGI_MOUSE_BUTTON_LEFT) return false; - Common::Rect displayLineRect(DISPLAY_WIDTH, FONT_DISPLAY_HEIGHT); - int16 statusRow = _text->statusRow_Get(); - - displayLineRect.moveTo(0, statusRow * FONT_DISPLAY_HEIGHT); - - if (displayLineRect.contains(_mouse.pos)) { - if (getFlag(VM_FLAG_MENUS_ACCESSIBLE) && _menu->isAvailable()) { - warning("click on status line -> menu TODO"); - // TODO: menu - // This should be done in a better way as in simulate ESC key - // Sierra seems to have hardcoded it in some way, but we would have to verify, what flags - // they checked. The previous way wasn't accurate. Mouse support for menu is missing atm anyway. - //if ((getflag(VM_FLAG_MENUS_WORK) || (getFeatures() & GF_MENUS)) && _mouse.y <= CHAR_LINES) { - // newInputMode(INPUTMODE_MENU); - // return true; - //} - key = 0; // eat event - return true; - } - } - - if (_text->promptIsEnabled() && (!cycleInnerLoopIsActive()) ) { - // Prompt is currently enabled, but no inner loop is active - int16 promptRow = _text->promptRow_Get(); + if (!cycleInnerLoopIsActive()) { + // Only do this, when no inner loop is currently active + Common::Rect displayLineRect(DISPLAY_WIDTH, FONT_DISPLAY_HEIGHT); + int16 statusRow = _text->statusRow_Get(); - displayLineRect.moveTo(0, promptRow * FONT_DISPLAY_HEIGHT); + displayLineRect.moveTo(0, statusRow * FONT_DISPLAY_HEIGHT); if (displayLineRect.contains(_mouse.pos)) { - // and user clicked within the line of the prompt - showPredictiveDialog(); + if (getFlag(VM_FLAG_MENUS_ACCESSIBLE) && _menu->isAvailable()) { + _menu->delayedExecuteViaMouse(); + key = 0; // eat event + return true; + } + } - key = 0; // eat event - return true; + if (_text->promptIsEnabled()) { + // Prompt is currently enabled + int16 promptRow = _text->promptRow_Get(); + + displayLineRect.moveTo(0, promptRow * FONT_DISPLAY_HEIGHT); + + if (displayLineRect.contains(_mouse.pos)) { + // and user clicked within the line of the prompt + showPredictiveDialog(); + + key = 0; // eat event + return true; + } } } @@ -347,8 +342,9 @@ bool AgiEngine::handleMouseClicks(uint16 &key) { case CYCLE_INNERLOOP_INVENTORY: // TODO: forward break; - case CYCLE_INNERLOOP_MENU: - // TODO: forward + case CYCLE_INNERLOOP_MENU_VIA_KEYBOARD: + _menu->mouseEvent(key); + key = 0; // eat event break; case CYCLE_INNERLOOP_SYSTEMUI_SELECTSAVEDGAMESLOT: // TODO: forward @@ -393,7 +389,7 @@ bool AgiEngine::handleController(uint16 key) { // Needs further investigation. if (getFlag(VM_FLAG_MENUS_ACCESSIBLE) && _menu->isAvailable()) { // menu is supposed to be accessible and is also available - _menu->delayedExecute(); + _menu->delayedExecuteViaKeyboard(); return true; } default: diff --git a/engines/agi/menu.cpp b/engines/agi/menu.cpp index ea24bd5b46..97b59d847c 100644 --- a/engines/agi/menu.cpp +++ b/engines/agi/menu.cpp @@ -36,16 +36,21 @@ GfxMenu::GfxMenu(AgiEngine *vm, GfxMgr *gfx, PictureMgr *picture, TextMgr *text) _allowed = true; _submitted = false; - _delayedExecute = false; + _delayedExecuteViaKeyboard = false; + _delayedExecuteViaMouse = false; _setupMenuColumn = 1; _setupMenuItemColumn = 1; - _selectedMenuNr = 0; - _selectedMenuHeight = 0; - _selectedMenuWidth = 0; - _selectedMenuRow = 0; - _selectedMenuColumn = 0; + _lastSelectedMenuNr = 0; + + _mouseModeItemNr = -1; + + _drawnMenuNr = -1; + _drawnMenuHeight = 0; + _drawnMenuWidth = 0; + _drawnMenuRow = 0; + _drawnMenuColumn = 0; } GfxMenu::~GfxMenu() { @@ -263,16 +268,24 @@ void GfxMenu::accessDeny() { _allowed = false; } -void GfxMenu::delayedExecute() { - _delayedExecute = true; +void GfxMenu::delayedExecuteViaKeyboard() { + _delayedExecuteViaKeyboard = true; + _delayedExecuteViaMouse = false; +} +void GfxMenu::delayedExecuteViaMouse() { + _delayedExecuteViaKeyboard = false; + _delayedExecuteViaMouse = true; } bool GfxMenu::delayedExecuteActive() { - return _delayedExecute; + return _delayedExecuteViaKeyboard | _delayedExecuteViaMouse; } void GfxMenu::execute() { - _delayedExecute = false; + bool viaKeyboard = _delayedExecuteViaKeyboard; + bool viaMouse = _delayedExecuteViaMouse; + _delayedExecuteViaKeyboard = false; + _delayedExecuteViaMouse = false; // got submitted? -> safety check if (!_submitted) @@ -290,12 +303,36 @@ void GfxMenu::execute() { for (uint16 menuNr = 0; menuNr < _array.size(); menuNr++) { drawMenuName(menuNr, false); } - drawActiveMenu(); + + // Draw last selected menu + _drawnMenuNr = _lastSelectedMenuNr; + + // Unless we are in "via mouse" mode. In that case check current mouse position + if (viaMouse) { + int16 mouseRow = _vm->_mouse.pos.y / FONT_DISPLAY_HEIGHT; + int16 mouseColumn = _vm->_mouse.pos.x / FONT_DISPLAY_WIDTH; + + mouseFindMenuSelection(mouseRow, mouseColumn, _drawnMenuNr, _mouseModeItemNr); + } + + if (_drawnMenuNr >= 0) { + if (viaKeyboard) { + drawMenu(_drawnMenuNr, _array[_drawnMenuNr]->selectedItemNr); + } + if (viaMouse) { + drawMenu(_drawnMenuNr, _mouseModeItemNr); + } + } // original AGI did not do this, at least when the menu was called by scripts _vm->inGameTimerPause(); - _vm->cycleInnerLoopActive(CYCLE_INNERLOOP_MENU); + if (viaKeyboard) { + _vm->cycleInnerLoopActive(CYCLE_INNERLOOP_MENU_VIA_KEYBOARD); + } else if (viaMouse) { + _vm->cycleInnerLoopActive(CYCLE_INNERLOOP_MENU_VIA_MOUSE); + } + do { _vm->mainCycle(); } while (_vm->cycleInnerLoopIsActive() && !(_vm->shouldQuit() || _vm->_restartGame)); @@ -303,7 +340,14 @@ void GfxMenu::execute() { // original AGI did not do this, at least when the menu was called by scripts _vm->inGameTimerResume(); - removeActiveMenu(); + if (_drawnMenuNr >= 0) { + removeActiveMenu(_drawnMenuNr); + } + + if (viaKeyboard) { + // In "via Keyboard" mode, remember last selection + _lastSelectedMenuNr = _drawnMenuNr; + } _text->charAttrib_Pop(); _text->charPos_Pop(); @@ -352,25 +396,25 @@ void GfxMenu::drawItemName(int16 itemNr, bool inverted) { _text->displayText(itemEntry->text.c_str(), disabledLook); } -void GfxMenu::drawActiveMenu() { - GuiMenuEntry *menuEntry = _array[_selectedMenuNr]; +void GfxMenu::drawMenu(int16 selectedMenuNr, int16 selectedMenuItemNr) { + GuiMenuEntry *menuEntry = _array[selectedMenuNr]; GuiMenuItemEntry *itemEntry = _itemArray[menuEntry->firstItemNr]; int16 itemNr = menuEntry->firstItemNr; int16 itemCount = menuEntry->itemCount; // draw menu name as inverted - drawMenuName(_selectedMenuNr, true); + drawMenuName(selectedMenuNr, true); // calculate active menu dimensions - _selectedMenuHeight = (menuEntry->itemCount + 2) * FONT_VISUAL_HEIGHT; - _selectedMenuWidth = (menuEntry->maxItemTextLen * FONT_VISUAL_WIDTH) + 8; - _selectedMenuRow = (menuEntry->itemCount + 3 - _text->getWindowRowMin()) * FONT_VISUAL_HEIGHT - 1; - _selectedMenuColumn = (itemEntry->column - 1) * FONT_VISUAL_WIDTH; + _drawnMenuHeight = (menuEntry->itemCount + 2) * FONT_VISUAL_HEIGHT; + _drawnMenuWidth = (menuEntry->maxItemTextLen * FONT_VISUAL_WIDTH) + 8; + _drawnMenuRow = (menuEntry->itemCount + 3 - _text->getWindowRowMin()) * FONT_VISUAL_HEIGHT - 1; + _drawnMenuColumn = (itemEntry->column - 1) * FONT_VISUAL_WIDTH; - _gfx->drawBox(_selectedMenuColumn, _selectedMenuRow, _selectedMenuWidth, _selectedMenuHeight, 15, 0); + _gfx->drawBox(_drawnMenuColumn, _drawnMenuRow, _drawnMenuWidth, _drawnMenuHeight, 15, 0); while (itemCount) { - if (itemNr == menuEntry->selectedItemNr) { + if (itemNr == selectedMenuItemNr) { drawItemName(itemNr, true); } else { drawItemName(itemNr, false); @@ -380,18 +424,18 @@ void GfxMenu::drawActiveMenu() { } } -void GfxMenu::removeActiveMenu() { +void GfxMenu::removeActiveMenu(int16 selectedMenuNr) { // draw menu name normally again - drawMenuName(_selectedMenuNr, false); + drawMenuName(selectedMenuNr, false); // overwrite actual menu items by rendering play screen - _gfx->render_Block(_selectedMenuColumn, _selectedMenuRow, _selectedMenuWidth, _selectedMenuHeight); + _gfx->render_Block(_drawnMenuColumn, _drawnMenuRow, _drawnMenuWidth, _drawnMenuHeight); } -void GfxMenu::charPress(int16 newChar) { - GuiMenuEntry *menuEntry = _array[_selectedMenuNr]; +void GfxMenu::charPress(uint16 newChar) { + GuiMenuEntry *menuEntry = _array[_drawnMenuNr]; GuiMenuItemEntry *itemEntry = _itemArray[menuEntry->selectedItemNr]; - int16 newMenuNr = _selectedMenuNr; + int16 newMenuNr = _drawnMenuNr; int16 newItemNr = menuEntry->selectedItemNr; switch (newChar) { @@ -444,7 +488,7 @@ void GfxMenu::charPress(int16 newChar) { break; } - if (newMenuNr != _selectedMenuNr) { + if (newMenuNr != _drawnMenuNr) { // selected menu was changed int16 lastMenuNr = _array.size() - 1; @@ -454,10 +498,10 @@ void GfxMenu::charPress(int16 newChar) { newMenuNr = 0; } - if (newMenuNr != _selectedMenuNr) { - removeActiveMenu(); - _selectedMenuNr = newMenuNr; - drawActiveMenu(); + if (newMenuNr != _drawnMenuNr) { + removeActiveMenu(_drawnMenuNr); + _drawnMenuNr = newMenuNr; + drawMenu(_drawnMenuNr, _array[_drawnMenuNr]->selectedItemNr); } } @@ -480,4 +524,138 @@ void GfxMenu::charPress(int16 newChar) { } } +// This gets called: +// During "via keyboard" mode in case user actively clicks on something +// During "via mouse" mode all the time, so that current mouse cursor position modifies active selection +// In "via mouse" mode, we check if user let go of the left mouse button and then select the item that way +void GfxMenu::mouseEvent(uint16 newChar) { + // Find out, where current mouse cursor actually is + int16 mouseRow = _vm->_mouse.pos.y / FONT_DISPLAY_HEIGHT; + int16 mouseColumn = _vm->_mouse.pos.x / FONT_DISPLAY_WIDTH; + + int16 activeMenuNr, activeItemNr; + mouseFindMenuSelection(mouseRow, mouseColumn, activeMenuNr, activeItemNr); + + switch (newChar) { + case AGI_MOUSE_BUTTON_LEFT: + // User clicked somewhere, in this case check if user clicked on status bar or on one of the currently shown menu items + // Happens in "via keyboard" mode only + // We do not close menu in case user clicked on something invalid + + if (activeItemNr >= 0) { + GuiMenuItemEntry *itemEntry = _itemArray[activeItemNr]; + if (!itemEntry->enabled) + return; + + // Trigger controller + _vm->_game.controllerOccured[itemEntry->controllerSlot] = true; + + _vm->cycleInnerLoopInactive(); // exit execute-loop + return; + } + if (activeMenuNr >= 0) { + // User clicked on a menu, check if that menu is already active + if (activeMenuNr != _drawnMenuNr) { + removeActiveMenu(_drawnMenuNr); + _drawnMenuNr = activeMenuNr; + drawMenu(_drawnMenuNr, _array[_drawnMenuNr]->selectedItemNr); + } + } + return; // exit all the time, we do not want to change the user selection while in "via keyboard" mode + break; + default: + break; + } + + // If mouse is not selecting any menu, just use the last menu instead + if (activeMenuNr < 0) { + activeMenuNr = _drawnMenuNr; + } + + if (activeMenuNr != _drawnMenuNr) { + if (_drawnMenuNr >= 0) { + removeActiveMenu(_drawnMenuNr); + } + + _drawnMenuNr = activeMenuNr; + + if (_drawnMenuNr >= 0) { + drawMenu(_drawnMenuNr, activeItemNr); + } + _mouseModeItemNr = activeItemNr; + } + + if (activeItemNr != _mouseModeItemNr) { + if (_mouseModeItemNr >= 0) { + drawItemName(_mouseModeItemNr, false); + } + if (activeItemNr >= 0) { + drawItemName(activeItemNr, true); + } + _mouseModeItemNr = activeItemNr; + } + + if (_vm->_mouse.button == kAgiMouseButtonUp) { + // User has stopped pressing the mouse button, if any item number is selected -> execute it + if (activeItemNr >= 0) { + GuiMenuItemEntry *itemEntry = _itemArray[activeItemNr]; + if (!itemEntry->enabled) + return; + + // Trigger controller + _vm->_game.controllerOccured[itemEntry->controllerSlot] = true; + } + + _vm->cycleInnerLoopInactive(); // exit execute-loop + return; + } +} + +void GfxMenu::mouseFindMenuSelection(int16 mouseRow, int16 mouseColumn, int16 &activeMenuNr, int16 &activeMenuItemNr) { + GuiMenuEntry *menuEntry = nullptr; + int16 menuCount = _array.size(); + + for (int16 menuNr = 0; menuNr < menuCount; menuNr++) { + menuEntry = _array[menuNr]; + + if (mouseRow == menuEntry->row) { + // line match + if ((mouseColumn >= menuEntry->column) && (mouseColumn <= (menuEntry->column + menuEntry->textLen))) { + // full match + activeMenuNr = menuNr; + activeMenuItemNr = -1; // no item selected + return; + } + } + } + + // Now also check current menu + if (_drawnMenuNr >= 0) { + // A menu is currently shown + menuEntry = _array[_drawnMenuNr]; + + int16 itemNr = menuEntry->firstItemNr; + int16 itemCount = menuEntry->itemCount; + + while (itemCount) { + GuiMenuItemEntry *itemEntry = _itemArray[itemNr]; + + if (mouseRow == itemEntry->row) { + // line match + if ((mouseColumn >= itemEntry->column) && (mouseColumn <= (itemEntry->column + itemEntry->textLen))) { + // full match + activeMenuNr = _drawnMenuNr; + activeMenuItemNr = itemNr; + return; + } + } + itemNr++; + itemCount--; + } + } + activeMenuNr = -1; + activeMenuItemNr = -1; + return; +} + } // End of namespace Agi diff --git a/engines/agi/menu.h b/engines/agi/menu.h index 1781704996..3daeb749ad 100644 --- a/engines/agi/menu.h +++ b/engines/agi/menu.h @@ -32,11 +32,12 @@ struct GuiMenuEntry { int16 row; int16 column; - int16 itemCount; - int16 firstItemNr; - int16 selectedItemNr; + int16 itemCount; // total number of menu items + int16 firstItemNr; // first menu item number, points into _itemArray[] - int16 maxItemTextLen; + int16 selectedItemNr; // currently selected menu item + + int16 maxItemTextLen; // maximum text length of all menu items }; typedef Common::Array GuiMenuArray; @@ -47,8 +48,8 @@ struct GuiMenuItemEntry { int16 row; int16 column; - bool enabled; - uint16 controllerSlot; + bool enabled; // enabled-state, set by scripts + uint16 controllerSlot; // controller to trigger, when item is executed }; typedef Common::Array GuiMenuItemArray; @@ -63,14 +64,17 @@ public: void itemEnable(uint16 controllerSlot); void itemDisable(uint16 controllerSlot); void itemEnableAll(); - void charPress(int16 newChar); + + void charPress(uint16 newChar); + void mouseEvent(uint16 newChar); bool isAvailable(); void accessAllow(); void accessDeny(); - void delayedExecute(); + void delayedExecuteViaKeyboard(); + void delayedExecuteViaMouse(); bool delayedExecuteActive(); void execute(); @@ -79,8 +83,10 @@ private: void drawMenuName(int16 menuNr, bool inverted); void drawItemName(int16 itemNr, bool inverted); - void drawActiveMenu(); - void removeActiveMenu(); + void drawMenu(int16 selectedMenuNr, int16 selectedMenuItemNr); + void removeActiveMenu(int16 selectedMenuNr); + + void mouseFindMenuSelection(int16 mouseRow, int16 mouseColumn, int16 &activeMenuNr, int16 &activeMenuItemNr); AgiEngine *_vm; GfxMgr *_gfx; @@ -89,7 +95,8 @@ private: bool _allowed; bool _submitted; - bool _delayedExecute; + bool _delayedExecuteViaKeyboard; + bool _delayedExecuteViaMouse; // for initial setup of the menu int16 _setupMenuColumn; @@ -98,70 +105,18 @@ private: GuiMenuArray _array; GuiMenuItemArray _itemArray; - int16 _selectedMenuNr; - - uint16 _selectedMenuHeight; - uint16 _selectedMenuWidth; - int16 _selectedMenuRow; - int16 _selectedMenuColumn; -}; - -#if 0 -#define MENU_BG 0x0f // White -#define MENU_DISABLED 0x07 // Grey - -#define MENU_FG 0x00 // Black -#define MENU_LINE 0x00 // Black - -struct AgiMenu; -struct AgiMenuOption; -typedef Common::List MenuList; -typedef Common::List MenuOptionList; + int16 _lastSelectedMenuNr; // only used for "via keyboard" mode -class GfxMgr; -class PictureMgr; + int16 _drawnMenuNr; -class Menu { -private: - AgiEngine *_vm; - GfxMgr *_gfx; - PictureMgr *_picture; - TextMgr *_text; - -public: - Menu(AgiEngine *vm, GfxMgr *gfx, PictureMgr *picture); - ~Menu(); - - void add(const char *s); - void addItem(const char *s, int code); - void submit(); - void setItem(int event, int state); - bool keyhandler(int key); - void enableAll(); - -private: - MenuList _menubar; - - int _hCurMenu; - int _vCurMenu; - - int _hIndex; - int _vIndex; - int _hCol; - int _hMaxMenu; - int _vMaxMenu[10]; - - AgiMenu* getMenu(int i); - AgiMenuOption *getMenuOption(int i, int j); - void drawMenuBar(); - void drawMenuHilite(int curMenu); - void drawMenuOption(int hMenu); - void drawMenuOptionHilite(int hMenu, int vMenu); - void newMenuSelected(int i); - bool mouseOverText(int line, int col, char *s); + uint16 _drawnMenuHeight; + uint16 _drawnMenuWidth; + int16 _drawnMenuRow; + int16 _drawnMenuColumn; + // Following variables are used in "via mouse" mode + int16 _mouseModeItemNr; }; -#endif } // End of namespace Agi diff --git a/engines/agi/op_cmd.cpp b/engines/agi/op_cmd.cpp index 13b3cc3008..336022b1e2 100644 --- a/engines/agi/op_cmd.cpp +++ b/engines/agi/op_cmd.cpp @@ -730,7 +730,7 @@ void cmdStopSound(AgiGame *state, AgiEngine *vm, uint8 *parameter) { void cmdMenuInput(AgiGame *state, AgiEngine *vm, uint8 *parameter) { if (vm->getFlag(VM_FLAG_MENUS_ACCESSIBLE)) { - vm->_menu->delayedExecute(); + vm->_menu->delayedExecuteViaKeyboard(); } } -- cgit v1.2.3