v1.1.0
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Modules Pages
states.h
1 /* -*- mode: C; c-basic-offset: 4; intent-tabs-mode: nil -*-
2  *
3  * Sifteo SDK
4  *
5  * Copyright <c> 2012 Sifteo, Inc.
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining a copy
8  * of this software and associated documentation files (the "Software"), to deal
9  * in the Software without restriction, including without limitation the rights
10  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11  * copies of the Software, and to permit persons to whom the Software is
12  * furnished to do so, subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included in
15  * all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23  * THE SOFTWARE.
24  */
25 
26 /*
27  * States are represented by three functions per state:
28  * transTo$TATE() - called whenever transitioning into the state.
29  * state$TATE() - the state itself, called every loop of the event pump.
30  * transFrom$TATE() - called before every loop of the event pump, responsible
31  * for transitioning to other states.
32  *
33  * States are identified by MENU_STATE_$TATE values in enum MenuState, and are
34  * mapped to their respective functions in changeState and pollEvent.
35  */
36 
37 #pragma once
38 #ifdef NOT_USERSPACE
39 # error This is a userspace-only header, not allowed by the current build.
40 #endif
41 
42 #include <sifteo/menu/types.h>
43 
44 namespace Sifteo {
45 
51 inline void Menu::changeState(MenuState newstate)
52 {
53  stateFinished = false;
54  currentState = newstate;
55 
56  MENU_LOG("STATE: -> ");
57  switch(currentState) {
58  case MENU_STATE_START:
59  MENU_LOG("start\n");
60  transToStart();
61  break;
62  case MENU_STATE_STATIC:
63  MENU_LOG("static\n");
64  transToStatic();
65  break;
66  case MENU_STATE_TILTING:
67  MENU_LOG("tilting\n");
68  transToTilting();
69  break;
70  case MENU_STATE_INERTIA:
71  MENU_LOG("inertia\n");
72  transToInertia();
73  break;
74  case MENU_STATE_FINISH:
75  MENU_LOG("finish\n");
76  transToFinish();
77  break;
78  case MENU_STATE_HOP_UP:
79  MENU_LOG("hop up\n");
80  transToHopUp();
81  break;
82  case MENU_STATE_PAN_TARGET:
83  MENU_LOG("pan target\n");
84  transToPanTarget();
85  break;
86  }
87 }
88 
99 inline void Menu::transToStart()
100 {
101  // Do nothing
102 }
103 
104 inline void Menu::stateStart()
105 {
106  // initialize video state
107 
108  vid->initMode(BG0_SPR_BG1);
109  vid->bg0.erase(*assets->background);
110 
111  // Allocate tiles for the static upper label, and draw it.
112  if (kHeaderHeight) {
113  const AssetImage& label = items[startingItem].label ? *items[startingItem].label : *assets->header;
114  vid->bg1.fillMask(vec(0,0), label.tileSize());
115  vid->bg1.image(vec(0,0), label);
116  }
117 
118  // Allocate tiles for the footer, and draw it.
119  if (kFooterHeight) {
120  const AssetImage& footer = assets->tips[0] ? *assets->tips[0] : *assets->footer;
121  Int2 topLeft = { 0, kNumVisibleTilesY - footer.tileHeight() };
122  vid->bg1.fillMask(topLeft, footer.tileSize());
123  vid->bg1.image(topLeft, footer);
124  }
125 
126  currentTip = 0;
127  prevTipTime = SystemTime::now();
128  drawFooter(true);
129 
130  // if/when we start animating the menu into existence, set this once the work is complete
131  stateFinished = true;
132 }
133 
134 inline void Menu::transFromStart()
135 {
136  if (stateFinished) {
137 
138  // garbage inexplicable appears here, often :P
139  vid->touch();
140  System::finish();
141 
142  hasBeenStarted = true;
143 
144  position = stoppingPositionFor(startingItem);
145  prev_ut = computeCurrentTile() + kNumTilesX;
146  updateBG0();
147 
148  for(int i = 0; i < NUM_SIDES; i++) {
149  neighbors[i].neighborSide = NO_SIDE;
150  neighbors[i].neighbor = CubeID::UNDEFINED;
151  neighbors[i].masterSide = NO_SIDE;
152  }
153 
154  changeState(
155  targetItem != -1 && startingItem != targetItem ?
156  MENU_STATE_PAN_TARGET :
157  MENU_STATE_STATIC
158  );
159 
160  }
161 }
162 
175 inline void Menu::transToStatic()
176 {
177  velocity = 0;
178  prevTouch = vid->cube().isTouching();
179 
180  currentEvent.type = MENU_ITEM_ARRIVE;
181  currentEvent.item = computeSelected();
182 
183  // show the title of the item
184  if (kHeaderHeight) {
185  const AssetImage& label = items[currentEvent.item].label ? *items[currentEvent.item].label : *assets->header;
186  vid->bg1.image(vec(0,0), label);
187  }
188 }
189 
190 inline void Menu::stateStatic()
191 {
192  checkForPress();
193 }
194 
195 inline void Menu::transFromStatic()
196 {
197  if (abs(accel.x) < kAccelThresholdOn || isTiltingAtEdge()) {
198  return;
199  }
200 
201  changeState(MENU_STATE_TILTING);
202 
203  currentEvent.type = MENU_ITEM_DEPART;
204  currentEvent.direction = accel.x > 0 ? 1 : -1;
205 
206  // hide header
207  if (kHeaderHeight) {
208  const AssetImage& label = *assets->header;
209  vid->bg1.image(vec(0,0), label);
210  }
211 }
212 
223 inline void Menu::transToTilting()
224 {
225  ASSERT(abs(accel.x) > kAccelThresholdOn);
226 }
227 
228 inline void Menu::stateTilting()
229 {
230  // normal scrolling
231  const int max_x = stoppingPositionFor(numItems - 1);
232  const float kInertiaThreshold = 10.f;
233 
234  velocity += (accel.x * frameclock.delta() * kTimeDilator) * velocityMultiplier();
235 
236  // clamp maximum velocity based on cube angle
237  if (abs(velocity) > maxVelocity()) {
238  velocity = (velocity < 0 ? 0 - maxVelocity() : maxVelocity());
239  }
240 
241  // don't go past the backstop unless we have inertia
242  if ((position > 0.f && velocity < 0) || (position < max_x && velocity > 0) || abs(velocity) > kInertiaThreshold) {
243  position += velocity * frameclock.delta() * kTimeDilator;
244  } else {
245  velocity = 0;
246  }
247  updateBG0();
248 }
249 
250 inline void Menu::transFromTilting()
251 {
252  const bool outOfBounds = (position < -0.05f) || (position > kItemPixelWidth()*(numItems-1) + kEndCapPadding + 0.05f);
253  if (abs(accel.x) < kAccelThresholdOff || outOfBounds) {
254  changeState(MENU_STATE_INERTIA);
255  }
256 }
257 
269 inline void Menu::transToInertia()
270 {
271  stopping_position = stoppingPositionFor(computeSelected());
272  if (abs(accel.x) > kAccelThresholdOff) {
273  tiltDirection = (kAccelScalingFactor * accel.x < 0) ? 1 : -1;
274  } else {
275  tiltDirection = 0;
276  }
277 }
278 
279 inline void Menu::stateInertia()
280 {
281  checkForPress();
282 
283  const float stiffness = 0.333f;
284 
285  // do not pull to item unless tilting has stopped.
286  if (abs(accel.x) < kAccelThresholdOff) {
287  tiltDirection = 0;
288  }
289  // if still tilting, do not bounce back to the stopping position.
290  if ((tiltDirection < 0 && velocity >= 0.f) || (tiltDirection > 0 && velocity <= 0.f)) {
291  return;
292  }
293 
294  velocity += stopping_position - position;
295  velocity *= stiffness;
296  position += velocity * frameclock.delta() * kTimeDilator;
297  position = lerp(position, stopping_position, 0.15f);
298 
299  stateFinished = abs(velocity) < 1.0f && abs(stopping_position - position) < 0.5f;
300  if (stateFinished) {
301  // prevent being off by one pixel when we stop
302  position = stopping_position;
303  }
304 
305  updateBG0();
306 }
307 
308 inline void Menu::transFromInertia()
309 {
310  if (abs(accel.x) > kAccelThresholdOn &&
311  !((tiltDirection < 0 && accel.x < 0.f) || (tiltDirection > 0 && accel.x > 0.f))) {
312  changeState(MENU_STATE_TILTING);
313  }
314  if (stateFinished) { // stateFinished formerly doneTilting
315  changeState(MENU_STATE_STATIC);
316  }
317 }
318 
330 inline void Menu::transToFinish()
331 {
332  // Prepare screen for item animation
333 
334  // We're about to switch things up in VRAM, make sure the cubes are done drawing.
335  System::finish();
336 
337  // blank out the background layer
338  vid->bg0.setPanning(vec(0, 0));
339  vid->bg0.erase(*assets->background);
340 
341  if (assets->header) {
342  Int2 vec = {0, 0};
343  vid->bg0.image(vec, *assets->header);
344  }
345  if (assets->footer) {
346  Int2 vec = { 0, kNumVisibleTilesY - assets->footer->tileHeight() };
347  vid->bg0.image(vec, *assets->footer);
348  }
349  {
350  const AssetImage* icon = items[computeSelected()].icon;
351  vid->bg1.eraseMask();
352  vid->bg1.fillMask(vec(0,0), icon->tileSize());
353  vid->bg1.image(vec(0,0), *icon);
354  }
355  finishIteration = 0;
356  currentEvent.type = MENU_PREPAINT;
357 }
358 
359 inline void Menu::stateFinish()
360 {
361  const float k = 5.f;
362  int offset = 0;
363 
364  finishIteration++;
365  float u = finishIteration/33.f;
366  u = (1.f-k*u);
367  offset = int(12*(1.f-u*u));
368  vid->bg1.setPanning(vec(-kEndCapPadding, offset + kIconYOffset));
369  currentEvent.type = MENU_PREPAINT;
370 
371  if (offset <= -128) {
372  currentEvent.type = MENU_EXIT;
373  currentEvent.item = computeSelected();
374  stateFinished = true;
375  }
376 }
377 
378 inline void Menu::transFromFinish()
379 {
380  if (stateFinished) {
381  // We already animated ourselves out of a job. If we're being called again, reset the menu
382  changeState(MENU_STATE_START);
383 
384  // And re-run the first iteraton of the event loop
385  MenuEvent ignore;
386  pollEvent(&ignore);
387  /* currentEvent will be set by this second iteration of pollEvent,
388  * so when we return from this function the currentEvent will be
389  * propagated back to pollEvent's parameter.
390  */
391  }
392 }
393 
403 inline void Menu::transToHopUp()
404 {
405  // blank out the background layer
406  vid->initMode(BG0_SPR_BG1);
407  vid->bg0.setPanning(vec(0, 0));
408  vid->bg0.erase(*assets->background);
409 
410  if (assets->header) {
411  Int2 vec = {0, 0};
412  vid->bg0.image(vec, *assets->header);
413  }
414  if (assets->footer) {
415  Int2 vec = { 0, kNumVisibleTilesY - assets->footer->tileHeight() };
416  vid->bg0.image(vec, *assets->footer);
417  }
418  {
419  const AssetImage* icon = items[computeSelected()].icon;
420  vid->bg1.eraseMask();
421  vid->bg1.fillMask(vec(0,0), icon->tileSize());
422  vid->bg1.image(vec(0,0), *icon);
423  }
424  finishIteration = 30;
425 
426  hasBeenStarted = true;
427 }
428 
429 inline void Menu::stateHopUp()
430 {
431  const float k = 5.f;
432  int offset = 0;
433 
434  finishIteration--;
435  float u = finishIteration/33.f;
436  u = (1.f-k*u);
437  offset = int(12*(1.f-u*u));
438  vid->bg1.setPanning(vec(-kEndCapPadding, offset + kIconYOffset));
439  currentEvent.type = MENU_PREPAINT;
440 
441  if (offset >= 0) {
442  stateFinished = true;
443  }
444 }
445 
446 inline void Menu::transFromHopUp()
447 {
448  if (stateFinished) {
449  // We're done with the animation, so jump right into the menu as normal.
450  changeState(MENU_STATE_START);
451 
452  // And re-run the first iteraton of the event loop
453  MenuEvent ignore;
454  pollEvent(&ignore);
455  /* currentEvent will be set by this second iteration of pollEvent,
456  * so when we return from this function the currentEvent will be
457  * propagated back to pollEvent's parameter.
458  */
459  }
460 }
461 
462 
471 inline void Menu::transToPanTarget()
472 {
473  stopping_position = stoppingPositionFor(targetItem);
474  panDelay = kPanDelayMilliseconds;
475 }
476 
477 inline void Menu::statePanTarget()
478 {
479  if (panDelay > 0) {
480  panDelay -= frameclock.delta().milliseconds();
481  } else {
482  float delta = kPanEasingRate * (stopping_position - position);
483  float kPanMaxSpeed = 7.5f; // moved here due to weird linker error
484  position += clamp(delta, -kPanMaxSpeed, kPanMaxSpeed);
485  if (abs(position - stopping_position) < 1.1f) {
486  position = stopping_position;
487  }
488  stateFinished = position == stopping_position;
489  updateBG0();
490  }
491 
492  // if (position > stopping_position) {
493  // position -= frameclock.delta() * 128.f;
494  // if (position < stopping_position) {
495  // position = stopping_position;
496  // stateFinished = true;
497  // }
498  // } else {
499  // position += frameclock.delta() * 128.f;
500  // if (position > stopping_position) {
501  // position = stopping_position;
502  // stateFinished = true;
503  // }
504  // }
505 
506 }
507 
508 inline void Menu::transFromPanTarget()
509 {
510  if (stateFinished) {
511  // We're done with the animation, so jump right into the menu as normal.
512  changeState(MENU_STATE_STATIC);
513  }
514 }
515 
520 }; // namespace Sifteo
static SystemTime now()
Returns a new SystemTime representing the current system clock value.
Definition: time.h:269
#define ASSERT(_x)
Runtime debug assertion.
Definition: macros.h:205
MenuState
Definition: menu/types.h:108
Vector2< int > Int2
Typedef for a 2-vector of ints.
Definition: math.h:641
T abs(const T &value)
For any type, return the absolute value.
Definition: math.h:90
Nil value (-1)
Definition: cube.h:60
static void finish()
Wait for any previous paint() to finish.
Definition: system.h:170
Definition: array.h:34
BG0 background, 8 sprites, BG1 overlay.
Definition: video.h:88
Total number of sides (4)
Definition: cube.h:59
T clamp(const T &value, const T &low, const T &high)
For any type, clamp a value to the extremes 'low' and 'high'.
Definition: math.h:72
static const _SYSCubeID UNDEFINED
A reserved ID, used to mark undefined CubeIDs.
Definition: cube.h:92
Vector2< T > vec(T x, T y)
Create a Vector2, from a set of (x,y) coordinates.
Definition: math.h:658