v1.0.0
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Groups Pages
states.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 /*
8  * States are represented by three functions per state:
9  * transTo$TATE() - called whenever transitioning into the state.
10  * state$TATE() - the state itself, called every loop of the event pump.
11  * transFrom$TATE() - called before every loop of the event pump, responsible
12  * for transitioning to other states.
13  *
14  * States are identified by MENU_STATE_$TATE values in enum MenuState, and are
15  * mapped to their respective functions in changeState and pollEvent.
16  */
17 
18 #pragma once
19 #ifdef NOT_USERSPACE
20 # error This is a userspace-only header, not allowed by the current build.
21 #endif
22 
23 #include <sifteo/menu/types.h>
24 
25 namespace Sifteo {
26 
32 inline void Menu::changeState(MenuState newstate)
33 {
34  stateFinished = false;
35  currentState = newstate;
36 
37  MENU_LOG("STATE: -> ");
38  switch(currentState) {
39  case MENU_STATE_START:
40  MENU_LOG("start\n");
41  transToStart();
42  break;
43  case MENU_STATE_STATIC:
44  MENU_LOG("static\n");
45  transToStatic();
46  break;
47  case MENU_STATE_TILTING:
48  MENU_LOG("tilting\n");
49  transToTilting();
50  break;
51  case MENU_STATE_INERTIA:
52  MENU_LOG("inertia\n");
53  transToInertia();
54  break;
55  case MENU_STATE_FINISH:
56  MENU_LOG("finish\n");
57  transToFinish();
58  break;
59  case MENU_STATE_HOP_UP:
60  MENU_LOG("hop up\n");
61  transToHopUp();
62  break;
63  }
64 }
65 
76 inline void Menu::transToStart()
77 {
78  // Do nothing
79 }
80 
81 inline void Menu::stateStart()
82 {
83  // initialize video state
84 
85  vid->initMode(BG0_SPR_BG1);
86  vid->bg0.erase(*assets->background);
87 
88  // Allocate tiles for the static upper label, and draw it.
89  if (kHeaderHeight) {
90  const AssetImage& label = items[startingItem].label ? *items[startingItem].label : *assets->header;
91  vid->bg1.fillMask(vec(0,0), label.tileSize());
92  vid->bg1.image(vec(0,0), label);
93  }
94 
95  // Allocate tiles for the footer, and draw it.
96  if (kFooterHeight) {
97  const AssetImage& footer = assets->tips[0] ? *assets->tips[0] : *assets->footer;
98  Int2 topLeft = { 0, kNumVisibleTilesY - footer.tileHeight() };
99  vid->bg1.fillMask(topLeft, footer.tileSize());
100  vid->bg1.image(topLeft, footer);
101  }
102 
103  currentTip = 0;
104  prevTipTime = SystemTime::now();
105  drawFooter(true);
106 
107  // if/when we start animating the menu into existence, set this once the work is complete
108  stateFinished = true;
109 }
110 
111 inline void Menu::transFromStart()
112 {
113  if (stateFinished) {
114  hasBeenStarted = true;
115 
116  position = stoppingPositionFor(startingItem);
117  prev_ut = computeCurrentTile() + kNumTilesX;
118  updateBG0();
119 
120  for(int i = 0; i < NUM_SIDES; i++) {
121  neighbors[i].neighborSide = NO_SIDE;
122  neighbors[i].neighbor = CubeID::UNDEFINED;
123  neighbors[i].masterSide = NO_SIDE;
124  }
125 
126  changeState(MENU_STATE_STATIC);
127  }
128 }
129 
142 inline void Menu::transToStatic()
143 {
144  velocity = 0;
145  prevTouch = vid->cube().isTouching();
146 
147  currentEvent.type = MENU_ITEM_ARRIVE;
148  currentEvent.item = computeSelected();
149 
150  // show the title of the item
151  if (kHeaderHeight) {
152  const AssetImage& label = items[currentEvent.item].label ? *items[currentEvent.item].label : *assets->header;
153  vid->bg1.image(vec(0,0), label);
154  }
155 }
156 
157 inline void Menu::stateStatic()
158 {
159  checkForPress();
160 }
161 
162 inline void Menu::transFromStatic()
163 {
164  if (abs(accel.x) < kAccelThresholdOn)
165  return;
166 
167  /*
168  * if we're tilting up against either the beginning or the end of the menu,
169  * don't generate a new event - there are no more items to navigate to.
170  */
171  int8_t direction = accel.x > 0 ? 1 : -1;
172  if ((currentEvent.item == 0 && direction < 0) ||
173  (currentEvent.item == numItems - 1 && direction > 0))
174  return;
175 
176  changeState(MENU_STATE_TILTING);
177 
178  currentEvent.type = MENU_ITEM_DEPART;
179  currentEvent.direction = direction;
180 
181  // hide header
182  if (kHeaderHeight) {
183  const AssetImage& label = *assets->header;
184  vid->bg1.image(vec(0,0), label);
185  }
186 }
187 
198 inline void Menu::transToTilting()
199 {
200  ASSERT(abs(accel.x) > kAccelThresholdOn);
201 }
202 
203 inline void Menu::stateTilting()
204 {
205  // normal scrolling
206  const int max_x = stoppingPositionFor(numItems - 1);
207  const float kInertiaThreshold = 10.f;
208 
209  velocity += (accel.x * frameclock.delta() * kTimeDilator) * velocityMultiplier();
210 
211  // clamp maximum velocity based on cube angle
212  if (abs(velocity) > maxVelocity()) {
213  velocity = (velocity < 0 ? 0 - maxVelocity() : maxVelocity());
214  }
215 
216  // don't go past the backstop unless we have inertia
217  if ((position > 0.f && velocity < 0) || (position < max_x && velocity > 0) || abs(velocity) > kInertiaThreshold) {
218  position += velocity * frameclock.delta() * kTimeDilator;
219  } else {
220  velocity = 0;
221  }
222  updateBG0();
223 }
224 
225 inline void Menu::transFromTilting()
226 {
227  const bool outOfBounds = (position < -0.05f) || (position > kItemPixelWidth()*(numItems-1) + kEndCapPadding + 0.05f);
228  if (abs(accel.x) < kAccelThresholdOff || outOfBounds) {
229  changeState(MENU_STATE_INERTIA);
230  }
231 }
232 
244 inline void Menu::transToInertia()
245 {
246  stopping_position = stoppingPositionFor(computeSelected());
247  if (abs(accel.x) > kAccelThresholdOff) {
248  tiltDirection = (kAccelScalingFactor * accel.x < 0) ? 1 : -1;
249  } else {
250  tiltDirection = 0;
251  }
252 }
253 
254 inline void Menu::stateInertia()
255 {
256  checkForPress();
257 
258  const float stiffness = 0.333f;
259 
260  // do not pull to item unless tilting has stopped.
261  if (abs(accel.x) < kAccelThresholdOff) {
262  tiltDirection = 0;
263  }
264  // if still tilting, do not bounce back to the stopping position.
265  if ((tiltDirection < 0 && velocity >= 0.f) || (tiltDirection > 0 && velocity <= 0.f)) {
266  return;
267  }
268 
269  velocity += stopping_position - position;
270  velocity *= stiffness;
271  position += velocity * frameclock.delta() * kTimeDilator;
272  position = lerp(position, stopping_position, 0.15f);
273 
274  stateFinished = abs(velocity) < 1.0f && abs(stopping_position - position) < 0.5f;
275  if (stateFinished) {
276  // prevent being off by one pixel when we stop
277  position = stopping_position;
278  }
279 
280  updateBG0();
281 }
282 
283 inline void Menu::transFromInertia()
284 {
285  if (abs(accel.x) > kAccelThresholdOn &&
286  !((tiltDirection < 0 && accel.x < 0.f) || (tiltDirection > 0 && accel.x > 0.f))) {
287  changeState(MENU_STATE_TILTING);
288  }
289  if (stateFinished) { // stateFinished formerly doneTilting
290  changeState(MENU_STATE_STATIC);
291  }
292 }
293 
305 inline void Menu::transToFinish()
306 {
307  // Prepare screen for item animation
308 
309  // We're about to switch things up in VRAM, make sure the cubes are done drawing.
310  System::finish();
311 
312  // blank out the background layer
313  vid->bg0.setPanning(vec(0, 0));
314  vid->bg0.erase(*assets->background);
315 
316  if (assets->header) {
317  Int2 vec = {0, 0};
318  vid->bg0.image(vec, *assets->header);
319  }
320  if (assets->footer) {
321  Int2 vec = { 0, kNumVisibleTilesY - assets->footer->tileHeight() };
322  vid->bg0.image(vec, *assets->footer);
323  }
324  {
325  const AssetImage* icon = items[computeSelected()].icon;
326  vid->bg1.eraseMask();
327  vid->bg1.fillMask(vec(0,0), icon->tileSize());
328  vid->bg1.image(vec(0,0), *icon);
329  }
330  finishIteration = 0;
331  currentEvent.type = MENU_PREPAINT;
332 }
333 
334 inline void Menu::stateFinish()
335 {
336  const float k = 5.f;
337  int offset = 0;
338 
339  finishIteration++;
340  float u = finishIteration/33.f;
341  u = (1.f-k*u);
342  offset = int(12*(1.f-u*u));
343  vid->bg1.setPanning(vec(-kEndCapPadding, offset + kIconYOffset));
344  currentEvent.type = MENU_PREPAINT;
345 
346  if (offset <= -128) {
347  currentEvent.type = MENU_EXIT;
348  currentEvent.item = computeSelected();
349  stateFinished = true;
350  }
351 }
352 
353 inline void Menu::transFromFinish()
354 {
355  if (stateFinished) {
356  // We already animated ourselves out of a job. If we're being called again, reset the menu
357  changeState(MENU_STATE_START);
358 
359  // And re-run the first iteraton of the event loop
360  MenuEvent ignore;
361  pollEvent(&ignore);
362  /* currentEvent will be set by this second iteration of pollEvent,
363  * so when we return from this function the currentEvent will be
364  * propagated back to pollEvent's parameter.
365  */
366  }
367 }
368 
378 inline void Menu::transToHopUp()
379 {
380  // blank out the background layer
381  vid->initMode(BG0_SPR_BG1);
382  vid->bg0.setPanning(vec(0, 0));
383  vid->bg0.erase(*assets->background);
384 
385  if (assets->header) {
386  Int2 vec = {0, 0};
387  vid->bg0.image(vec, *assets->header);
388  }
389  if (assets->footer) {
390  Int2 vec = { 0, kNumVisibleTilesY - assets->footer->tileHeight() };
391  vid->bg0.image(vec, *assets->footer);
392  }
393  {
394  const AssetImage* icon = items[computeSelected()].icon;
395  vid->bg1.eraseMask();
396  vid->bg1.fillMask(vec(0,0), icon->tileSize());
397  vid->bg1.image(vec(0,0), *icon);
398  }
399  finishIteration = 30;
400 
401  hasBeenStarted = true;
402 }
403 
404 inline void Menu::stateHopUp()
405 {
406  const float k = 5.f;
407  int offset = 0;
408 
409  finishIteration--;
410  float u = finishIteration/33.f;
411  u = (1.f-k*u);
412  offset = int(12*(1.f-u*u));
413  vid->bg1.setPanning(vec(-kEndCapPadding, offset + kIconYOffset));
414  currentEvent.type = MENU_PREPAINT;
415 
416  if (offset >= 0) {
417  stateFinished = true;
418  }
419 }
420 
421 inline void Menu::transFromHopUp()
422 {
423  if (stateFinished) {
424  // We're done with the animation, so jump right into the menu as normal.
425  changeState(MENU_STATE_START);
426 
427  // And re-run the first iteraton of the event loop
428  MenuEvent ignore;
429  pollEvent(&ignore);
430  /* currentEvent will be set by this second iteration of pollEvent,
431  * so when we return from this function the currentEvent will be
432  * propagated back to pollEvent's parameter.
433  */
434  }
435 }
436 
441 }; // namespace Sifteo