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  if (curpos == 0)
112  return; // There is no previous element
113 
114  int nextloc = curpos - 1;
115  while (nextloc != (int)curpos) {
116  if (nextloc < 0)
117  nextloc = _element.size() - 1;
118 
119  if (_element[nextloc]._visible == true) {
120  _hoverIndex = _path[nextloc];
121  break;
122  }
123 
124  nextloc--;
125  }
126  }
127  }
128 
129  //------------------------------------------------------------------------
130  // Purpose: Handle keyboard input
131  //------------------------------------------------------------------------
132  int handleKeyboard(const Common::Event &event) {
133  using namespace pyrodactyl::input;
134 
135  if (g_engine->_inputManager->getKeyBindingMode() != KBM_UI) {
136  g_engine->_inputManager->setKeyBindingMode(KBM_UI);
137  }
138 
139  if (!_element.empty()) {
140  if (_pathType != PATH_HORIZONTAL) {
141  if (g_engine->_inputManager->state(IU_DOWN)) {
142  next();
143  _latestInput = KEYBOARD;
144  } else if (g_engine->_inputManager->state(IU_UP)) {
145  prev();
146  _latestInput = KEYBOARD;
147  }
148  }
149 
150  if (_pathType != PATH_VERTICAL) {
151  if (g_engine->_inputManager->state(IU_RIGHT)) {
152  next();
153  _latestInput = KEYBOARD;
154  } else if (g_engine->_inputManager->state(IU_LEFT)) {
155  prev();
156  _latestInput = KEYBOARD;
157  }
158  }
159 
160  if (g_engine->_inputManager->state(IU_ACCEPT) && _hoverIndex != -1)
161  return _hoverIndex;
162 
163  // We pressed a key, which means we have to update the hovering status
164  if (_latestInput == KEYBOARD) {
165  // Update hover status of keys according to the current index
166  int i = 0;
167  for (auto it = _element.begin(); it != _element.end(); ++it, ++i)
168  it->_hoverKey = (i == _hoverIndex);
169  }
170  }
171 
172  return -1;
173  }
174 
175 public:
176  // The collection of buttons in the menu
177  Common::Array<T> _element;
178 
179  Menu() {
180  _hoverIndex = -1;
181  _useKeyboard = false;
182  _latestInput = MOUSE;
183  _pathType = PATH_DEFAULT;
184  }
185  ~Menu() {}
186 
187  void reset() {
188  _latestInput = MOUSE;
189  _hoverIndex = -1;
190  for (auto &b : _element)
191  b.reset();
192  }
193 
194  void setUI() {
195  for (auto &i : _element)
196  i.setUI();
197  }
198 
199  //------------------------------------------------------------------------
200  // Purpose: Load the menu from a file
201  //------------------------------------------------------------------------
202  void load(rapidxml::xml_node<char> *node) {
203  if (nodeValid(node)) {
204  for (auto n = node->first_node(); n != nullptr; n = n->next_sibling()) {
205  T b;
206  b.load(n);
207  _element.push_back(b);
208  }
209 
210  loadBool(_useKeyboard, "keyboard", node, false);
211  assignPaths();
212  }
213  }
214 
215  //------------------------------------------------------------------------
216  // Purpose: Event Handling
217  // The reason this function doesn't declare its own Event object is because
218  // a menu might not be the only object in a game state
219  //------------------------------------------------------------------------
220  int handleEvents(const Common::Event &event, const int &xOffset = 0, const int &yOffset = 0) {
221  // The keyboard/joystick event handling bit
222  if (_useKeyboard) {
223  int result = handleKeyboard(event);
224 
225  // We have accepted a menu option using the keyboard
226  if (result != -1) {
227  // Reset the menu state
228  reset();
229  g_engine->_inputManager->setKeyBindingMode(pyrodactyl::input::KBM_GAME);
230  return result;
231  }
232  }
233 
234  // Check if we have moved or clicked the mouse
235  if (Common::isMouseEvent(event)) {
236  // Since the player is moving the mouse, we have to recalculate hover index at every opportunity
237  _hoverIndex = -1;
238  _latestInput = MOUSE;
239  }
240 
241  // The mouse and hotkey event handling bit
242  int i = 0;
243  for (auto it = _element.begin(); it != _element.end(); ++it, ++i) {
244  // We clicked on a button using the mouse
245  if (it->handleEvents(event, xOffset, yOffset) == BUAC_LCLICK) {
246  // Reset the menu state
247  reset();
248  g_engine->_inputManager->setKeyBindingMode(pyrodactyl::input::KBM_GAME);
249  return i;
250  }
251 
252  // We did not click a button, however we did hover over the button
253  // However if we are use keyboard to browse through the menu, hovering is forgotten until we move the mouse again
254  if (it->_hoverMouse && _latestInput == MOUSE) {
255  _hoverIndex = i;
256 
257  // The latest input is the mouse, which means we have to forget the keyboard hover states
258  for (auto &e : _element)
259  e._hoverKey = false;
260  }
261  }
262 
263  if (_latestInput == KEYBOARD) {
264  // The latest input is the keyboard, which means we have to forget the mouse hover states
265  for (auto &it : _element)
266  it._hoverMouse = false;
267  }
268  return -1;
269  }
270 
271  //------------------------------------------------------------------------
272  // Purpose: Draw the menu
273  //------------------------------------------------------------------------
274  void draw(const int &XOffset = 0, const int &YOffset = 0) {
275  for (auto &it : _element)
276  it.draw(XOffset, YOffset);
277  }
278 
279  //------------------------------------------------------------------------
280  // Purpose: Get info about the menu
281  //------------------------------------------------------------------------
282  void useKeyboard(const bool &val) {
283  _useKeyboard = val;
284  }
285 
286  void clear() {
287  _element.clear();
288  }
289 
290  int hoverIndex() {
291  return _hoverIndex;
292  }
293 
294  //------------------------------------------------------------------------
295  // Purpose: Assign traversal paths
296  //------------------------------------------------------------------------
297  void assignPaths() {
298  _path.clear();
299 
300  // These variables are used to see if the X and Y values of buttons are the same or not
301  // Used to determine the path type of the menu
302  bool sameX = true, sameY = true;
303 
304  if (!_element.empty()) {
305  _path.push_back(0);
306 
307  for (uint i = 1; i < _element.size(); i++) {
308  _path.push_back(i);
309 
310  int prevX = _element[i - 1].x;
311  int prevY = _element[i - 1].y;
312 
313  if (sameX && _element[i].x != prevX)
314  sameX = false;
315 
316  if (sameY && _element[i].y != prevY)
317  sameY = false;
318  }
319  }
320 
321  if (sameX) {
322  if (sameY)
323  _pathType = PATH_DEFAULT;
324  else
325  _pathType = PATH_VERTICAL;
326  } else if (sameY)
327  _pathType = PATH_HORIZONTAL;
328  else
329  _pathType = PATH_DEFAULT;
330  }
331 };
332 
333 // A menu with simple buttons
334 typedef Menu<Button> ButtonMenu;
335 } // End of namespace ui
336 } // End of namespace pyrodactyl
337 
338 } // End of namespace Crab
339 
340 #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