Menu
Source: src/menu.c · 453 lines · 21 functions.
Screen captures
Views of this part running on the Mini Arcade.






Module entry points
These functions are exposed to the rest of the project. Open one to follow its decisions and the helpers it calls.
How to read these diagrams
Each function has its own expandable diagram using the actual expressions and statements from this file. Decisions split into True and False; loop bodies are indented. Returns end the function immediately; break and continue follow their C meanings. Arrows between statements show their order when execution reaches them.
These are structural source diagrams before preprocessing, not runtime traces. Conditional compilation and macros remain source expressions. Ternary choices remain inside their statement. Calls are linked separately; callback execution and task scheduling are not inferred.
Functions in this file
active_items() · source line 111
static const menu_item_t *active_items(void)
- Decision
current_screen == SCREEN_GAMESTrue- Exit this function
return games_items;
FalseContinue after this decision.
- Decision
current_screen == SCREEN_SETTINGSTrue- Exit this function
return settings_items;
FalseContinue after this decision.
- Exit this function
return current_screen == SCREEN_MISC ? misc_items : main_items;
Read original function source
static const menu_item_t *active_items(void)
{
if (current_screen == SCREEN_GAMES) return games_items;
if (current_screen == SCREEN_SETTINGS) return settings_items;
return current_screen == SCREEN_MISC ? misc_items : main_items;
}active_selection() · source line 118
static size_t *active_selection(void)
- Decision
current_screen == SCREEN_GAMESTrue- Exit this function
return &games_selected;
FalseContinue after this decision.
- Decision
current_screen == SCREEN_SETTINGSTrue- Exit this function
return &settings_selected;
FalseContinue after this decision.
- Exit this function
return current_screen == SCREEN_MISC ? &misc_selected : &main_selected;
Read original function source
static size_t *active_selection(void)
{
if (current_screen == SCREEN_GAMES) return &games_selected;
if (current_screen == SCREEN_SETTINGS) return &settings_selected;
return current_screen == SCREEN_MISC ? &misc_selected : &main_selected;
}note_action() · source line 125
static void note_action(uint32_t now_ms)
- Execute
last_action = now_ms; - Execute
redraw = true;
Read original function source
static void note_action(uint32_t now_ms)
{
last_action = now_ms;
redraw = true;
}change_screen() · source line 131
static void change_screen(screen_id_t screen, uint32_t now_ms)
Calls into this documentation:
- Execute
current_screen = screen; - Decision
screen == SCREEN_MENUTrue- Execute
main_selected = 0U;
False- Decision
screen == SCREEN_GAMESTrue- Execute
games_selected = 0U;
False- Decision
screen == SCREEN_SETTINGSTrue- Execute
settings_selected = 0U;
False- Decision
screen == SCREEN_MISCTrue- Execute
misc_selected = 0U;
FalseContinue after this decision.
- Execute
button_direction = 0; - Execute
button_repeating = false; - Execute
held_direction = MOVE_NONE; - Execute
waiting_for_centre = true; - Execute
repeating = false; - Execute
note_action(now_ms);
Read original function source
static void change_screen(screen_id_t screen, uint32_t now_ms)
{
current_screen = screen;
if (screen == SCREEN_MENU) main_selected = 0U;
else if (screen == SCREEN_GAMES) games_selected = 0U;
else if (screen == SCREEN_SETTINGS) settings_selected = 0U;
else if (screen == SCREEN_MISC) misc_selected = 0U;
button_direction = 0;
button_repeating = false;
held_direction = MOVE_NONE;
waiting_for_centre = true;
repeating = false;
note_action(now_ms);
}move_selection() · source line 299
static void move_selection(menu_direction_t direction, uint32_t now_ms)
Calls into this documentation:
- Execute
const size_t count = menu_count(); - Execute
size_t *selection = active_selection(); - Execute
const size_t previous_selection = *selection; - Decision
count == 0True- Exit this function
return;
FalseContinue after this decision.
- Decision
current_screen == SCREEN_MENU || current_screen == SCREEN_GAMES || current_screen == SCREEN_SETTINGS || current_screen == SCREEN_MISCTrue- Execute
const size_t maximum_rows_per_column = 4U; - Execute
const size_t items_per_page = maximum_rows_per_column * 2U; - Execute
const size_t page_count = (count + items_per_page - 1U) / items_per_page; - Execute
size_t page = *selection / items_per_page; - Execute
size_t position = *selection % items_per_page; - Execute
const size_t page_start = page * items_per_page; - Execute
const size_t page_items = count - page_start < items_per_page ? count - page_start : items_per_page; - Execute
size_t rows_per_column = (page_items + 1U) / 2U; - Decision
rows_per_column > maximum_rows_per_columnTrue- Execute
rows_per_column = maximum_rows_per_column;
FalseContinue after this decision.
- Execute
size_t column = position / rows_per_column; - Execute
size_t row = position % rows_per_column; - Execute
size_t rows = page_items > column * rows_per_column ? page_items - column * rows_per_column : 0U; - Decision
rows > rows_per_columnTrue- Execute
rows = rows_per_column;
FalseContinue after this decision.
- Decision
direction == MOVE_LEFT || direction == MOVE_RIGHTTrue- Execute
const size_t other_column = column == 0U ? 1U : 0U; - Execute
size_t other_rows = page_items > other_column * rows_per_column ? page_items - other_column * rows_per_column : 0U; - Decision
other_rows > rows_per_columnTrue- Execute
other_rows = rows_per_column;
FalseContinue after this decision.
- Decision
other_rows > 0UTrue- Execute
column = other_column; - Decision
row >= other_rowsTrue- Execute
row = other_rows - 1U;
FalseContinue after this decision.
FalseContinue after this decision.
False- Decision
direction == MOVE_UP || direction == MOVE_DOWNTrue- Decision
(direction == MOVE_UP && row > 0U) || (direction == MOVE_DOWN && row + 1U < rows)True- Execute
row = direction == MOVE_UP ? row - 1U : row + 1U;
False- DO / WHILE loop
rows == 0URun body, then test condition; repeat while true.
- Execute
page = direction == MOVE_UP ? (page + page_count - 1U) % page_count : (page + 1U) % page_count; - Execute
const size_t next_start = page * items_per_page; - Execute
const size_t next_items = count - next_start < items_per_page ? count - next_start : items_per_page; - Execute
rows_per_column = (next_items + 1U) / 2U; - Decision
rows_per_column > maximum_rows_per_columnTrue- Execute
rows_per_column = maximum_rows_per_column;
FalseContinue after this decision.
- Execute
rows = next_items > column * rows_per_column ? next_items - column * rows_per_column : 0U; - Decision
rows > rows_per_columnTrue- Execute
rows = rows_per_column;
FalseContinue after this decision.
- Execute
row = direction == MOVE_UP ? rows - 1U : 0U;
False- Exit this function
return;
- Execute
*selection = page * items_per_page + column * rows_per_column + row;
False- Exit this function
return;
- Decision
*selection != previous_selectionTrue- Execute
audioPlay(AUDIO_SFX_MENU_MOVE);
FalseContinue after this decision.
- Execute
note_action(now_ms);
Read original function source
static void move_selection(menu_direction_t direction, uint32_t now_ms)
{
const size_t count = menu_count();
size_t *selection = active_selection();
const size_t previous_selection = *selection;
if (count == 0) {
return;
}
if (current_screen == SCREEN_MENU || current_screen == SCREEN_GAMES ||
current_screen == SCREEN_SETTINGS || current_screen == SCREEN_MISC) {
const size_t maximum_rows_per_column = 4U;
const size_t items_per_page = maximum_rows_per_column * 2U;
const size_t page_count = (count + items_per_page - 1U) / items_per_page;
size_t page = *selection / items_per_page;
size_t position = *selection % items_per_page;
const size_t page_start = page * items_per_page;
const size_t page_items = count - page_start < items_per_page ?
count - page_start : items_per_page;
size_t rows_per_column = (page_items + 1U) / 2U;
if (rows_per_column > maximum_rows_per_column) rows_per_column = maximum_rows_per_column;
size_t column = position / rows_per_column;
size_t row = position % rows_per_column;
size_t rows = page_items > column * rows_per_column ?
page_items - column * rows_per_column : 0U;
if (rows > rows_per_column) rows = rows_per_column;
if (direction == MOVE_LEFT || direction == MOVE_RIGHT) {
const size_t other_column = column == 0U ? 1U : 0U;
size_t other_rows = page_items > other_column * rows_per_column ?
page_items - other_column * rows_per_column : 0U;
if (other_rows > rows_per_column) other_rows = rows_per_column;
if (other_rows > 0U) {
column = other_column;
if (row >= other_rows) row = other_rows - 1U;
}
} else if (direction == MOVE_UP || direction == MOVE_DOWN) {
if ((direction == MOVE_UP && row > 0U) ||
(direction == MOVE_DOWN && row + 1U < rows)) {
row = direction == MOVE_UP ? row - 1U : row + 1U;
} else {
do {
page = direction == MOVE_UP ? (page + page_count - 1U) % page_count :
(page + 1U) % page_count;
const size_t next_start = page * items_per_page;
const size_t next_items = count - next_start < items_per_page ?
count - next_start : items_per_page;
rows_per_column = (next_items + 1U) / 2U;
if (rows_per_column > maximum_rows_per_column)
rows_per_column = maximum_rows_per_column;
rows = next_items > column * rows_per_column ?
next_items - column * rows_per_column : 0U;
if (rows > rows_per_column) rows = rows_per_column;
} while (rows == 0U);
row = direction == MOVE_UP ? rows - 1U : 0U;
}
} else {
return;
}
*selection = page * items_per_page + column * rows_per_column + row;
} else {
return;
}
if (*selection != previous_selection) {
audioPlay(AUDIO_SFX_MENU_MOVE);
}
note_action(now_ms);
}Complete original file, including constants and data
/* Coded By JD(2026) */
#error "Reference listing only — not provided as build-ready source."
#include "menu.h"
#include "audio.h"
#include "hardware_features.h"
#define MENU_TIMEOUT_MS 120000U
#define JOYSTICK_ENTER 600
#define JOYSTICK_RELEASE 350
#define JOYSTICK_REPEAT_START_MS 400U
#define JOYSTICK_REPEAT_MS 150U
typedef struct {
const char *text;
screen_id_t target;
bool submenu;
} menu_item_t;
typedef enum {
MOVE_NONE,
MOVE_UP,
MOVE_DOWN,
MOVE_LEFT,
MOVE_RIGHT,
} menu_direction_t;
static const menu_item_t main_items[] = {
{"DEMO", SCREEN_ABOUT, false},
{"GAMES", SCREEN_GAMES, true},
{"HALL OF FAME", SCREEN_HALL_OF_FAME, false},
{"MISC", SCREEN_MISC, true},
{"SETTINGS", SCREEN_SETTINGS, true},
};
// Add each game here when its module is ported and linked.
static const menu_item_t games_items[] = {
{"ASTEROIDS", SCREEN_ASTEROIDS, false},
{"BATTLE ZONE", SCREEN_BATTLE_ZONE, false},
{"BATTLESHIPS", SCREEN_BATTLESHIPS, false},
{"BLACKJACK", SCREEN_BLACKJACK, false},
{"BOULDER DASH", SCREEN_BOULDER_DASH, false},
{"BREAKOUT", SCREEN_BREAKOUT, false},
{"CHECKERS", SCREEN_CHECKERS, false},
{"CONNECT 4", SCREEN_CONNECT_FOUR, false},
{"CRAWLER", SCREEN_CRAWLER, false},
{"DEFENDER", SCREEN_DEFENDER, false},
{"DUNGEON", SCREEN_DUNGEON, false},
{"GALAGA", SCREEN_GALAGA, false},
{"INVADERS", SCREEN_INVADERS, false},
{"MAZE CHASE", SCREEN_MAZE_CHASE, false},
{"MISSILE COMMAND", SCREEN_MISSILE_COMMAND, false},
{"OTHELLO", SCREEN_OTHELLO, false},
{"PIG", SCREEN_PIG, false},
{"POKER", SCREEN_POKER, false},
{"POKER DICE", SCREEN_POKER_DICE, false},
{"SCRAMBLE", SCREEN_SCRAMBLE, false},
{"SIMON", SCREEN_SIMON, false},
{"TEMPEST", SCREEN_TEMPEST, false},
{"YAHTZEE", SCREEN_YAHTZEE, false},
{"BACK", SCREEN_MENU, false},
};
static const menu_item_t settings_items[] = {
{"AI MOVE", SCREEN_AI_MOVE, false},
{"AI RESTART", SCREEN_AI_RESTART, false},
{"BATTERY", SCREEN_BATTERY, false},
{"BRIGHTNESS", SCREEN_BRIGHTNESS, false},
{"VOLUME", SCREEN_VOLUME, false},
{"CONTROLLER TEST", SCREEN_CONTROLLER_TEST, false},
{"ESP INFO", SCREEN_ESP_INFO, false},
{"FILE BROWSER", SCREEN_SD_CARD_TEST, false},
{"FONT TEST", SCREEN_FONT_TEST, false},
{"GAME CONFIG", SCREEN_GAME_CONFIG, false},
{"GRAPHICS TEST", SCREEN_GRAPHICS_TEST, false},
{"POWER OFF", SCREEN_POWER_OFF, false},
#if RAM_MONITOR_ENABLED
{"RAM LOG", SCREEN_RAM_LOG, false},
#endif
{"RESTART", SCREEN_RESTART, false},
{"SPIFFS TEST", SCREEN_SPIFFS_TEST, false},
{"SOUND TEST", SCREEN_SOUND_TEST, false},
{"WIFI", SCREEN_WIFI, false},
{"BACK", SCREEN_MENU, false},
};
static const menu_item_t misc_items[] = {
{"BOING BALL", SCREEN_BOING_BALL, false},
{"CALCULATOR", SCREEN_CALCULATOR, false},
{"CLOCK", SCREEN_CLOCK, false},
{"KALEIDOSCOPE", SCREEN_KALEIDOSCOPE, false},
{"MANDELBROT", SCREEN_MANDELBROT, false},
{"MATRIX", SCREEN_MATRIX, false},
{"SLIDESHOW", SCREEN_SLIDESHOW, false},
{"WARP TUNNEL", SCREEN_WARP_TUNNEL, false},
{"BACK", SCREEN_MENU, false},
};
static screen_id_t current_screen = SCREEN_MENU;
static size_t main_selected = 0;
static size_t games_selected = 0;
static size_t settings_selected = 0;
static size_t misc_selected = 0;
static bool redraw = true;
static bool waiting_for_centre = true;
static bool repeating = false;
static menu_direction_t held_direction = MOVE_NONE;
static uint32_t last_action = 0;
static uint32_t last_move = 0;
static int button_direction = 0;
static bool button_repeating = false;
static uint32_t last_button_move = 0;
static const menu_item_t *active_items(void)
{
if (current_screen == SCREEN_GAMES) return games_items;
if (current_screen == SCREEN_SETTINGS) return settings_items;
return current_screen == SCREEN_MISC ? misc_items : main_items;
}
static size_t *active_selection(void)
{
if (current_screen == SCREEN_GAMES) return &games_selected;
if (current_screen == SCREEN_SETTINGS) return &settings_selected;
return current_screen == SCREEN_MISC ? &misc_selected : &main_selected;
}
static void note_action(uint32_t now_ms)
{
last_action = now_ms;
redraw = true;
}
static void change_screen(screen_id_t screen, uint32_t now_ms)
{
current_screen = screen;
if (screen == SCREEN_MENU) main_selected = 0U;
else if (screen == SCREEN_GAMES) games_selected = 0U;
else if (screen == SCREEN_SETTINGS) settings_selected = 0U;
else if (screen == SCREEN_MISC) misc_selected = 0U;
button_direction = 0;
button_repeating = false;
held_direction = MOVE_NONE;
waiting_for_centre = true;
repeating = false;
note_action(now_ms);
}
void menu_initialize(uint32_t now_ms)
{
main_selected = 0;
games_selected = 0;
settings_selected = 0;
misc_selected = 0;
change_screen(SCREEN_MENU, now_ms);
}
screen_id_t menu_current_screen(void)
{
return current_screen;
}
const char *menuScreenshotLabel(void)
{
switch (current_screen) {
case SCREEN_MENU: return "main-menu";
case SCREEN_GAMES: return "games-menu";
case SCREEN_SETTINGS: return "settings-menu";
case SCREEN_BRIGHTNESS: return "brightness";
case SCREEN_VOLUME: return "volume";
case SCREEN_AI_MOVE: return "ai-move";
case SCREEN_AI_RESTART: return "ai-restart";
case SCREEN_WIFI: return "wifi";
case SCREEN_POWER_OFF: return "power-off";
case SCREEN_RESTART: return "restart";
case SCREEN_ESP_INFO: return "esp-info";
case SCREEN_RAM_LOG: return "ram-log";
case SCREEN_GAME_CONFIG: return "game-config";
case SCREEN_MISC: return "misc-menu";
case SCREEN_ABOUT: return "about";
case SCREEN_MATRIX: return "matrix";
case SCREEN_BOING_BALL: return "boing-ball";
case SCREEN_DEMO: return "demo";
case SCREEN_SIMON: return "simon";
case SCREEN_CALCULATOR: return "calculator";
case SCREEN_KALEIDOSCOPE: return "kaleidoscope";
case SCREEN_MANDELBROT: return "mandelbrot";
case SCREEN_SLIDESHOW: return "slideshow";
case SCREEN_WARP_TUNNEL: return "warp-tunnel";
case SCREEN_CONTROLLER_TEST: return "controller-test";
case SCREEN_GRAPHICS_TEST: return "graphics-test";
case SCREEN_FONT_TEST: return "font-test";
case SCREEN_CHECKERS: return "checkers";
case SCREEN_OTHELLO: return "othello";
case SCREEN_CONNECT_FOUR: return "connect-four";
case SCREEN_BATTLESHIPS: return "battleships";
case SCREEN_BATTLE_ZONE: return "battle-zone";
case SCREEN_BOULDER_DASH: return "boulder-dash";
case SCREEN_BREAKOUT: return "breakout";
case SCREEN_CRAWLER: return "crawler";
case SCREEN_MAZE_CHASE: return "maze-chase";
case SCREEN_BATTERY: return "battery";
case SCREEN_SD_CARD_TEST: return "file-browser";
case SCREEN_SPIFFS_TEST: return "spiffs-test";
case SCREEN_SOUND_TEST: return "sound-test";
case SCREEN_ASTEROIDS: return "asteroids";
case SCREEN_TEMPEST: return "tempest";
case SCREEN_INVADERS: return "invaders";
case SCREEN_YAHTZEE: return "yahtzee";
case SCREEN_BLACKJACK: return "blackjack";
case SCREEN_POKER: return "poker";
case SCREEN_POKER_DICE: return "poker-dice";
case SCREEN_PIG: return "pig";
case SCREEN_MISSILE_COMMAND: return "missile-command";
case SCREEN_SCRAMBLE: return "scramble";
case SCREEN_DEFENDER: return "defender";
case SCREEN_GALAGA: return "galaga";
case SCREEN_DUNGEON: return "dungeon";
case SCREEN_HALL_OF_FAME: return "hall-of-fame";
case SCREEN_CLOCK: return "clock";
default: return "unknown";
}
}
size_t menu_count(void)
{
if (current_screen == SCREEN_GAMES) {
return sizeof(games_items) / sizeof(games_items[0]);
}
if (current_screen == SCREEN_BOULDER_DASH) return 0;
if (current_screen == SCREEN_BRIGHTNESS || current_screen == SCREEN_VOLUME || current_screen == SCREEN_AI_MOVE || current_screen == SCREEN_AI_RESTART || current_screen == SCREEN_WIFI || current_screen == SCREEN_POWER_OFF || current_screen == SCREEN_RESTART || current_screen == SCREEN_ESP_INFO || current_screen == SCREEN_RAM_LOG || current_screen == SCREEN_GAME_CONFIG || current_screen == SCREEN_CONTROLLER_TEST || current_screen == SCREEN_GRAPHICS_TEST || current_screen == SCREEN_FONT_TEST || current_screen == SCREEN_BATTERY || current_screen == SCREEN_SD_CARD_TEST || current_screen == SCREEN_SPIFFS_TEST || current_screen == SCREEN_SOUND_TEST || current_screen == SCREEN_ABOUT || current_screen == SCREEN_MATRIX || current_screen == SCREEN_BOING_BALL || current_screen == SCREEN_DEMO || current_screen == SCREEN_SIMON || current_screen == SCREEN_CALCULATOR || current_screen == SCREEN_KALEIDOSCOPE || current_screen == SCREEN_MANDELBROT || current_screen == SCREEN_SLIDESHOW || current_screen == SCREEN_WARP_TUNNEL || current_screen == SCREEN_HALL_OF_FAME || current_screen == SCREEN_CLOCK || current_screen == SCREEN_CHECKERS || current_screen == SCREEN_OTHELLO || current_screen == SCREEN_CONNECT_FOUR || current_screen == SCREEN_BATTLESHIPS || current_screen == SCREEN_BATTLE_ZONE || current_screen == SCREEN_BREAKOUT || current_screen == SCREEN_CRAWLER || current_screen == SCREEN_MAZE_CHASE || current_screen == SCREEN_ASTEROIDS || current_screen == SCREEN_TEMPEST || current_screen == SCREEN_INVADERS || current_screen == SCREEN_YAHTZEE || current_screen == SCREEN_BLACKJACK || current_screen == SCREEN_POKER || current_screen == SCREEN_POKER_DICE || current_screen == SCREEN_PIG || current_screen == SCREEN_MISSILE_COMMAND || current_screen == SCREEN_SCRAMBLE || current_screen == SCREEN_DEFENDER || current_screen == SCREEN_GALAGA || current_screen == SCREEN_DUNGEON) {
return 0;
}
if (current_screen == SCREEN_SETTINGS) return sizeof(settings_items) / sizeof(settings_items[0]);
return current_screen == SCREEN_MISC ? sizeof(misc_items) / sizeof(misc_items[0])
: sizeof(main_items) / sizeof(main_items[0]);
}
size_t menu_selected_item(void)
{
return *active_selection();
}
const char *menu_title(void)
{
if (current_screen == SCREEN_GAMES) {
return "GAMES";
}
if (current_screen == SCREEN_SETTINGS) return "SETTINGS";
return current_screen == SCREEN_MISC ? "MISC" : "MENU";
}
const char *menu_item_text(size_t index)
{
return index < menu_count() ? active_items()[index].text : "";
}
bool menu_item_is_submenu(size_t index)
{
return index < menu_count() && active_items()[index].submenu;
}
bool menu_selection_visible(void)
{
return true;
}
bool menu_take_redraw(void)
{
const bool needed = redraw;
redraw = false;
return needed;
}
void menu_select(uint32_t now_ms)
{
if (current_screen == SCREEN_BOULDER_DASH) return;
if (current_screen == SCREEN_BRIGHTNESS || current_screen == SCREEN_VOLUME || current_screen == SCREEN_AI_MOVE || current_screen == SCREEN_AI_RESTART || current_screen == SCREEN_WIFI || current_screen == SCREEN_POWER_OFF || current_screen == SCREEN_RESTART || current_screen == SCREEN_ESP_INFO || current_screen == SCREEN_RAM_LOG || current_screen == SCREEN_GAME_CONFIG || current_screen == SCREEN_CONTROLLER_TEST || current_screen == SCREEN_GRAPHICS_TEST || current_screen == SCREEN_FONT_TEST || current_screen == SCREEN_BATTERY || current_screen == SCREEN_SD_CARD_TEST || current_screen == SCREEN_SPIFFS_TEST || current_screen == SCREEN_ABOUT || current_screen == SCREEN_MATRIX || current_screen == SCREEN_BOING_BALL || current_screen == SCREEN_SIMON || current_screen == SCREEN_CALCULATOR || current_screen == SCREEN_KALEIDOSCOPE || current_screen == SCREEN_MANDELBROT || current_screen == SCREEN_SLIDESHOW || current_screen == SCREEN_HALL_OF_FAME || current_screen == SCREEN_CLOCK || current_screen == SCREEN_CHECKERS || current_screen == SCREEN_OTHELLO || current_screen == SCREEN_CONNECT_FOUR || current_screen == SCREEN_BATTLESHIPS || current_screen == SCREEN_BATTLE_ZONE || current_screen == SCREEN_BREAKOUT || current_screen == SCREEN_CRAWLER || current_screen == SCREEN_ASTEROIDS || current_screen == SCREEN_TEMPEST || current_screen == SCREEN_INVADERS || current_screen == SCREEN_YAHTZEE || current_screen == SCREEN_BLACKJACK || current_screen == SCREEN_POKER || current_screen == SCREEN_POKER_DICE || current_screen == SCREEN_PIG || current_screen == SCREEN_MISSILE_COMMAND || current_screen == SCREEN_SCRAMBLE || current_screen == SCREEN_DEFENDER || current_screen == SCREEN_GALAGA || current_screen == SCREEN_DUNGEON) {
return;
}
audioPlay(AUDIO_SFX_MENU_SELECT);
change_screen(active_items()[*active_selection()].target, now_ms);
}
void menu_back(uint32_t now_ms)
{
if (current_screen == SCREEN_BOULDER_DASH) {
change_screen(SCREEN_GAMES, now_ms);
return;
}
change_screen((current_screen == SCREEN_CHECKERS || current_screen == SCREEN_OTHELLO || current_screen == SCREEN_CONNECT_FOUR || current_screen == SCREEN_BATTLESHIPS || current_screen == SCREEN_BATTLE_ZONE || current_screen == SCREEN_BREAKOUT || current_screen == SCREEN_CRAWLER || current_screen == SCREEN_MAZE_CHASE || current_screen == SCREEN_ASTEROIDS || current_screen == SCREEN_TEMPEST || current_screen == SCREEN_INVADERS || current_screen == SCREEN_MISSILE_COMMAND || current_screen == SCREEN_SCRAMBLE || current_screen == SCREEN_SIMON || current_screen == SCREEN_DEFENDER || current_screen == SCREEN_GALAGA || current_screen == SCREEN_YAHTZEE || current_screen == SCREEN_BLACKJACK || current_screen == SCREEN_POKER || current_screen == SCREEN_POKER_DICE || current_screen == SCREEN_PIG || current_screen == SCREEN_DUNGEON) ? SCREEN_GAMES :
(current_screen == SCREEN_CLOCK || current_screen == SCREEN_MATRIX || current_screen == SCREEN_BOING_BALL || current_screen == SCREEN_DEMO || current_screen == SCREEN_CALCULATOR || current_screen == SCREEN_KALEIDOSCOPE || current_screen == SCREEN_MANDELBROT || current_screen == SCREEN_SLIDESHOW || current_screen == SCREEN_WARP_TUNNEL) ? SCREEN_MISC :
(current_screen == SCREEN_BRIGHTNESS || current_screen == SCREEN_VOLUME || current_screen == SCREEN_AI_MOVE || current_screen == SCREEN_AI_RESTART || current_screen == SCREEN_WIFI || current_screen == SCREEN_POWER_OFF || current_screen == SCREEN_RESTART || current_screen == SCREEN_ESP_INFO || current_screen == SCREEN_RAM_LOG || current_screen == SCREEN_GAME_CONFIG || current_screen == SCREEN_CONTROLLER_TEST || current_screen == SCREEN_GRAPHICS_TEST || current_screen == SCREEN_FONT_TEST || current_screen == SCREEN_BATTERY || current_screen == SCREEN_SD_CARD_TEST || current_screen == SCREEN_SPIFFS_TEST || current_screen == SCREEN_SOUND_TEST) ? SCREEN_SETTINGS : SCREEN_MENU,
now_ms);
}
void menu_home(uint32_t now_ms)
{
change_screen(SCREEN_MENU, now_ms);
}
static void move_selection(menu_direction_t direction, uint32_t now_ms)
{
const size_t count = menu_count();
size_t *selection = active_selection();
const size_t previous_selection = *selection;
if (count == 0) {
return;
}
if (current_screen == SCREEN_MENU || current_screen == SCREEN_GAMES ||
current_screen == SCREEN_SETTINGS || current_screen == SCREEN_MISC) {
const size_t maximum_rows_per_column = 4U;
const size_t items_per_page = maximum_rows_per_column * 2U;
const size_t page_count = (count + items_per_page - 1U) / items_per_page;
size_t page = *selection / items_per_page;
size_t position = *selection % items_per_page;
const size_t page_start = page * items_per_page;
const size_t page_items = count - page_start < items_per_page ?
count - page_start : items_per_page;
size_t rows_per_column = (page_items + 1U) / 2U;
if (rows_per_column > maximum_rows_per_column) rows_per_column = maximum_rows_per_column;
size_t column = position / rows_per_column;
size_t row = position % rows_per_column;
size_t rows = page_items > column * rows_per_column ?
page_items - column * rows_per_column : 0U;
if (rows > rows_per_column) rows = rows_per_column;
if (direction == MOVE_LEFT || direction == MOVE_RIGHT) {
const size_t other_column = column == 0U ? 1U : 0U;
size_t other_rows = page_items > other_column * rows_per_column ?
page_items - other_column * rows_per_column : 0U;
if (other_rows > rows_per_column) other_rows = rows_per_column;
if (other_rows > 0U) {
column = other_column;
if (row >= other_rows) row = other_rows - 1U;
}
} else if (direction == MOVE_UP || direction == MOVE_DOWN) {
if ((direction == MOVE_UP && row > 0U) ||
(direction == MOVE_DOWN && row + 1U < rows)) {
row = direction == MOVE_UP ? row - 1U : row + 1U;
} else {
do {
page = direction == MOVE_UP ? (page + page_count - 1U) % page_count :
(page + 1U) % page_count;
const size_t next_start = page * items_per_page;
const size_t next_items = count - next_start < items_per_page ?
count - next_start : items_per_page;
rows_per_column = (next_items + 1U) / 2U;
if (rows_per_column > maximum_rows_per_column)
rows_per_column = maximum_rows_per_column;
rows = next_items > column * rows_per_column ?
next_items - column * rows_per_column : 0U;
if (rows > rows_per_column) rows = rows_per_column;
} while (rows == 0U);
row = direction == MOVE_UP ? rows - 1U : 0U;
}
} else {
return;
}
*selection = page * items_per_page + column * rows_per_column + row;
} else {
return;
}
if (*selection != previous_selection) {
audioPlay(AUDIO_SFX_MENU_MOVE);
}
note_action(now_ms);
}
bool menu_update_buttons(bool left, bool right, uint32_t now_ms)
{
if (!left && !right) {
button_direction = 0;
button_repeating = false;
return false;
}
// Give buttons their own repeat timing without requiring joystick centring.
held_direction = MOVE_NONE;
repeating = false;
const int direction = (int)right - (int)left;
if (direction == 0 || menu_count() == 0) {
button_direction = 0;
button_repeating = false;
return true;
}
if (direction == button_direction && now_ms - last_button_move <
(button_repeating ? JOYSTICK_REPEAT_MS : JOYSTICK_REPEAT_START_MS)) {
return true;
}
button_repeating = direction == button_direction;
button_direction = direction;
last_button_move = now_ms;
move_selection(direction < 0 ? MOVE_UP : MOVE_DOWN, now_ms);
return true;
}
void menu_update_joystick(int x_offset, int y_offset, bool valid, uint32_t now_ms)
{
if (current_screen == SCREEN_BOULDER_DASH) return;
if (current_screen == SCREEN_POWER_OFF || current_screen == SCREEN_GAME_CONFIG || current_screen == SCREEN_CONTROLLER_TEST || current_screen == SCREEN_GRAPHICS_TEST || current_screen == SCREEN_FONT_TEST || current_screen == SCREEN_BATTERY || current_screen == SCREEN_SD_CARD_TEST || current_screen == SCREEN_SPIFFS_TEST || current_screen == SCREEN_SOUND_TEST || current_screen == SCREEN_ABOUT || current_screen == SCREEN_HALL_OF_FAME || current_screen == SCREEN_CLOCK || current_screen == SCREEN_MATRIX || current_screen == SCREEN_BOING_BALL || current_screen == SCREEN_DEMO || current_screen == SCREEN_SIMON || current_screen == SCREEN_CALCULATOR || current_screen == SCREEN_KALEIDOSCOPE || current_screen == SCREEN_MANDELBROT || current_screen == SCREEN_SLIDESHOW || current_screen == SCREEN_WARP_TUNNEL || current_screen == SCREEN_CHECKERS || current_screen == SCREEN_OTHELLO || current_screen == SCREEN_CONNECT_FOUR || current_screen == SCREEN_BATTLESHIPS || current_screen == SCREEN_BATTLE_ZONE || current_screen == SCREEN_BREAKOUT || current_screen == SCREEN_CRAWLER || current_screen == SCREEN_MAZE_CHASE || current_screen == SCREEN_ASTEROIDS || current_screen == SCREEN_TEMPEST || current_screen == SCREEN_INVADERS || current_screen == SCREEN_YAHTZEE || current_screen == SCREEN_BLACKJACK || current_screen == SCREEN_POKER || current_screen == SCREEN_POKER_DICE || current_screen == SCREEN_PIG || current_screen == SCREEN_MISSILE_COMMAND || current_screen == SCREEN_SCRAMBLE || current_screen == SCREEN_DEFENDER || current_screen == SCREEN_GALAGA || current_screen == SCREEN_DUNGEON) {
return;
}
if (!valid) {
waiting_for_centre = true;
held_direction = MOVE_NONE;
return;
}
const int x_magnitude = x_offset < 0 ? -x_offset : x_offset;
const int y_magnitude = y_offset < 0 ? -y_offset : y_offset;
if (x_magnitude <= JOYSTICK_RELEASE && y_magnitude <= JOYSTICK_RELEASE) {
waiting_for_centre = false;
held_direction = MOVE_NONE;
repeating = false;
return;
}
if (waiting_for_centre) {
return;
}
menu_direction_t direction = MOVE_NONE;
if (x_magnitude >= y_magnitude && x_magnitude >= JOYSTICK_ENTER) {
direction = x_offset > 0 ? MOVE_RIGHT : MOVE_LEFT;
} else if (y_magnitude >= JOYSTICK_ENTER) {
direction = y_offset > 0 ? MOVE_UP : MOVE_DOWN;
}
if (direction == MOVE_NONE) {
return;
}
if (direction != held_direction) {
held_direction = direction;
repeating = false;
last_move = now_ms;
move_selection(direction, now_ms);
} else if (now_ms - last_move >=
(repeating ? JOYSTICK_REPEAT_MS : JOYSTICK_REPEAT_START_MS)) {
repeating = true;
last_move = now_ms;
move_selection(direction, now_ms);
}
}
void menu_update(uint32_t now_ms)
{
if (current_screen == SCREEN_BOULDER_DASH) return;
if (current_screen == SCREEN_POWER_OFF || current_screen == SCREEN_GAME_CONFIG || current_screen == SCREEN_CONTROLLER_TEST || current_screen == SCREEN_GRAPHICS_TEST || current_screen == SCREEN_FONT_TEST || current_screen == SCREEN_BATTERY || current_screen == SCREEN_SD_CARD_TEST || current_screen == SCREEN_SPIFFS_TEST || current_screen == SCREEN_SOUND_TEST || current_screen == SCREEN_ABOUT || current_screen == SCREEN_HALL_OF_FAME || current_screen == SCREEN_CLOCK || current_screen == SCREEN_MATRIX || current_screen == SCREEN_BOING_BALL || current_screen == SCREEN_DEMO || current_screen == SCREEN_SIMON || current_screen == SCREEN_CALCULATOR || current_screen == SCREEN_KALEIDOSCOPE || current_screen == SCREEN_MANDELBROT || current_screen == SCREEN_SLIDESHOW || current_screen == SCREEN_WARP_TUNNEL || current_screen == SCREEN_CHECKERS || current_screen == SCREEN_OTHELLO || current_screen == SCREEN_CONNECT_FOUR || current_screen == SCREEN_BATTLESHIPS || current_screen == SCREEN_BATTLE_ZONE || current_screen == SCREEN_BREAKOUT || current_screen == SCREEN_CRAWLER || current_screen == SCREEN_MAZE_CHASE || current_screen == SCREEN_ASTEROIDS || current_screen == SCREEN_TEMPEST || current_screen == SCREEN_INVADERS || current_screen == SCREEN_YAHTZEE || current_screen == SCREEN_BLACKJACK || current_screen == SCREEN_POKER || current_screen == SCREEN_POKER_DICE || current_screen == SCREEN_PIG || current_screen == SCREEN_MISSILE_COMMAND || current_screen == SCREEN_SCRAMBLE || current_screen == SCREEN_DEFENDER || current_screen == SCREEN_GALAGA || current_screen == SCREEN_DUNGEON) {
return;
}
if (now_ms - last_action >= MENU_TIMEOUT_MS) {
// Timeout opens ABOUT because it is the established route into the Demo.
change_screen(SCREEN_ABOUT, now_ms);
return;
}
}