ScummVM API documentation
menu.h
1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program. If not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 /*
23  * This code is based on the CRAB engine
24  *
25  * Copyright (c) Arvind Raja Yadav
26  *
27  * Licensed under MIT
28  *
29  */
30 
31 //=============================================================================
32 // Author: Arvind
33 // Purpose: Menu class
34 //=============================================================================
35 #ifndef CRAB_MENU_H
36 #define CRAB_MENU_H
37 
38 #include "crab/crab.h"
39 #include "crab/metaengine.h"
40 #include "crab/ui/button.h"
41 
42 namespace Crab {
43 
44 namespace pyrodactyl {
45 namespace ui {
46 template<typename T>
47 class Menu {
48 protected:
49  // The index of current selected option and highlighted option
50  int _hoverIndex;
51 
52  // The order in which a keyboard or gamepad traverses the menu
53  Common::Array<uint> _path;
54 
55  // Are keyboard buttons enabled?
56  bool _useKeyboard;
57 
58  // Has a key been pressed?
59  enum InputDevice {
60  KEYBOARD,
61  MOUSE
62  } _latestInput;
63 
64  // Do the paths use horizontal, vertical or both types of input for keyboard traversal
65  enum PathType {
66  PATH_DEFAULT,
67  PATH_HORIZONTAL,
68  PATH_VERTICAL
69  } _pathType;
70 
71  //------------------------------------------------------------------------
72  // Purpose: Find the next element in our path
73  //------------------------------------------------------------------------
74  void next() {
75  if (_hoverIndex == -1) {
76  for (uint pos = 0; pos < _path.size(); pos++)
77  if (_element[_path[pos]]._visible == true) {
78  _hoverIndex = _path[pos];
79  break;
80  }
81  } else {
82  uint curpos = 0;
83  for (; curpos < _path.size(); curpos++)
84  if ((int)_path[curpos] == _hoverIndex)
85  break;
86 
87  for (uint nextloc = (curpos + 1) % _element.size(); nextloc != curpos; nextloc = (nextloc + 1) % _element.size())
88  if (_element[nextloc]._visible == true) {
89  _hoverIndex = _path[nextloc];
90  break;
91  }
92  }
93  }
94 
95  //------------------------------------------------------------------------
96  // Purpose: Find the previous element in our path
97  //------------------------------------------------------------------------
98  void prev() {
99  if (_hoverIndex == -1) {
100  for (uint pos = 0; pos < _path.size(); pos++)
101  if (_element[_path[pos]]._visible == true) {
102  _hoverIndex = _path[pos];
103  break;
104  }
105  } else {
106  uint curpos = 0;
107  for (; curpos < _path.size(); curpos++)
108  if ((int)_path[curpos] == _hoverIndex)
109  break;
110 
111  int nextloc = curpos - 1;
112  while (nextloc != (int)curpos) {
113  if (nextloc < 0)
114  nextloc = _element.size() - 1;
115 
116  if (_element[nextloc]._visible == true) {
117  _hoverIndex = _path[nextloc];
118  break;
119  }
120 
121  nextloc--;
122  }
123  }
124  }
125 
126  //------------------------------------------------------------------------
127  // Purpose: Handle keyboard input
128  //------------------------------------------------------------------------
129  int handleKeyboard(const Common::Event &event) {
130  using namespace pyrodactyl::input;
131 
132  if (g_engine->_inputManager->getKeyBindingMode() != KBM_UI) {
133  g_engine->_inputManager->setKeyBindingMode(KBM_UI);
134  }
135 
136  if (!_element.empty()) {
137  if (_pathType != PATH_HORIZONTAL) {
138  if (g_engine->_inputManager->state(IU_DOWN)) {
139  next();
140  _latestInput = KEYBOARD;
141  } else if (g_engine->_inputManager->state(IU_UP)) {
142  prev();
143  _latestInput = KEYBOARD;
144  }
145  }
146 
147  if (_pathType != PATH_VERTICAL) {
148  if (g_engine->_inputManager->state(IU_RIGHT)) {
149  next();
150  _latestInput = KEYBOARD;
151  } else if (g_engine->_inputManager->state(IU_LEFT)) {
152  prev();
153  _latestInput = KEYBOARD;
154  }
155  }
156 
157  if (g_engine->_inputManager->state(IU_ACCEPT) && _hoverIndex != -1)
158  return _hoverIndex;
159 
160  // We pressed a key, which means we have to update the hovering status
161  if (_latestInput == KEYBOARD) {
162  // Update hover status of keys according to the current index
163  int i = 0;
164  for (auto it = _element.begin(); it != _element.end(); ++it, ++i)
165  it->_hoverKey = (i == _hoverIndex);
166  }
167  }
168 
169  return -1;
170  }
171 
172 public:
173  // The collection of buttons in the menu
174  Common::Array<T> _element;
175 
176  Menu() {
177  _hoverIndex = -1;
178  _useKeyboard = false;
179  _latestInput = MOUSE;
180  _pathType = PATH_DEFAULT;
181  }
182  ~Menu() {}
183 
184  void reset() {
185  _latestInput = MOUSE;
186  _hoverIndex = -1;
187  for (auto &b : _element)
188  b.reset();
189  }
190 
191  void setUI() {
192  for (auto &i : _element)
193  i.setUI();
194  }
195 
196  //------------------------------------------------------------------------
197  // Purpose: Load the menu from a file
198  //------------------------------------------------------------------------
199  void load(rapidxml::xml_node<char> *node) {
200  if (nodeValid(node)) {
201  for (auto n = node->first_node(); n != nullptr; n = n->next_sibling()) {
202  T b;
203  b.load(n);
204  _element.push_back(b);
205  }
206 
207  loadBool(_useKeyboard, "keyboard", node, false);
208  assignPaths();
209  }
210  }
211 
212  //------------------------------------------------------------------------
213  // Purpose: Event Handling
214  // The reason this function doesn't declare its own Event object is because
215  // a menu might not be the only object in a game state
216  //------------------------------------------------------------------------
217  int handleEvents(const Common::Event &event, const int &xOffset = 0, const int &yOffset = 0) {
218  // The keyboard/joystick event handling bit
219  if (_useKeyboard) {
220  int result = handleKeyboard(event);
221 
222  // We have accepted a menu option using the keyboard
223  if (result != -1) {
224  // Reset the menu state
225  reset();
226  g_engine->_inputManager->setKeyBindingMode(pyrodactyl::input::KBM_GAME);
227  return result;
228  }
229  }
230 
231  // Check if we have moved or clicked the mouse
232  if (Common::isMouseEvent(event)) {
233  // Since the player is moving the mouse, we have to recalculate hover index at every opportunity
234  _hoverIndex = -1;
235  _latestInput = MOUSE;
236  }
237 
238  // The mouse and hotkey event handling bit
239  int i = 0;
240  for (auto it = _element.begin(); it != _element.end(); ++it, ++i) {
241  // We clicked on a button using the mouse
242  if (it->handleEvents(event, xOffset, yOffset) == BUAC_LCLICK) {
243  // Reset the menu state
244  reset();
245  g_engine->_inputManager->setKeyBindingMode(pyrodactyl::input::KBM_GAME);
246  return i;
247  }
248 
249  // We did not click a button, however we did hover over the button
250  // However if we are use keyboard to browse through the menu, hovering is forgotten until we move the mouse again
251  if (it->_hoverMouse && _latestInput == MOUSE) {
252  _hoverIndex = i;
253 
254  // The latest input is the mouse, which means we have to forget the keyboard hover states
255  for (auto &e : _element)
256  e._hoverKey = false;
257  }
258  }
259 
260  if (_latestInput == KEYBOARD) {
261  // The latest input is the keyboard, which means we have to forget the mouse hover states
262  for (auto &it : _element)
263  it._hoverMouse = false;
264  }
265  return -1;
266  }
267 
268  //------------------------------------------------------------------------
269  // Purpose: Draw the menu
270  //------------------------------------------------------------------------
271  void draw(const int &XOffset = 0, const int &YOffset = 0) {
272  for (auto &it : _element)
273  it.draw(XOffset, YOffset);
274  }
275 
276  //------------------------------------------------------------------------
277  // Purpose: Get info about the menu
278  //------------------------------------------------------------------------
279  void useKeyboard(const bool &val) {
280  _useKeyboard = val;
281  }
282 
283  void clear() {
284  _element.clear();
285  }
286 
287  int hoverIndex() {
288  return _hoverIndex;
289  }
290 
291  //------------------------------------------------------------------------
292  // Purpose: Assign traversal paths
293  //------------------------------------------------------------------------
294  void assignPaths() {
295  _path.clear();
296 
297  // These variables are used to see if the X and Y values of buttons are the same or not
298  // Used to determine the path type of the menu
299  bool sameX = true, sameY = true;
300 
301  if (!_element.empty()) {
302  _path.push_back(0);
303 
304  for (uint i = 1; i < _element.size(); i++) {
305  _path.push_back(i);
306 
307  int prevX = _element[i - 1].x;
308  int prevY = _element[i - 1].y;
309 
310  if (sameX && _element[i].x != prevX)
311  sameX = false;
312 
313  if (sameY && _element[i].y != prevY)
314  sameY = false;
315  }
316  }
317 
318  if (sameX) {
319  if (sameY)
320  _pathType = PATH_DEFAULT;
321  else
322  _pathType = PATH_VERTICAL;
323  } else if (sameY)
324  _pathType = PATH_HORIZONTAL;
325  else
326  _pathType = PATH_DEFAULT;
327  }
328 };
329 
330 // A menu with simple buttons
331 typedef Menu<Button> ButtonMenu;
332 } // End of namespace ui
333 } // End of namespace pyrodactyl
334 
335 } // End of namespace Crab
336 
337 #endif // CRAB_MENU_H
bool isMouseEvent(const Event &event)
void clear()
Definition: array.h:320
Definition: menu.h:47
iterator end()
Definition: array.h:379
iterator begin()
Definition: array.h:374
Engine * g_engine
bool empty() const
Definition: array.h:351
void push_back(const T &element)
Definition: array.h:180
Definition: events.h:199
size_type size() const
Definition: array.h:315
Definition: moveeffect.h:37