v1.1.0
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Groups Pages
public.h
1 /* -*- mode: C; c-basic-offset: 4; intent-tabs-mode: nil -*-
2  *
3  * This file is part of the public interface to the Sifteo SDK.
4  * Copyright <c> 2012 Sifteo, Inc. All rights reserved.
5  */
6 
7 #pragma once
8 #ifdef NOT_USERSPACE
9 # error This is a userspace-only header, not allowed by the current build.
10 #endif
11 
12 #include <sifteo/menu/types.h>
13 
14 namespace Sifteo {
15 
21 inline Menu::Menu(VideoBuffer &vid, const MenuAssets *aAssets, MenuItem *aItems)
22 {
23  init(vid, aAssets, aItems);
24 }
25 
26 inline void Menu::init(VideoBuffer &vid, const MenuAssets *aAssets, MenuItem *aItems)
27 {
28  this->vid = &vid;
29  hasBeenStarted = false;
30 
31  currentEvent.type = MENU_UNEVENTFUL;
32  items = aItems;
33  assets = aAssets;
34  changeState(MENU_STATE_START);
35 
36  // initialize instance constants
37  kHeaderHeight = 0;
38  kFooterHeight = 0;
39  kIconTileWidth = 0;
40 
41  // calculate the number of items
42  uint8_t i = 0;
43  while (items[i].icon != NULL) {
44  if (kIconTileWidth == 0) {
45  kIconTileWidth = items[i].icon->tileWidth();
46  kIconTileHeight = items[i].icon->tileHeight();
47  kEndCapPadding = (kNumVisibleTilesX - kIconTileWidth) * (TILE / 2.f);
48  ASSERT((kItemPixelWidth() - kIconPixelWidth()) % TILE == 0);
49  } else {
50  ASSERT(items[i].icon->tileWidth() == kIconTileWidth);
51  ASSERT(items[i].icon->tileHeight() == kIconTileHeight);
52  }
53 
54  i++;
55  if (items[i].label != NULL) {
56  ASSERT(items[i].label->tileWidth() == kNumVisibleTilesX);
57  if (kHeaderHeight == 0) {
58  kHeaderHeight = items[i].label->tileHeight();
59  } else {
60  ASSERT(items[i].label->tileHeight() == kHeaderHeight);
61  }
62  /* XXX: if there are any labels, a header is required for now.
63  * supporting labels and no header would require toggling bg0
64  * tiles fairly often and that's state I don't want to deal with
65  * for the time being.
66  * workaround: header can be an appropriately-sized, entirely
67  * transparent PNG.
68  */
69  ASSERT(assets->header != NULL);
70  }
71  }
72  numItems = i;
73 
74  // calculate the number of tips
75  i = 0;
76  while (assets->tips[i] != NULL) {
77  ASSERT(assets->tips[i]->tileWidth() == kNumVisibleTilesX);
78  if (kFooterHeight == 0) {
79  kFooterHeight = assets->tips[i]->tileHeight();
80  } else {
81  ASSERT(assets->tips[i]->tileHeight() == kFooterHeight);
82  }
83  i++;
84  }
85  numTips = i;
86 
87  // sanity check the rest of the assets
88  ASSERT(assets->background);
89  ASSERT(assets->background->tileWidth() == 1 && assets->background->tileHeight() == 1);
90  if (assets->footer) {
91  ASSERT(assets->footer->tileWidth() == kNumVisibleTilesX);
92  if (kFooterHeight == 0) {
93  kFooterHeight = assets->footer->tileHeight();
94  } else {
95  ASSERT(assets->footer->tileHeight() == kFooterHeight);
96  }
97  }
98 
99  if (assets->header) {
100  ASSERT(assets->header->tileWidth() == kNumVisibleTilesX);
101  if (kHeaderHeight == 0) {
102  kHeaderHeight = assets->header->tileHeight();
103  } else {
104  ASSERT(assets->header->tileHeight() == kHeaderHeight);
105  }
106  }
107 
108  prev_ut = 0;
109  startingItem = 0;
110 
111  position = 0.0f;
112 
113  setIconYOffset(kDefaultIconYOffset);
114  setPeekTiles(kDefaultPeekTiles);
115 }
116 
117 inline VideoBuffer * Menu::videoBuffer() const
118 {
119  return vid;
120 }
121 
122 inline CubeID Menu::cube() const
123 {
124  return vid ? vid->cube() : CubeID();
125 }
126 
127 inline bool Menu::pollEvent(struct MenuEvent *ev)
128 {
129  // Events not handled at this point are discarded
130  ASSERT(currentEvent.type != MENU_PREPAINT);
131  clearEvent();
132 
133  /* state changes can happen in the default event handler which may dispatch
134  * events (like MENU_STATE_STATIC -> MENU_STATE_FINISH dispatches a
135  * MENU_PREPAINT).
136  */
137  if (dispatchEvent(ev)) {
138  return (ev->type != MENU_EXIT);
139  }
140 
141  // keep track of time so if our framerate changes, apparent speed persists
142  frameclock.next();
143 
144  // neighbor changes?
145  if (currentState != MENU_STATE_START) {
146  detectNeighbors();
147  }
148  if (dispatchEvent(ev)) {
149  return (ev->type != MENU_EXIT);
150  }
151 
152  // update commonly-used data
153  const float kAccelScalingFactor = -0.25f;
154  accel = kAccelScalingFactor * vid->virtualAccel().xy();
155 
156  // state changes
157  switch (currentState) {
158  case MENU_STATE_START:
159  transFromStart();
160  break;
161  case MENU_STATE_STATIC:
162  transFromStatic();
163  break;
164  case MENU_STATE_TILTING:
165  transFromTilting();
166  break;
167  case MENU_STATE_INERTIA:
168  transFromInertia();
169  break;
170  case MENU_STATE_FINISH:
171  transFromFinish();
172  break;
173  case MENU_STATE_HOP_UP:
174  transFromHopUp();
175  break;
176  case MENU_STATE_PAN_TARGET:
177  transFromPanTarget();
178  break;
179  }
180  if (dispatchEvent(ev)) {
181  return (ev->type != MENU_EXIT);
182  }
183 
184  // run loop
185  switch (currentState) {
186  case MENU_STATE_START:
187  stateStart();
188  break;
189  case MENU_STATE_STATIC:
190  stateStatic();
191  break;
192  case MENU_STATE_TILTING:
193  stateTilting();
194  break;
195  case MENU_STATE_INERTIA:
196  stateInertia();
197  break;
198  case MENU_STATE_FINISH:
199  stateFinish();
200  break;
201  case MENU_STATE_HOP_UP:
202  stateHopUp();
203  break;
204  case MENU_STATE_PAN_TARGET:
205  statePanTarget();
206  break;
207  }
208  if (dispatchEvent(ev)) {
209  return (ev->type != MENU_EXIT);
210  }
211 
212  // no special events, paint a frame.
213  drawFooter();
214  currentEvent.type = MENU_PREPAINT;
215  dispatchEvent(ev);
216  return true;
217 }
218 
219 inline void Menu::reset()
220 {
221  changeState(MENU_STATE_START);
222 }
223 
224 inline void Menu::replaceIcon(uint8_t item, const AssetImage *icon, const AssetImage *label)
225 {
226  ASSERT(item < numItems);
227 
228  items[item].icon = icon;
229  for (int i = prev_ut; i < prev_ut + kNumTilesX; i++)
230  if (itemVisibleAtCol(item, i))
231  drawColumn(i);
232 
233  if (label) {
234  uint8_t currentItem = computeSelected();
235  items[item].label = label;
236 
237  if (kHeaderHeight && currentState == MENU_STATE_STATIC &&
238  currentItem == item)
239  {
240  const AssetImage& label = items[currentItem].label
241  ? *items[currentItem].label
242  : *assets->header;
243  vid->bg1.image(vec(0,0), label);
244  }
245  }
246 }
247 
248 inline bool Menu::itemVisible(uint8_t item)
249 {
250  ASSERT(item >= 0 && item < numItems);
251 
252  for (int i = MAX(0, prev_ut); i < prev_ut + kNumTilesX; i++) {
253  if (itemVisibleAtCol(item, i)) return true;
254  }
255  return false;
256 }
257 
258 inline void Menu::setIconYOffset(uint8_t px)
259 {
260  ASSERT(px >= 0 || px < kNumTilesX * 8);
261  kIconYOffset = -px;
262  if (hasBeenStarted)
263  updateBG0();
264 }
265 
266 inline void Menu::setPeekTiles(uint8_t numTiles)
267 {
268  ASSERT(numTiles >= 1 || numTiles * 2 < kNumTilesX);
269  kPeekTiles = numTiles;
270  if (hasBeenStarted)
271  updateBG0();
272 }
273 
282 inline void Menu::anchor(uint8_t item, bool hopUp, int8_t panTarget)
283 {
284  ASSERT(item < numItems);
285  startingItem = item;
286  targetItem = panTarget;
287 
288  if (hopUp) {
289  position = stoppingPositionFor(startingItem);
290  prev_ut = computeCurrentTile() + kNumTilesX;
291  updateBG0();
292 
293  changeState(MENU_STATE_HOP_UP);
294  }
295 }
296 
297 inline MenuState Menu::getState()
298 {
299  return currentState;
300 }
301 
302 inline bool Menu::isTilted()
303 {
304  const float robustThreshold = kAccelThresholdOn * 1.3;
305  return (abs(accel.x) >= robustThreshold); // avoids accel. noise
306 }
307 
308 inline bool Menu::isHorizontal()
309 {
310  const float robustThreshold = kAccelThresholdOn * 0.3;
311  return (abs(accel.x) < robustThreshold); // avoids accel. noise
312 }
313 
314 inline bool Menu::isAtEdge()
315 {
316  uint8_t item = computeSelected();
317  return (item == 0 || item == numItems - 1);
318 }
319 
320 inline bool Menu::isTiltingAtEdge()
321 {
322  /*
323  * if we're tilting up against either the beginning or the end of the menu,
324  * there are no more items to navigate to.
325  */
326  uint8_t item = computeSelected();
327  int8_t direction = accel.x > 0 ? 1 : -1;
328 
329  return ((direction < 0 && item == 0) ||
330  (direction > 0 && item == numItems - 1));
331 }
332 
333 inline void Menu::setNumTips(uint8_t nt)
334 {
335  ASSERT(nt >= 0);
336 
337  numTips = nt;
338 }
339 
340 inline int Menu::getCurrentTip()
341 {
342  ASSERT(currentTip < numTips);
343 
344  return currentTip;
345 }
346 
347 
352 }; // namespace Sifteo
353