v1.0.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  }
177  if (dispatchEvent(ev)) {
178  return (ev->type != MENU_EXIT);
179  }
180 
181  // run loop
182  switch (currentState) {
183  case MENU_STATE_START:
184  stateStart();
185  break;
186  case MENU_STATE_STATIC:
187  stateStatic();
188  break;
189  case MENU_STATE_TILTING:
190  stateTilting();
191  break;
192  case MENU_STATE_INERTIA:
193  stateInertia();
194  break;
195  case MENU_STATE_FINISH:
196  stateFinish();
197  break;
198  case MENU_STATE_HOP_UP:
199  stateHopUp();
200  break;
201  }
202  if (dispatchEvent(ev)) {
203  return (ev->type != MENU_EXIT);
204  }
205 
206  // no special events, paint a frame.
207  drawFooter();
208  currentEvent.type = MENU_PREPAINT;
209  dispatchEvent(ev);
210  return true;
211 }
212 
213 inline void Menu::reset()
214 {
215  changeState(MENU_STATE_START);
216 }
217 
218 inline void Menu::replaceIcon(uint8_t item, const AssetImage *icon, const AssetImage *label)
219 {
220  ASSERT(item < numItems);
221 
222  items[item].icon = icon;
223  for (int i = prev_ut; i < prev_ut + kNumTilesX; i++)
224  if (itemVisibleAtCol(item, i))
225  drawColumn(i);
226 
227  if (label) {
228  uint8_t currentItem = computeSelected();
229  items[item].label = label;
230 
231  if (kHeaderHeight && currentState == MENU_STATE_STATIC &&
232  currentItem == item)
233  {
234  const AssetImage& label = items[currentItem].label
235  ? *items[currentItem].label
236  : *assets->header;
237  vid->bg1.image(vec(0,0), label);
238  }
239  }
240 }
241 
242 inline bool Menu::itemVisible(uint8_t item)
243 {
244  ASSERT(item >= 0 && item < numItems);
245 
246  for (int i = MAX(0, prev_ut); i < prev_ut + kNumTilesX; i++) {
247  if (itemVisibleAtCol(item, i)) return true;
248  }
249  return false;
250 }
251 
252 inline void Menu::setIconYOffset(uint8_t px)
253 {
254  ASSERT(px >= 0 || px < kNumTilesX * 8);
255  kIconYOffset = -px;
256  if (hasBeenStarted)
257  updateBG0();
258 }
259 
260 inline void Menu::setPeekTiles(uint8_t numTiles)
261 {
262  ASSERT(numTiles >= 1 || numTiles * 2 < kNumTilesX);
263  kPeekTiles = numTiles;
264  if (hasBeenStarted)
265  updateBG0();
266 }
267 
276 inline void Menu::anchor(uint8_t item, bool hopUp)
277 {
278  ASSERT(item < numItems);
279  startingItem = item;
280 
281  if (hopUp) {
282  position = stoppingPositionFor(startingItem);
283  prev_ut = computeCurrentTile() + kNumTilesX;
284  updateBG0();
285 
286  changeState(MENU_STATE_HOP_UP);
287  }
288 }
289 
290 inline MenuState Menu::getState()
291 {
292  return currentState;
293 }
294 
299 }; // namespace Sifteo
300