# UI Mode System Architecture in rpi_pico_wav_player: State Machine Design in src/UIMode.h

> Explore the UI mode system architecture in rpi_pico_wav_player. Learn how the state machine design in src/UIMode.h manages screen transitions and modes.

- Repository: [Elehobica/rpi_pico_wav_player](https://github.com/elehobica/rpi_pico_wav_player)
- Tags: architecture
- Published: 2026-03-01

---

**The UI mode system in rpi_pico_wav_player implements a statically-allocated state machine where each screen inherits from the abstract `UIMode` class, transitions occur by returning new mode pointers from `update()`, and all seven concrete modes are managed through a singleton registry in [`src/UIMode.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/UIMode.h).**

The `rpi_pico_wav_player` project uses a compact, resource-efficient UI mode system architecture to manage multiple screens on the Raspberry Pi Pico. Defined primarily in [`src/UIMode.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/UIMode.h) and implemented in [`src/UIMode.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/UIMode.cpp), this architecture employs an abstract base class pattern with static allocation to minimize heap fragmentation while supporting complex navigation flows like file browsing and configuration menus.

## Core Components of the UI Mode Architecture

### The UIMode Abstract Base Class

At the heart of the system is the abstract class `UIMode` defined in [`src/UIMode.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/UIMode.h). This class establishes the contract that every UI screen must fulfill through three pure virtual methods:

- **`update()`**: Processes button events and timer-based logic, returning a `UIMode*` pointer to determine the next active state.
- **`entry()`**: Called when transitioning into the mode, receiving a pointer to the previous mode for context-aware initialization.
- **`draw()`**: Renders the screen content to the LCD via the shared `LcdCanvas` singleton.

The base class also maintains common state including `btn_act` and `btn_unit` for button events, `idle_count` for power management timeouts, and a static pointer to `UIVars` for shared application data.

### Static Mode Registry and ui_mode_ary

Rather than using dynamic allocation, the architecture pre-allocates all mode instances in a static array. The `UIMode` class declares `ui_mode_ary`, a `std::array` containing singleton instances of every concrete mode:

```cpp
// From src/UIMode.h
static std::array<UIMode*, 7> ui_mode_ary;

```

The `UIMode::initialize()` function in [`src/UIMode.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/UIMode.cpp) populates this array by instantiating each concrete class:

```cpp
void UIMode::initialize(UIVars* vars) {
    ui_mode_ary[InitialMode] = new UIInitialMode(vars);
    ui_mode_ary[ChargeMode] = new UIChargeMode(vars);
    ui_mode_ary[OpeningMode] = new UIOpeningMode(vars);
    ui_mode_ary[FileViewMode] = new UIFileViewMode(vars);
    ui_mode_ary[PlayMode] = new UIPlayMode(vars);
    ui_mode_ary[ConfigMode] = new UIConfigMode(vars);
    ui_mode_ary[PowerOffMode] = new UIPowerOffMode(vars);
}

```

Access to modes occurs through `UIMode::getUIMode(ui_mode_enm_t mode)`, which returns the pointer stored in the array.

### Shared State with UIVars

All modes share mutable state through the `UIVars` structure, passed to each constructor and stored as a static member in the base class. This structure contains:

- File system navigation data (`dir_stack` for directory hierarchy traversal)
- Playback state (current file index, play mode settings)
- UI configuration (cursor positions, display preferences)
- System flags (battery status, USB connection state)

The `dir_stack`, implemented as a static `std::stack<std::string>`, enables the file browser modes to remember navigation history when drilling into subdirectories.

## State Machine Implementation and Mode Transitions

### The Main Loop State Machine

The UI mode system architecture operates as a cooperative state machine driven by the main loop in [`src/arduino_main.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/arduino_main.cpp). The loop maintains a pointer to the current mode and repeatedly executes the update-draw cycle:

```cpp
static UIMode* cur = UIMode::getUIMode(UIInitialMode);

while (true) {
    cur = cur->update();  // State transition happens here
    cur->draw();          // Render the new state
}

```

This design decouples the main application logic from individual screen implementations, allowing each mode to encapsulate its own behavior while the framework handles the lifecycle.

### Transition Logic in update() Methods

State transitions occur when a mode's `update()` method returns a pointer to a different mode rather than `this`. The base class provides the `getUIMode()` accessor to retrieve mode instances by their enum identifier:

```cpp
// Example transition from any mode to the file browser
UIMode* SomeMode::update() {
    ui_get_btn_evt(btn_act, btn_unit);  // Check for input
    
    if (btn_act == Enter && btn_unit == Center) {
        // Transition to file view mode
        return UIMode::getUIMode(FileViewMode);
    }
    
    // Remain in current mode
    return this;
}

```

When a transition occurs, the new mode's `entry()` method receives a pointer to the previous mode, enabling context-aware initialization. For example, `UIFileViewMode` uses this to determine whether it arrived from the opening screen or from playback.

## Concrete UI Mode Implementations

The architecture defines seven concrete mode classes, each handling a distinct screen:

- **UIInitialMode**: Displays the splash screen and determines whether to proceed to charging or opening based on USB power detection.
- **UIChargeMode**: Shows battery charging status and implements idle timeout logic for automatic power-off.
- **UIOpeningMode**: Mounts the SD card, restores the navigation stack from flash memory, and displays the opening logo.
- **UIFileViewMode**: Implements the file browser with directory listing, navigation via `dir_stack`, and file selection for playback.
- **UIPlayMode**: Manages audio decoding, displays track information via `readTag()`, and handles playback controls (play, pause, skip).
- **UIConfigMode**: Presents hierarchical configuration menus for volume, UI layout, and playback preferences.
- **UIPowerOffMode**: Saves the current navigation state to flash via `storeToFlash()`, performs clean shutdown, and manages reboot sequences.

Each class overrides the three virtual methods from `UIMode`, with `UIFileViewMode` and `UIPlayMode` containing the most complex logic due to their interaction with the file system and audio decoder.

## Key Source Files and Build Integration

| File | Role in UI Architecture |
|------|-------------------------|
| **[`src/UIMode.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/UIMode.h)** | Declares the abstract `UIMode` class, `UIVars` struct, mode enum, and interfaces for all concrete modes. |
| **[`src/UIMode.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/UIMode.cpp)** | Implements base class functionality including `initialize()`, `getUIMode()`, and all concrete mode methods. |
| **[`src/arduino_main.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/arduino_main.cpp)** | Contains the main state machine loop that drives `update()` and `draw()` calls. |
| **[`src/ui_control.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/ui_control.h)** / **[`src/ui_control.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/ui_control.cpp)** | Provides `ui_get_btn_evt()` for button event processing used by all modes. |
| **[`src/LcdCanvas.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/LcdCanvas.h)** / **[`src/LcdCanvas.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/LcdCanvas.cpp)** | Singleton LCD abstraction used by `draw()` methods across all modes. |
| **[`src/file_menu_FatFs.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/file_menu_FatFs.h)** | File system interface used by `UIFileViewMode` for directory navigation. |

## Summary

- The **UI mode system architecture** in `rpi_pico_wav_player` implements a statically-allocated state machine centered on the abstract `UIMode` class defined in [`src/UIMode.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/UIMode.h).
- **Seven concrete modes** (Initial, Charge, Opening, FileView, Play, Config, PowerOff) inherit from `UIMode` and implement `update()`, `entry()`, and `draw()` methods.
- **State transitions** occur when `update()` returns a pointer to a different mode retrieved via `UIMode::getUIMode()`, while the main loop in [`arduino_main.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/arduino_main.cpp) drives the state machine.
- **Shared resources** including `UIVars`, `LcdCanvas`, and `dir_stack` enable consistent navigation, display rendering, and file system browsing across all modes without dynamic memory allocation during runtime.

## Frequently Asked Questions

### How does the UI mode system handle state transitions between screens?

State transitions are handled through return values in the `update()` method. When a mode wants to change screens, it returns a pointer to the target mode obtained via `UIMode::getUIMode(ModeEnum)`. If no transition is needed, the method returns `this`. The main loop in [`src/arduino_main.cpp`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/arduino_main.cpp) assigns this return value to the current mode pointer, effectively switching the active state machine context.

### What is the purpose of the UIVars structure in the UI architecture?

`UIVars` serves as the global mutable state container shared across all UI modes. Defined in [`src/UIMode.h`](https://github.com/elehobica/rpi_pico_wav_player/blob/main/src/UIMode.h) and passed to each mode during initialization, it stores critical runtime data including the directory navigation stack (`dir_stack`), current file cursor positions, playback state, battery status, and configuration flags. This design eliminates the need for modes to maintain separate state copies while ensuring consistent data access during screen transitions.

### How does the file browser mode manage directory navigation history?

The `UIFileViewMode` class utilizes a static `std::stack<std::string>` called `dir_stack` to track directory traversal history. When users navigate into subdirectories, the current path is pushed onto the stack. When returning to parent directories or transitioning between related modes, the stack provides the navigation context needed to restore the file browser's position. This mechanism is part of the shared `UIVars` structure accessible to all modes.

### Why does the architecture use a static array instead of dynamic allocation for modes?

The `UIMode` architecture uses a static `std::array` named `ui_mode_ary` to store singleton instances of all concrete modes, populated once during `UIMode::initialize()`. This design choice eliminates heap fragmentation risks on the resource-constrained Raspberry Pi Pico microcontroller, ensures deterministic memory usage, and provides O(1) access to any mode via `getUIMode()`. The approach aligns with embedded systems best practices where dynamic memory allocation during runtime is minimized to prevent memory leaks and fragmentation.