v1.1.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  case MENU_STATE_PAN_TARGET:
64  MENU_LOG("pan target\n");
65  transToPanTarget();
66  break;
67  }
68 }
69 
80 inline void Menu::transToStart()
81 {
82  // Do nothing
83 }
84 
85 inline void Menu::stateStart()
86 {
87  // initialize video state
88 
89  vid->initMode(BG0_SPR_BG1);
90  vid->bg0.erase(*assets->background);
91 
92  // Allocate tiles for the static upper label, and draw it.
93  if (kHeaderHeight) {
94  const AssetImage& label = items[startingItem].label ? *items[startingItem].label : *assets->header;
95  vid->bg1.fillMask(vec(0,0), label.tileSize());
96  vid->bg1.image(vec(0,0), label);
97  }
98 
99  // Allocate tiles for the footer, and draw it.
100  if (kFooterHeight) {
101  const AssetImage& footer = assets->tips[0] ? *assets->tips[0] : *assets->footer;
102  Int2 topLeft = { 0, kNumVisibleTilesY - footer.tileHeight() };
103  vid->bg1.fillMask(topLeft, footer.tileSize());
104  vid->bg1.image(topLeft, footer);
105  }
106 
107  currentTip = 0;
108  prevTipTime = SystemTime::now();
109  drawFooter(true);
110 
111  // if/when we start animating the menu into existence, set this once the work is complete
112  stateFinished = true;
113 }
114 
115 inline void Menu::transFromStart()
116 {
117  if (stateFinished) {
118 
119  // garbage inexplicable appears here, often :P
120  vid->touch();
121  System::finish();
122 
123  hasBeenStarted = true;
124 
125  position = stoppingPositionFor(startingItem);
126  prev_ut = computeCurrentTile() + kNumTilesX;
127  updateBG0();
128 
129  for(int i = 0; i < NUM_SIDES; i++) {
130  neighbors[i].neighborSide = NO_SIDE;
131  neighbors[i].neighbor = CubeID::UNDEFINED;
132  neighbors[i].masterSide = NO_SIDE;
133  }
134 
135  changeState(
136  targetItem != -1 && startingItem != targetItem ?
137  MENU_STATE_PAN_TARGET :
138  MENU_STATE_STATIC
139  );
140 
141  }
142 }
143 
156 inline void Menu::transToStatic()
157 {
158  velocity = 0;
159  prevTouch = vid->cube().isTouching();
160 
161  currentEvent.type = MENU_ITEM_ARRIVE;
162  currentEvent.item = computeSelected();
163 
164  // show the title of the item
165  if (kHeaderHeight) {
166  const AssetImage& label = items[currentEvent.item].label ? *items[currentEvent.item].label : *assets->header;
167  vid->bg1.image(vec(0,0), label);
168  }
169 }
170 
171 inline void Menu::stateStatic()
172 {
173  checkForPress();
174 }
175 
176 inline void Menu::transFromStatic()
177 {
178  if (abs(accel.x) < kAccelThresholdOn || isTiltingAtEdge()) {
179  return;
180  }
181 
182  changeState(MENU_STATE_TILTING);
183 
184  currentEvent.type = MENU_ITEM_DEPART;
185  currentEvent.direction = accel.x > 0 ? 1 : -1;
186 
187  // hide header
188  if (kHeaderHeight) {
189  const AssetImage& label = *assets->header;
190  vid->bg1.image(vec(0,0), label);
191  }
192 }
193 
204 inline void Menu::transToTilting()
205 {
206  ASSERT(abs(accel.x) > kAccelThresholdOn);
207 }
208 
209 inline void Menu::stateTilting()
210 {
211  // normal scrolling
212  const int max_x = stoppingPositionFor(numItems - 1);
213  const float kInertiaThreshold = 10.f;
214 
215  velocity += (accel.x * frameclock.delta() * kTimeDilator) * velocityMultiplier();
216 
217  // clamp maximum velocity based on cube angle
218  if (abs(velocity) > maxVelocity()) {
219  velocity = (velocity < 0 ? 0 - maxVelocity() : maxVelocity());
220  }
221 
222  // don't go past the backstop unless we have inertia
223  if ((position > 0.f && velocity < 0) || (position < max_x && velocity > 0) || abs(velocity) > kInertiaThreshold) {
224  position += velocity * frameclock.delta() * kTimeDilator;
225  } else {
226  velocity = 0;
227  }
228  updateBG0();
229 }
230 
231 inline void Menu::transFromTilting()
232 {
233  const bool outOfBounds = (position < -0.05f) || (position > kItemPixelWidth()*(numItems-1) + kEndCapPadding + 0.05f);
234  if (abs(accel.x) < kAccelThresholdOff || outOfBounds) {
235  changeState(MENU_STATE_INERTIA);
236  }
237 }
238 
250 inline void Menu::transToInertia()
251 {
252  stopping_position = stoppingPositionFor(computeSelected());
253  if (abs(accel.x) > kAccelThresholdOff) {
254  tiltDirection = (kAccelScalingFactor * accel.x < 0) ? 1 : -1;
255  } else {
256  tiltDirection = 0;
257  }
258 }
259 
260 inline void Menu::stateInertia()
261 {
262  checkForPress();
263 
264  const float stiffness = 0.333f;
265 
266  // do not pull to item unless tilting has stopped.
267  if (abs(accel.x) < kAccelThresholdOff) {
268  tiltDirection = 0;
269  }
270  // if still tilting, do not bounce back to the stopping position.
271  if ((tiltDirection < 0 && velocity >= 0.f) || (tiltDirection > 0 && velocity <= 0.f)) {
272  return;
273  }
274 
275  velocity += stopping_position - position;
276  velocity *= stiffness;
277  position += velocity * frameclock.delta() * kTimeDilator;
278  position = lerp(position, stopping_position, 0.15f);
279 
280  stateFinished = abs(velocity) < 1.0f && abs(stopping_position - position) < 0.5f;
281  if (stateFinished) {
282  // prevent being off by one pixel when we stop
283  position = stopping_position;
284  }
285 
286  updateBG0();
287 }
288 
289 inline void Menu::transFromInertia()
290 {
291  if (abs(accel.x) > kAccelThresholdOn &&
292  !((tiltDirection < 0 && accel.x < 0.f) || (tiltDirection > 0 && accel.x > 0.f))) {
293  changeState(MENU_STATE_TILTING);
294  }
295  if (stateFinished) { // stateFinished formerly doneTilting
296  changeState(MENU_STATE_STATIC);
297  }
298 }
299 
311 inline void Menu::transToFinish()
312 {
313  // Prepare screen for item animation
314 
315  // We're about to switch things up in VRAM, make sure the cubes are done drawing.
316  System::finish();
317 
318  // blank out the background layer
319  vid->bg0.setPanning(vec(0, 0));
320  vid->bg0.erase(*assets->background);
321 
322  if (assets->header) {
323  Int2 vec = {0, 0};
324  vid->bg0.image(vec, *assets->header);
325  }
326  if (assets->footer) {
327  Int2 vec = { 0, kNumVisibleTilesY - assets->footer->tileHeight() };
328  vid->bg0.image(vec, *assets->footer);
329  }
330  {
331  const AssetImage* icon = items[computeSelected()].icon;
332  vid->bg1.eraseMask();
333  vid->bg1.fillMask(vec(0,0), icon->tileSize());
334  vid->bg1.image(vec(0,0), *icon);
335  }
336  finishIteration = 0;
337  currentEvent.type = MENU_PREPAINT;
338 }
339 
340 inline void Menu::stateFinish()
341 {
342  const float k = 5.f;
343  int offset = 0;
344 
345  finishIteration++;
346  float u = finishIteration/33.f;
347  u = (1.f-k*u);
348  offset = int(12*(1.f-u*u));
349  vid->bg1.setPanning(vec(-kEndCapPadding, offset + kIconYOffset));
350  currentEvent.type = MENU_PREPAINT;
351 
352  if (offset <= -128) {
353  currentEvent.type = MENU_EXIT;
354  currentEvent.item = computeSelected();
355  stateFinished = true;
356  }
357 }
358 
359 inline void Menu::transFromFinish()
360 {
361  if (stateFinished) {
362  // We already animated ourselves out of a job. If we're being called again, reset the menu
363  changeState(MENU_STATE_START);
364 
365  // And re-run the first iteraton of the event loop
366  MenuEvent ignore;
367  pollEvent(&ignore);
368  /* currentEvent will be set by this second iteration of pollEvent,
369  * so when we return from this function the currentEvent will be
370  * propagated back to pollEvent's parameter.
371  */
372  }
373 }
374 
384 inline void Menu::transToHopUp()
385 {
386  // blank out the background layer
387  vid->initMode(BG0_SPR_BG1);
388  vid->bg0.setPanning(vec(0, 0));
389  vid->bg0.erase(*assets->background);
390 
391  if (assets->header) {
392  Int2 vec = {0, 0};
393  vid->bg0.image(vec, *assets->header);
394  }
395  if (assets->footer) {
396  Int2 vec = { 0, kNumVisibleTilesY - assets->footer->tileHeight() };
397  vid->bg0.image(vec, *assets->footer);
398  }
399  {
400  const AssetImage* icon = items[computeSelected()].icon;
401  vid->bg1.eraseMask();
402  vid->bg1.fillMask(vec(0,0), icon->tileSize());
403  vid->bg1.image(vec(0,0), *icon);
404  }
405  finishIteration = 30;
406 
407  hasBeenStarted = true;
408 }
409 
410 inline void Menu::stateHopUp()
411 {
412  const float k = 5.f;
413  int offset = 0;
414 
415  finishIteration--;
416  float u = finishIteration/33.f;
417  u = (1.f-k*u);
418  offset = int(12*(1.f-u*u));
419  vid->bg1.setPanning(vec(-kEndCapPadding, offset + kIconYOffset));
420  currentEvent.type = MENU_PREPAINT;
421 
422  if (offset >= 0) {
423  stateFinished = true;
424  }
425 }
426 
427 inline void Menu::transFromHopUp()
428 {
429  if (stateFinished) {
430  // We're done with the animation, so jump right into the menu as normal.
431  changeState(MENU_STATE_START);
432 
433  // And re-run the first iteraton of the event loop
434  MenuEvent ignore;
435  pollEvent(&ignore);
436  /* currentEvent will be set by this second iteration of pollEvent,
437  * so when we return from this function the currentEvent will be
438  * propagated back to pollEvent's parameter.
439  */
440  }
441 }
442 
443 
452 inline void Menu::transToPanTarget()
453 {
454  stopping_position = stoppingPositionFor(targetItem);
455  panDelay = kPanDelayMilliseconds;
456 }
457 
458 inline void Menu::statePanTarget()
459 {
460  if (panDelay > 0) {
461  panDelay -= frameclock.delta().milliseconds();
462  } else {
463  float delta = kPanEasingRate * (stopping_position - position);
464  float kPanMaxSpeed = 7.5f; // moved here due to weird linker error
465  position += clamp(delta, -kPanMaxSpeed, kPanMaxSpeed);
466  if (abs(position - stopping_position) < 1.1f) {
467  position = stopping_position;
468  }
469  stateFinished = position == stopping_position;
470  updateBG0();
471  }
472 
473  // if (position > stopping_position) {
474  // position -= frameclock.delta() * 128.f;
475  // if (position < stopping_position) {
476  // position = stopping_position;
477  // stateFinished = true;
478  // }
479  // } else {
480  // position += frameclock.delta() * 128.f;
481  // if (position > stopping_position) {
482  // position = stopping_position;
483  // stateFinished = true;
484  // }
485  // }
486 
487 }
488 
489 inline void Menu::transFromPanTarget()
490 {
491  if (stateFinished) {
492  // We're done with the animation, so jump right into the menu as normal.
493  changeState(MENU_STATE_STATIC);
494  }
495 }
496 
501 }; // namespace Sifteo