Files
onepilot/UICheatSheet.md
T
firestar5683 d0e1db6766 StarPilot
2026-03-22 03:15:05 -05:00

128 KiB
Raw Blame History

StarPilot UI Architecture: Qt to Raylib Porting Cheat Sheet

This document serves as the definitive reference for porting the Qt-based Big UI to the Raylib-based Big UI in StarPilot. It provides direct parallels, code patterns, and implementation guidance.

Last updated: March 17, 2026 (Late Session)Fully implemented Driving Model panel with High-Fidelity Qt SelectionDialog (Favorites/Stars, Premium styling, Sort cycling), Model Auto-Fetch, and robust background download management. Completed Sounds panel with Alert Volume Controller and Custom Alerts. Added StarPilotState singleton, InputDialog, Lateral full implementation (5 sub-panels), 11 value factory functions, Params helpers, and sound testing architecture.


Table of Contents

  1. High-Level Architecture Comparison
  2. Key Parallels: Qt to Raylib
  3. Code Structure Patterns
  4. Onroad UI Comparison
  5. Sidebar Comparison
  6. Settings Layout Comparison
  7. Implementation Differences
  8. Files Requiring Work for Full Port
  9. StarPilot Complete Implementation Roadmap
  10. Technical Reference
  11. RayGUI Analysis (Aborted)

1. High-Level Architecture Comparison

1.1 Qt UI Structure (Current Legacy)

MainWindow (QStackedLayout)
├── HomeWindow (QHBoxLayout)
│   ├── Sidebar (fixed 300px width)
│   └── QStackedLayout
│       ├── OffroadHome (home.cc)
│       ├── OnroadWindow (onroad_home.cc)
│       ├── BodyWindow (body.cc)
│       └── DriverViewWindow (driverview.cc)
├── SettingsWindow (QStackedWidget)
│   └── Multiple panels (Device, Network, Toggles, Software, Firehose, Developer)
└── OnboardingWindow

Entry Point: selfdrive/ui/main.cc → compiled binary selfdrive/ui/ui

Key Files:

  • selfdrive/ui/qt/window.cc (Main window management)
  • selfdrive/ui/qt/home.cc (Home window container)
  • selfdrive/ui/qt/sidebar.cc (Sidebar implementation)
  • StarPilot Qt Panels: starpilot/ui/qt/offroad/starpilot_settings.cc/h (plus lateral_settings.cc, longitudinal_settings.cc, etc.)
  • Raylib Port: selfdrive/ui/layouts/settings/starpilot (Modularized!)
  • UI State Singleton: selfdrive/ui/lib/starpilot_state.py (Centralized StarPilotState)
  • selfdrive/ui/qt/offroad/settings.cc (Settings panels)

1.2 Raylib Big UI Structure (Current)

MainLayout (Widget)
├── Sidebar (300px width - selfdrive/ui/layouts/sidebar.py)
└── State Machine (MainState enum)
    ├── HOME → HomeLayout (selfdrive/ui/layouts/home.py)
    ├── SETTINGS → SettingsLayout (selfdrive/ui/layouts/settings/settings.py)
    └── ONROAD → AugmentedRoadView (selfdrive/ui/onroad/augmented_road_view.py)

The StarPilot Raylib Big UI is built on a modular "Panel" architecture. The main entry point is StarPilotLayout (in main_panel.py), which manages a stack of StarPilotPanel instances.

Entry Point: selfdrive/ui/ui.py (lines 13-36)

Key Files:

  • selfdrive/ui/ui.py (Entry point)
  • selfdrive/ui/layouts/main.py (Main layout container)
  • selfdrive/ui/layouts/sidebar.py (Sidebar implementation)
  • system/ui/lib/application.py (GuiApplication class)

1.3 Device Resolution Reference

Device UI Framework Resolution
TICI/TIZI Qt (C++) 2160x1080
MICI/PC Raylib (Python) Big: 2160x1080 / Small: 536x240

Note: The Raylib UI is used on ALL devices (TICI, TIZI, MICI, PC). Big UI (2160x1080) is shown on TICI/TIZI or when BIG=1 environment variable is set.


2. Key Parallels: Qt to Raylib

2.1 Navigation & Layout

Qt Component Raylib Equivalent File Location
QStackedLayout MainState enum + dictionary layouts/main.py:15-32
QHBoxLayout Manual rect calculation layouts/main.py:61-65
QWidget.show()/hide() show_event()/hide_event() widgets/__init__.py:180-184
setFixedWidth(300) SIDEBAR_WIDTH = 300 layouts/sidebar.py:12
setVisible(bool) set_visible(bool) widgets/__init__.py:66-67
QStackedWidget Dictionary + _set_current_layout() layouts/main.py:83-87

2.2 Widget System

Qt Widget Raylib Widget File Location
QFrame Widget (base class) widgets/__init__.py:22-184
QPushButton Button widgets/button.py:12-24
QToggle Toggle widgets/toggle.py:17-79
QLabel Label, MiciLabel, UnifiedLabel widgets/label.py:30+
Custom paint _render() method All widgets
mousePressEvent _handle_mouse_event() widgets/__init__.py:114-153
QLayout Manual rect calculation Per layout file

2.3 Settings Panels

Qt Panel Raylib Panel Status Location
DevicePanel DeviceLayout Implemented layouts/settings/device.py
NetworkPanel NetworkUI Implemented system/ui/widgets/network.py
TogglesPanel TogglesLayout ⚠️ Basic only layouts/settings/toggles.py
SoftwarePanel SoftwareLayout Implemented layouts/settings/software.py
FirehosePanel FirehoseLayout Implemented layouts/settings/firehose.py
DeveloperPanel DeveloperLayout Implemented layouts/settings/developer.py

2.4 StarPilot Qt Panels

Qt Panel Purpose Qt File Location Raylib Status
StarPilotDataPanel Data settings starpilot/ui/qt/offroad/data_settings.cc 🟡 Implemented (Stub)
StarPilotDevicePanel Device controls starpilot/ui/qt/offroad/device_settings.cc 🟡 Implemented (Stub)
StarPilotLateralPanel Steering controls starpilot/ui/qt/offroad/lateral_settings.cc Implemented (5 sub-panels with real controls)
StarPilotLongitudinalPanel Gas/Brake controls starpilot/ui/qt/offroad/longitudinal_settings.cc 🟡 Implemented (Weather sub-panels with real value controls)
StarPilotMapsPanel Map data starpilot/ui/qt/offroad/maps_settings.cc 🟡 Implemented (Stub)
StarPilotModelPanel Driving model starpilot/ui/qt/offroad/model_settings.cc Implemented (Auto-fetch, Selection, Download, Favorites, Sort)
StarPilotNavigationPanel Navigation starpilot/ui/qt/offroad/navigation_settings.cc 🟡 Implemented (Stub)
StarPilotSoundsPanel Alerts and sounds starpilot/ui/qt/offroad/sounds_settings.cc Implemented (Alert Volume Controller + Custom Alerts sub-panels)
StarPilotThemesPanel Theme settings starpilot/ui/qt/offroad/theme_settings.cc 🟡 Implemented (MANAGE buttons)
StarPilotUtilitiesPanel Utilities starpilot/ui/qt/offroad/utilities.cc 🟡 Implemented (Stub)
StarPilotVehiclesPanel Vehicle settings starpilot/ui/qt/offroad/vehicle_settings.cc 🟡 Implemented (Stub)
StarPilotVisualsPanel Appearance/Visuals starpilot/ui/qt/offroad/visual_settings.cc 🟡 Implemented (MANAGE buttons)
StarPilotWheelPanel Wheel controls starpilot/ui/qt/offroad/wheel_settings.cc 🟡 Implemented (Stub)

Main Entry: starpilot/ui/qt/offroad/starpilot_settings.cc - Contains category navigation Raylib Implementation: selfdrive/ui/layouts/settings/starpilot.py - Full sub-panel hierarchy implemented

Qt Back Navigation:

  • Uses Qt signals: closeSubPanel(), closeSubSubPanel() signals
  • Parent SettingsWindow emits these signals, child panels listen and respond
  • Each sub-panel has a way to emit signal to return to previous view

2.5 StarPilot Raylib Folders

  • selfdrive/ui/layouts/settings/starpilot/ (Directory containing 14+ setting modules)

2.6 StarPilot Key Parallels

  • Qt StackedLayoutRaylib _panels dictionary in main_panel.py
  • Qt StarPilotPanelTypeRaylib StarPilotPanelType in panel.py (Integer-based routing)
  • Qt Panel::showEvent()Raylib Widget.show_event() (Used for dynamic range calculation)

3. Code Structure Patterns

3.1 Signal/Slot vs Callback Pattern

Qt Signal/Slot

// From selfdrive/ui/qt/home.cc
QObject::connect(sidebar, &Sidebar::openSettings, this, &HomeWindow::openSettings);
QObject::connect(home, &OffroadHome::openSettings, this, &HomeWindow::openSettings);
QObject::connect(settingsWindow, &SettingsWindow::closeSettings, this, &MainWindow::closeSettings);

Raylib Callback

# From selfdrive/ui/layouts/main.py
def _setup_callbacks(self):
    self._sidebar.set_callbacks(on_settings=self._on_settings_clicked,
                                on_flag=self._on_bookmark_clicked,
                                open_settings=lambda: self.open_settings(PanelType.TOGGLES))
    self._layouts[MainState.HOME]._setup_widget.set_open_settings_callback(...)
    self._layouts[MainState.SETTINGS].set_callbacks(on_close=self._set_mode_for_state)
    self._layouts[MainState.ONROAD].set_click_callback(self._on_onroad_clicked)
    device.add_interactive_timeout_callback(self._set_mode_for_state)

3.2 Widget Callback Registration

Qt

// From selfdrive/ui/qt/widgets/toggle.cc
QObject::connect(toggle, &Toggle::stateChanged, this, &TogglesPanel::toggleToggled);

Raylib

# From system/ui/widgets/toggle.py
class Toggle(Widget):
    def __init__(self, initial_state: bool = False, callback: Callable[[bool], None] | None = None):
        self._callback = callback
    
    def _handle_mouse_release(self, mouse_pos: MousePos):
        self._state = not self._state
        if self._callback:
            self._callback(self._state)

3.3 Paint Event vs Render Method

Qt Custom Paint

// From selfdrive/ui/qt/sidebar.cc
void Sidebar::drawMetric(QPainter &p, const QPair<QString, QString> &label, QColor c, int y) {
    const QRect rect = {30, y, 240, 126};
    p.setPen(Qt::NoPen);
    p.setBrush(QBrush(c));
    p.drawRoundedRect(QRect(rect.x() + 4, rect.y() + 4, 100, 118), 18, 18);
}

Raylib Render

# From selfdrive/ui/layouts/sidebar.py
def _draw_metric(self, rect: rl.Rectangle, label: str, value: str, color: rl.Color):
    # Draw colored bar
    bar_rect = rl.Rectangle(rect.x + 4, rect.y + 4, 100, 118)
    rl.draw_rectangle_rounded(bar_rect, 1.0, 18, color)

3.4 Toggle Implementation Comparison

Qt Toggle (widgets/toggle.cc)

  • Uses QPropertyAnimation for knob animation
  • Custom painting with QPainter
  • 80px height, variable width
  • Green (#178644) when ON, Gray (#393939) when OFF

Raylib Toggle (widgets/toggle.py)

WIDTH, HEIGHT = 160, 80
BG_HEIGHT = 60
ANIMATION_SPEED = 8.0

ON_COLOR = rl.Color(51, 171, 76, 255)   # Green
OFF_COLOR = rl.Color(0x39, 0x39, 0x39, 255)  # Dark gray

class Toggle(Widget):
    def _render(self, rect: rl.Rectangle):
        # Background
        bg_rect = rl.Rectangle(self._rect.x + 5, self._rect.y + 10, WIDTH - 10, BG_HEIGHT)
        rl.draw_rectangle_rounded(bg_rect, 1.0, 10, bg_color)
        
        # Knob
        knob_x = self._rect.x + HEIGHT / 2 + (WIDTH - HEIGHT) * self._progress
        rl.draw_circle(int(knob_x), int(knob_y), HEIGHT / 2, knob_color)

3.5 State Management Pattern

Qt State

// From selfdrive/ui/qt/home.cc
void HomeWindow::updateState(const UIState &s, const StarPilotUIState &fs) {
    if (s.scene.started) {
        if (starpilot_scene.driver_camera_timer >= UI_FREQ / 2) {
            showDriverView(true, true);
        } else {
            slayout->setCurrentWidget(onroad);
        }
    }
}

Raylib State

# From selfdrive/ui/layouts/main.py
class MainState(IntEnum):
    HOME = 0
    SETTINGS = 1
    ONROAD = 2

class MainLayout(Widget):
    def _handle_onroad_transition(self):
        if ui_state.started != self._prev_onroad:
            self._prev_onroad = ui_state.started
            self._set_mode_for_state()
    
    def _set_mode_for_state(self):
        if ui_state.started:
            self._set_current_layout(MainState.ONROAD)
        else:
            self._set_current_layout(MainState.HOME)

4. Onroad UI Comparison

4.1 Qt Onroad Components

OnroadWindow (onroad_home.cc)
├── AnnotatedCameraWidget (camera + lane lines + path)
│   ├── ModelRenderer (lane lines, path predictions)
│   └── HudRenderer (speed, cruise control)
├── OnroadAlerts (alert messages)
├── StarPilotAnnotatedCameraWidget (StarPilot overlays)
└── StarPilotOnroadWindow (additional overlays)
    ├── Blind spot visualization
    ├── FPS counter
    ├── Steering torque metrics
    └── Turn signal indicators

Key Files:

  • selfdrive/ui/qt/onroad/onroad_home.cc
  • selfdrive/ui/qt/onroad/annotated_camera.cc
  • selfdrive/ui/qt/onroad/hud.cc
  • selfdrive/ui/qt/onroad/alerts.cc
  • selfdrive/ui/qt/onroad/buttons.cc
  • starpilot/ui/qt/onroad/starpilot_onroad.cc

4.2 Raylib Onroad Components

AugmentedRoadView (augmented_road_view.py - extends CameraView)
├── ModelRenderer (lane lines, path predictions) ✅
├── HudRenderer (speed, cruise control, status) ✅
├── AlertRenderer (driving alerts) ✅
└── DriverStateRenderer (driver monitoring) ✅

Key Files:

  • selfdrive/ui/onroad/augmented_road_view.py (234 lines)
  • selfdrive/ui/onroad/model_renderer.py
  • selfdrive/ui/onroad/hud_renderer.py (180 lines)
  • selfdrive/ui/onroad/alert_renderer.py
  • selfdrive/ui/onroad/driver_state.py
  • selfdrive/ui/onroad/cameraview.py

4.3 Onroad Features: Qt vs Raylib

Feature Qt Raylib Notes
Camera feed
Lane lines
Path predictions
Speed display
Cruise control
Alert messages
Driver monitoring
Blind spot metrics Needs port
FPS counter Needs port
Steering torque Needs port
Turn signals Needs port
Theme support ⚠️ Partial

5. Sidebar Comparison

5.1 Qt Sidebar (qt/sidebar.cc - 302 lines)

// Fixed 300px width
Sidebar::Sidebar(QWidget *parent) : QFrame(parent) {
    setFixedWidth(300);
    
    // Metrics displayed:
    // - Temperature (with color coding)
    // - CPU usage (with tap-to-cycle)
    // - Memory usage (with tap-to-cycle)
    // - Panda status
    // - Network status
    
    // Buttons:
    // - Home button (180x180)
    // - Flag button
    // - Settings button (200x117)
}

5.2 Raylib Sidebar (layouts/sidebar.py - 229 lines)

SIDEBAR_WIDTH = 300
METRIC_HEIGHT = 126
METRIC_WIDTH = 240
METRIC_MARGIN = 30
FONT_SIZE = 35

class Sidebar(Widget):
    def __init__(self):
        # Same metrics: Temperature, CPU, Memory, Panda, Network
        # Same buttons with texture-based rendering
        
        self._home_img = gui_app.texture("images/button_home.png", HOME_BTN.width, HOME_BTN.height)
        self._flag_img = gui_app.texture("images/button_flag.png", HOME_BTN.width, HOME_BTN.height)
        self._settings_img = gui_app.texture("images/button_settings.png", SETTINGS_BTN.width, SETTINGS_BTN.height)

5.3 Differences

Aspect Qt Raylib
Width 300px 300px
Temperature with color with color
CPU with tap cycle basic
Memory with tap cycle basic
Panda
Network
Developer toggle

6. Settings Layout Comparison

6.1 Qt Settings Structure

// From selfdrive/ui/qt/offroad/settings.cc
SettingsWindow::SettingsWindow(QWidget *parent) {
    // Left sidebar navigation (icon + text)
    // Panel selector: Device, Network, Toggles, Software, Firehose, Developer
    
    // Panel content area on right
    // Scrollable content within each panel
}

6.2 Raylib Settings Structure

# From selfdrive/ui/layouts/settings/settings.py
SIDEBAR_WIDTH = 500  # Wider than main sidebar
NAV_BTN_HEIGHT = 110
PANEL_MARGIN = 50

class PanelType(IntEnum):
    DEVICE = 0
    NETWORK = 1
    TOGGLES = 2
    SOFTWARE = 3
    FIREHOSE = 4
    DEVELOPER = 5

class SettingsLayout(Widget):
    def _render(self, rect: rl.Rectangle):
        # Left sidebar (500px) with nav buttons
        # Right panel area
        self._draw_sidebar(sidebar_rect)
        self._draw_current_panel(panel_rect)

6.2.1 Settings Panel Width Comparison

Panel Qt Sidebar Raylib Sidebar
Main Settings 300px 500px

7. Implementation Differences

7.1 Language & Framework

Aspect Qt Raylib
Language C++ Python
Graphics API Qt QPainter pyray (OpenGL wrapper)
Event System Qt event loop raylib input + custom mouse thread

7.2 Event Loop

Qt Event Loop

  • Qt's built-in signal/slot mechanism
  • QApplication::exec() runs the loop
  • Events dispatched via QObject::event()

Raylib Event Loop

# From selfdrive/ui/ui.py
def main():
    gui_app.init_window("UI")
    if gui_app.big_ui():
        main_layout = MainLayout()
    else:
        main_layout = MiciMainLayout()
    
    for should_render in gui_app.render():  # Generator
        ui_state.update()
        if should_render:
            main_layout.render()

7.3 Text Rendering

Qt

p.setFont(InterFont(35, QFont::DemiBold));
p.drawText(rect, Qt::AlignCenter, text);

Raylib

# From system/ui/widgets/label.py
font = gui_app.font(FontWeight.NORMAL)
rl.draw_text_ex(font, text, position, font_size, spacing, color)

7.4 Layout Calculations

Qt (Automatic)

QHBoxLayout *main_layout = new QHBoxLayout(this);
main_layout->setMargin(0);
main_layout->setSpacing(0);
main_layout->addWidget(sidebar);

Raylib (Manual)

# From selfdrive/ui/layouts/main.py
def _update_layout_rects(self):
    self._sidebar_rect = rl.Rectangle(self._rect.x, self._rect.y, SIDEBAR_WIDTH, self._rect.height)
    x_offset = SIDEBAR_WIDTH if self._sidebar.is_visible else 0
    self._content_rect = rl.Rectangle(self._rect.y + x_offset, self._rect.y, self._rect.width - x_offset, self._rect.height)

7.5 Animations

Qt (Property Animation)

// From selfdrive/ui/qt/widgets/toggle.cc
_anim = new QPropertyAnimation(this, "offset_circle", this);
_anim->setStartValue(on ? left + immediateOffset : right - immediateOffset);
_anim->setEndValue(on ? right : left);
_anim->setDuration(animation_duration);
_anim->start();

Raylib (Manual Interpolation)

# From system/ui/widgets/toggle.py
def update(self):
    if abs(self._progress - self._target) > 0.01:
        delta = rl.get_frame_time() * ANIMATION_SPEED
        self._progress += delta if self._progress < self._target else -delta
        self._progress = max(0.0, min(1.0, self._progress))

8. Files Requiring Work for Full Port

8.1 Priority 1: Core Infrastructure

File Description Status
selfdrive/ui/layouts/settings/ StarPilot panels 🟡 Structure Done (stubs for most, Lateral fully implemented)
system/ui/widgets/toggle.py Add description support Done
system/ui/widgets/input_dialog.py Text input dialog with keyboard Done (NEW)
system/ui/widgets/selection_dialog.py Radio-button selection dialog Done (NEW)
system/ui/widgets/confirm_dialog.py Confirm/alert dialog with rich text Done (UPDATED)
selfdrive/ui/lib/starpilot_state.py Car state + StarPilotState singleton Done (NEW)
common/params.py Params get_int/get_float/put_int/put_float Done (UPDATED)
selfdrive/ui/layouts/sidebar.py Developer metrics toggle 🔴 Not Started

8.1.1 StarPilot Panel Hierarchy (Implemented)

StarPilot Settings
├── Tuning Level ✅
├── Category: Alerts and Sounds → SOUNDS ✅ (Alert Volume Controller + Custom Alerts sub-panels with real controls)
│   ├── Alert Volume Controller ✅ (7 volume sliders with Test buttons, persistent subprocess for offroad sound testing)
│   └── Custom Alerts ✅ (5 toggles with conditional visibility: BSM, ShowSpeedLimits/SpeedLimitController)
├── Category: Driving Controls
│   ├── DRIVING_MODEL ✅ (Auto-fetch, Premium SelectionDialog, Managed background downloads, Blacklist/Ratings)
│   ├── LONGITUDINAL 🟡 (MANAGE buttons + Weather sub-panels with real value_item controls)
│   └── LATERAL ✅ (5 sub-panels with REAL controls: value_item, value_button_item, toggle_item)
│       ├── Advanced Lateral Tuning ✅ (8 controls: 5 value_button_item + 3 toggle_item)
│       ├── Always On Lateral ✅ (3 controls)
│       ├── Lane Changes ✅ (6 controls with conditional visibility)
│       ├── Lateral Tuning ✅ (3 toggles with reboot confirmation)
│       └── Quality of Life ✅ (1 value_button_item with sub-toggle)
├── Category: Navigation
│   ├── MAPS 🟡 (stub)
│   └── NAVIGATION 🟡 (stub)
├── Category: System Settings
│   ├── DATA 🟡 (stub)
│   ├── DEVICE 🟡 (stub)
│   └── UTILITIES 🟡 (stub)
├── Category: Theme and Appearance
│   ├── VISUALS 🟡 (5 MANAGE buttons)
│   └── THEMES 🟡 (stub)
└── Category: Vehicle Settings
    ├── VEHICLE 🟡 (stub)
    └── WHEEL 🟡 (stub)

Legend: = Full | 🟡 = Structure/stub | 🔴 = Not started

Completed (March 16, 2026)

  • Added StarPilotLayout entry to settings/settings.py
  • Created layouts/settings/starpilot.py with full sub-panel navigation system
  • Added StarPilotPanelType enum with 14 panel types for proper sub-panel routing:
    • MAIN, SOUNDS, DRIVING_MODEL, LONGITUDINAL, LATERAL, MAPS, NAVIGATION,
    • DATA, DEVICE, UTILITIES, VISUALS, THEMES, VEHICLE, WHEEL
  • Implemented StarPilotSoundsLayout with 6 toggle items for custom alerts
  • Implemented StarPilotDrivingModelLayout with toggles + buttons
  • Implemented StarPilotLongitudinalLayout, StarPilotLateralLayout with MANAGE buttons
  • Implemented stub layouts for all remaining panels (Maps, Navigation, Data, Device, Utilities, Visuals, Themes, Vehicle, Wheel)
  • Added tuning level visibility filtering to sub-panels (set_tuning_levels(), refresh_visibility())
  • Uses button_item with "MANAGE" button text matching Qt UI
  • Added starpilot_texture() method to GuiApplication for loading StarPilot assets
  • Added starpilot_icon parameter to button_item() and toggle_item() for StarPilot icon support
  • StarPilot assets remain in starpilot/assets/ (not copied)
  • Descriptions use HTML bold tags (<b>...</b>) matching Qt format
  • Toggle callbacks properly persist settings via Params
  • Hierarchical back navigation: Settings sidebar back button handles depth > 0 (go back) vs depth 0 (close)
  • Category buttons: Fixed with per-button width calculation + cumulative positioning + matching width_hint

Completed (March 17, 2026)

  • Created StarPilotState singleton (selfdrive/ui/lib/starpilot_state.py) with:
    • StarPilotCarState dataclass with all car type, capability, and value fields
    • Reads CarParamsPersistent, FrogPilotCarParamsPersistent, LiveTorqueParameters, FrogPilotToggles from Params
    • Throttled updates (2.0s interval) to avoid slowing UI
    • PC/desktop fallback mode with configurable car make/model
    • Global import: from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state
  • Fully implemented Lateral panel with 5 sub-panels and real controls:
    • StarPilotAdvancedLateralLayout: 5 value_button_item controls with car-specific dynamic ranges (starpilot_state.car_state.steerKp * 0.5 to * 1.5) + Reset buttons + 3 toggle items with conditional visibility based on car state
    • StarPilotAlwaysOnLateralLayout: 2 toggles (with reboot confirmation) + 1 value slider
    • StarPilotLaneChangesLayout: 6 controls with conditional visibility (LaneChanges AND NudgelessLaneChange)
    • StarPilotLateralTuneLayout: 3 toggles (NNFF, NNFFLite, TurnDesires) with reboot confirmation
    • StarPilotLateralQOLLayout: 1 value_button_item with sub-toggle
  • Weather sub-panels now use real value_item() controls with put_int() callbacks
  • Created InputDialog widget (system/ui/widgets/input_dialog.py):
    • Full keyboard input with Keyboard widget integration
    • Text input field with hint text, blinking cursor
    • Confirm/Cancel buttons, dimmed background overlay
    • Callback: on_close(DialogResult, str) returns entered text
  • Created SelectionDialog widget (system/ui/widgets/selection_dialog.py):
    • Radio-button selection from a list of options
    • Scrollable list using Scroller widget
    • Visual feedback: green dot for selected, circle outline for unselected
    • Callback: on_close(DialogResult, int, str) returns index and text
  • Extended Params wrapper (common/params.py):
    • get_int(key, block, return_default, default) — Parses string param as int
    • get_float(key, block, return_default, default) — Parses string param as float
    • put_int(key, val) — Type-aware save (checks get_type() for FLOAT/INT/BOOL/string)
    • put_float(key, val) — Same type-aware save for float values
    • Fixes TypeError when saving FLOAT-typed params like IncreaseFollowingLowVisibility
  • Updated ConfirmDialog (system/ui/widgets/confirm_dialog.py):
    • Added rich mode with HtmlRenderer + Scroller for rich text content
    • Scrollable text area for long messages
    • Keyboard shortcut support: Enter (confirm), Escape (cancel)
  • Settings back button icon changed from icons/close2.pngicons/backspace.png
  • Zero-size image guard in application.py: _load_image_from_path now returns early if image.width == 0 or image.height == 0, preventing potential crashes from empty textures

Completed (March 17, 2026 - Late Session)

  • Fully Implemented Driving Model Panel:
    • Integrated ModelManager for auto-fetching model lists if empty.
    • Implemented managed background download thread in starpilot.py for desktop/PC environments (Daemon-less).
    • Added blacklist and ratings management dialogs.
  • High-Fidelity SelectionDialog Enhancements:
    • Favorites (Star) System: Every item can be starred (♥/♡). Persisted in UserFavorites and CommunityFavorites.
    • Premium Styling: Matched Qt header (#333333 BG) and selection (#465BEA BG + 3px white border).
    • Cyclic Sort Mode: Sort button cycles through: Alphabetical -> Newest -> Oldest -> Favorites First.
    • Disclosure Triangles: Used text-based symbols (/) for collapsible categories to match Qt without complex mesh drawing.
    • Width Inheritance: Fixed hit-testing and alignment by ensuring list items span the full width of the scroller.

Completed (March 17, 2026) - Sounds Panel Full Implementation

  • Sounds panel restructured to use sub-panel navigation:
    • Main panel has 2 MANAGE buttons: "Alert Volume Controller" and "StarPilot Alerts"
    • StarPilotVolumeControlLayout: 7 volume sliders (Disengage, Engage, Prompt, PromptDistracted, Refuse, WarningSoft, WarningImmediate)
    • StarPilotCustomAlertsLayout: 5 toggles (GoatScream, GreenLightAlert, LeadDepartingAlert, LoudBlindspotAlert, SpeedLimitChangedAlert)
  • Volume slider features:
    • Range 0-101 (0=Muted, 101=Auto, 1-100=percentage)
    • WarningSoft/WarningImmediate have min=25 (can't go below 25%)
    • Each slider has "Test" button to preview sound
  • Sound testing architecture (critical fix):
    • Offroad: Uses persistent Python subprocess (matching Qt's initializeSoundPlayer())
    • Subprocess runs a while loop reading path|volume\n lines from stdin
    • Uses sounddevice to play WAV files with volume scaling
    • No threading - stdin automatically serializes writes (subprocess isolation prevents memory corruption)
    • Theme sounds take priority over stock sounds (ACTIVE_THEME_PATH / "sounds")
    • Onroad: Uses TestAlert param in params_memory (handled by soundd.py)
  • Custom Alerts conditional visibility:
    • LoudBlindspotAlert: Visible only if starpilot_state.car_state.hasBSM
    • SpeedLimitChangedAlert: Visible if ShowSpeedLimits OR (hasOpenpilotLongitudinal AND SpeedLimitController)
  • Sub-panel navigation: _setup_sounds_sub_panels() in StarPilotLayout wires up navigation callbacks

8.1.1 Implementation Guide - Adding New StarPilot Sub-Panels (The Modular Way)

Since the StarPilot settings split (March 2026), adding or updating panels follows a modular file-based pattern:

  1. Create a new module in selfdrive/ui/layouts/settings/starpilot/ (e.g., new_feature.py).
  2. Inherit from StarPilotPanel (from panel.py):
    from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
    
    class StarPilotNewFeatureLayout(StarPilotPanel):
      def __init__(self):
        super().__init__()
        # Build Layout...
    
  3. Restoring Base Class Methods (IMPORTANT): If you override __init__, always check if you need to restore core methods that the monolithic refactor moved to the base class:
    • self.set_tuning_levels()
    • self._update_state() (for Driving Model)
    • self.refresh_visibility()
  4. Register in main_panel.py:
    • Import the new class.
    • Add a key to StarPilotPanelType.
    • Add to the _panels dictionary in StarPilotLayout.
  5. Update refresh_visibility in main_panel.py: Ensure the new panel is included in the loop to react to tuning level changes.

Key Learning: Large File Prevention

Don't let any single panel layout exceed ~500 lines. If a panel has many sub-sub-panels (like Longitudinal), break those into their own classes or separate files (e.g. longitudinal_tuning.py).

Key Learnings from StarPilot Implementation

  1. Native Pattern is Critical: Always use Scroller + factory functions (button_item, toggle_item, etc.) - never draw custom rectangles. This ensures:

    • Consistent styling with other panels
    • Proper scrolling behavior
    • Built-in description toggle on tap
    • Proper touch/mouse event handling
  2. Sub-Panel Navigation & Boilerplate Reduction: StarPilot uses a DRY architecture via the StarPilotPanel(Widget) base class:

    • Inheriting from StarPilotPanel automatically provides: self._params, self._params_memory, self._tuning_levels, and navigation lifecycle functions.
    • It also automatically handles standard _render() and show_event() routing for self._scroller and self._sub_panels maps.
    • Main categories route to sub-panels via a two-level navigation system using StarPilotPanelType enums.
  3. Asset Loading for External Packages:

    • Use gui_app.texture() for openpilot assets (selfdrive/assets/)
    • Use gui_app.starpilot_texture() for assets located in the core starpilot assets folder.
item.set_icon("icon_steering.png", starpilot=True) # Uses starpilot_texture internally
-   Never copy assets between folders - always load from original location
-   Note: StarPilot toggle icons are in `starpilot/assets/toggle_icons/`
  1. Icon Handling in List Items:

    # For StarPilot icons, use starpilot_icon=True parameter
    item = button_item(
      title_fn, button_fn, desc_fn,
      icon="toggle_icons/icon_sound.png",  # Full path from assets root
      starpilot_icon=True,  # This flag is critical!
    )
    
  2. Description Formatting: Use HTML bold tags to match Qt:

    description=tr_noop("<b>Description text here</b>")
    
  3. Button Text Patterns:

    • Navigation/management: "MANAGE", "VIEW", "CHANGE"
    • NOT ">" or similar symbols
  4. Python 3.12 Compatibility:

    • Use from __future__ import annotations at top of files
    • Use Optional[X] instead of X | None for type hints
    • Use from typing import Callable for callbacks
  5. Known Issues:

    • Empty string handling in set_icon() can cause division by zero if texture loading fails
    • Icons from StarPilot assets require starpilot_icon=True parameter
  6. Back Navigation Pattern:

    • In Raylib, there's no built-in "back" button like in Qt's stacked layout
    • Each sub-panel needs a "Back" button to return to the parent/main view
    • Pattern: Create a callback in the parent layout (_back_to_main) and pass it to child layouts
    • Use button_item with a "<" or "Back" button text for the back action
    • See starpilot.py for the complete implementation
  7. Premium UI "TLC" (High-Fidelity Parity):

    • Colors: Use specific Qt hex codes (e.g., #333333 for headers, #465BEA for selection).
    • Hit Testing: Ensure buttons/items span the full width of their container. Users expect to be able to click anywhere on the row, not just the text.
    • Transitions: When a download starts, the UI should immediately reflect it (e.g., "CANCEL" button instead of "DOWNLOAD").
    • Symbols: Raylib handles Unicode characters well—use fonts that support characters like , , , and for quick, clean UI elements.
  8. Background Process Management:

    • Daemon-less Downloads: On PC/Desktop where starpilot_process.py isn't running, the UI must manage its own background threads.
    • Thread Safety: Always wrap long-running UI-initiated threads in try-except blocks. If they crash, they fail silently in Raylib.
    • Progress Tracking: Poll Params (memory) for progress updates (ModelDownloadProgress) to keep the UI responsive.
  9. Tuning Level System & Developer Panel Visibility

    The Qt UI has a sophisticated tuning level system that controls which StarPilot settings are visible:

    • Tuning Levels: 0=Minimal, 1=Standard, 2=Advanced, 3=Developer
    • Each StarPilot toggle has a minimum required tuning level (stored in starpilotToggleLevels map)
    • Toggle visibility: tuningLevel >= starpilotToggleLevels[key]

    IMPORTANT - Two Different "Developer" Concepts:

    • "Developer" Tuning Level (level 3 in StarPilot's Tuning Level selector): The highest level that unlocks ALL StarPilot settings
    • "Developer" Panel (separate panel in Settings sidebar): ONLY visible when Tuning Level >= 3

    These are different features that share the same name! This matches Qt behavior:

    // From settings.cc - Developer panel visibility
    void SettingsWindow::updateDeveloperToggle(int tuningLevel) {
      for (QAbstractButton *btn : nav_btns->buttons()) {
        if (btn->text() == tr("Developer")) {
          btn->setVisible(tuningLevel >= 3);
          break;
        }
      }
    }
    

    Implementation in Raylib:

    • StarPilotLayout stores reference to SettingsLayout via set_settings_layout()
    • When tuning level changes, calls settings_layout.refresh_developer_visibility()
    • Developer panel checked dynamically on each render
  10. Category Buttons with Horizontal Layout

    Qt displays multiple buttons horizontally next to each category title. Implemented in Raylib using a new custom action:

    New Component: CategoryButtonsAction

    # In system/ui/widgets/list_view.py
    class CategoryButtonsAction(ItemAction):
      def __init__(self, buttons, button_width=180, enabled=True):
        # Auto-sizes buttons based on text content
        # Renders multiple buttons horizontally
    

    Factory Function:

    category_buttons_item(
      title="Category Title",
      buttons=[("BUTTON1", callback1), ("BUTTON2", callback2)],
      description="Description text",
      icon="icon.png",
      starpilot_icon=True,
    )
    

    CATEGORIES Structure (in starpilot.py):

    CATEGORIES = [
      {"title": "Alerts and Sounds", "icon": "icon_sound.png", "desc": "...",
       "buttons": [("MANAGE", "SOUNDS", 0)]},
      {"title": "Driving Controls", "icon": "icon_steering.png", "desc": "...",
       "buttons": [("DRIVING MODEL", "DRIVING_MODEL", 0), ("GAS / BRAKE", "LONGITUDINAL", 0), ("STEERING", "LATERAL", 0)]},
      # ... etc
    ]
    

    IMPORTANT - Each Button Needs Unique Panel Type:

    • Qt uses stacked layouts where each category button switches to a different panel
    • Originally we made the mistake of mapping all 3 Driving buttons to the same "DRIVING" panel
    • Fix: Each button must map to its own unique StarPilotPanelType (DRIVING_MODEL, LONGITUDINAL, LATERAL)
    • The panel dictionary must have entries for each unique panel type

    Each button tuple is: (button_label, panel_key, min_level)

    • button_label: Text shown on button
    • panel_key: Which panel to open
    • min_level: Minimum tuning level required to see this button

    Tuning Level Filtering:

    • Level 0 (Minimal): Shows buttons with min_level 0
    • Level 1 (Standard): Shows buttons with min_level 0 or 1
    • Level 2 (Advanced): Shows buttons with min_level 0, 1, or 2
    • Level 3 (Developer): Shows all buttons

StarPilot Panel Categories (6 Main Categories)

Category Qt Panel Raylib File Sub-panels
Alerts and Sounds StarPilotSoundsPanel sounds.py 1
Driving Controls DrivingModel, Lateral, Longitudinal driving_model.py, lateral.py, longitudinal.py 5
Navigation Maps, Navigation maps.py, navigation.py 1
System Settings Data, Device, Utilities data.py, device.py, utilities.py 3
Theme and Appearance Visuals, Themes visuals.py, themes.py 2
Vehicle Settings Vehicles, Wheel vehicle.py, wheel.py 2

Total: 14 modules across 6 categories

Common Pitfalls and Solutions

Issue Cause Solution
Division by zero when loading texture Empty string passed to texture loader, or file doesn't exist Pass empty string to ListItem init, then call set_icon(path, starpilot=True) separately. Ensure StarPilot icons use starpilot_icon=True flag
Icons not loading Wrong asset path or missing starpilot flag Use full path (toggle_icons/icon.png) and starpilot_icon=True
UI doesn't match native panels Custom drawing instead of Scroller Always use Scroller + factory functions
Button text wrong Using ">" instead of "MANAGE" Use descriptive button text from Qt UI
Descriptions don't render Missing HTML tags Use <b>...</b> tags in descriptions
Sub-panel navigation Need to switch between sub-panels Use StarPilotPanelType enum + dictionary pattern like in starpilot.py
No way to go back Sub-panels have no back button Use hierarchical back button in settings sidebar - implements global back navigation (depth > 0 = go back, depth 0 = close settings)
Confusing "Developer" naming Two different features with same name Remember: "Developer" Tuning Level (level 3) controls StarPilot toggle visibility; "Developer" Panel (Settings sidebar) only appears at Tuning Level >= 3
Toggle visibility not filtering Sub-panels don't check tuning level Implement set_tuning_levels() and refresh_visibility() methods in each sub-panel layout
Category buttons not side-by-side Need horizontal button layout next to title Use category_buttons_item() factory function with CategoryButtonsAction
Each category button opens same panel Wrong - all 3 Driving buttons opened same panel Each button needs unique panel_key in CATEGORIES + unique StarPilotPanelType enum value + panel dict entry
Category buttons overlapping title CategoryButtonsAction positioning was wrong Buttons should start at rect.x (left edge of action rect), NOT rect.x + rect.width. The action rect is already positioned at the right side of the item by get_right_item_rect.
Category buttons text overflow Text like "DRIVING MODEL" (~220px) overflows fixed-width 150px buttons Solution: Per-button width based on text + padding (20px), scaled proportionally to fit available space. Algorithm: (1) For each button, calculate ideal_width = text_width + 20px, (2) Calculate total ideal width + spacing, (3) If total > available space, scale all widths proportionally, else use ideal widths. BUTTON_FONT_SIZE = 35 (matches Qt).
Category buttons overlapping/gaps Button positioning used wrong formula: i * (btn_w + spacing) instead of cumulative sum. This caused buttons to overlap or have inconsistent gaps. Fix: Use cumulative positioning. Initialize current_button_x = rect.x, then after drawing each button, advance: current_button_x += btn_w + spacing. This correctly positions each button after the previous one. Example with 3 buttons (widths: 146, 128, 92, spacing: 20): Button1@rect.x, Button2@rect.x+166, Button3@rect.x+314. Total width: 406px with proper 20px gaps.
Category buttons overflow (Vehicle Settings) width_hint in init used fixed 150px per button, but _render used dynamic text-based widths. Mismatch caused incorrect space allocation, leading to overflow/overlap. Fix: Calculate width_hint in init the same way as _render: iterate through buttons, measure text, add padding (20px), sum with spacing. Example for Vehicle Settings: "VEHICLE SETTINGS"(140px) + "WHEEL CONTROLS"(130px) + padding(40px) + spacing(20px) = ~330px. This ensures the action rect has exactly the space needed.
Navigation callbacks missing Refactor broke manual wiring of sub-panel back buttons Fix: Re-wire panel.set_back_callback(self._go_back) in parent layouts (sounds.py, lateral.py)
StarPilotPanel method loss Refactor moved methods to base but some subclasses didn't call them Fix: Ensure subclasses call self.set_tuning_levels() and self._update_state() in __init__ before return
Model lists empty DrivingModel missing _update_model_metadata() in __init__ Fix: Explicitly call metadata update and _update_state to populate lists before first render
Reboot confirmation pattern Many toggles (NNFF, ForceTorqueController, AlwaysOnLateral) require reboot when changed while driving. Pattern: Helper method _on_reboot_toggle(key, state) that: (1) saves param, (2) checks ui_state.started, (3) shows ConfirmDialog via gui_app.set_modal_overlay(), (4) calls HARDWARE.reboot() on confirm.
Lambda capture in loops Creating panel callbacks in a loop (for cat in CATEGORIES) where panel_type changes but all lambdas capture the same variable. Fix: Use default argument capture: lambda p=panel_type: self._set_current_panel(p). Without p=panel_type, all callbacks would reference the last value of panel_type.
Sound testing only first button works Spawning a new thread per Test button click. sounddevice doesn't handle concurrent sd.play() calls well - subsequent calls get queued/dropped, and multiple threads cause memory corruption (double free). Fix: Use persistent Python subprocess (matching Qt's initializeSoundPlayer()). Subprocess runs a while loop reading `path

8.2 Priority 2: Onroad Overlays

Feature Qt File Status
Blind spot visualization starpilot/ui/qt/onroad/starpilot_onroad.cc 🔴 Not Started
FPS counter overlay selfdrive/ui/onroad/hud_renderer.py 🟡 Partial
Steering torque selfdrive/ui/onroad/hud_renderer.py 🔴 Not Started
Turn signals selfdrive/ui/onroad/hud_renderer.py 🔴 Not Started

8.3 Priority 3: Additional Features

Feature Qt File Status
Drive stats widget starpilot/ui/qt/widgets/drive_stats.cc 🔴 Not Started
Drive summary widget starpilot/ui/qt/widgets/drive_summary.cc 🔴 Not Started
Developer sidebar starpilot/ui/qt/widgets/developer_sidebar.cc 🔴 Not Started
Theme system Multiple files 🟡 Partial

9. StarPilot Complete Implementation Roadmap

Note

: This roadmap was created through detailed analysis of ALL Qt source files in starpilot/ui/qt/offroad/. The Qt implementation has 200+ controls across multiple levels of sub-panels with complex conditional visibility rules.

9.1 Architecture Overview

The StarPilot UI has a three-level hierarchy:

Level 1: Main StarPilot Panel (CATEGORY buttons)
    │
    └── Level 2: Sub-Panel (MANAGE buttons → controls)
              │
              └── Level 3: Sub-Sub-Panel (additional controls)
                        │
                        └── Level 4: Sub-Sub-Sub-Panel (Weather → conditions)

Examples:

  • Driving Controls (category) → GAS/BRAKE (LONGITUDINAL panel) → MANAGE button → Conditional Experimental Mode sub-panel
  • Driving Controls → GAS/BRAKE → MANAGE button → Weather → Low Visibility/Rain/Rainstorm/Snow (4 conditions)

Total Controls: 200+ toggles, buttons, and value sliders across all panels

9.2 Current Status

Component Status Notes
Top-level navigation Done 6 categories with horizontal buttons
Panel Modularization Done split into 14 distinct files (March 2026)
StarPilotPanelType (14 types) Done Unique panel types for routing in panel.py
Sounds panel (toggles) Done Extracted to sounds.py
Value control factory functions Done 8 types: value_item, value_button_item, dual_value_item, button_toggle_item, buttons_item, selection_button_item, label_item, category_buttons_item
Three-level navigation Done Panel stack, navigation callbacks, Weather 4-condition sub-panels
StarPilotState singleton Done Real car param parsing, desktop fallback, 2s throttle
InputDialog widget Done Keyboard input, hint text, blinking cursor
SelectionDialog widget Done Radio-button selection, scrollable, green dot indicator
Params helpers Done get_int, get_float, put_int, put_float
ConfirmDialog rich text Done HtmlRenderer + Scroller, keyboard shortcuts
Lateral panel Done 5 sub-panels, ~21 real controls using starpilot_state
Longitudinal panel 🟡 Partial Weather sub-panels (4 conditions with real value_item controls) done, rest not started
Sounds panel (volume sliders) Done 7 volume controls + Test buttons + persistent subprocess for offroad sound testing
Driving Model panel 🔴 Not Started 9 toggles + 7 buttons + dialogs
Visual panel 🔴 Not Started 5 sub-panels, ~35 controls
Themes panel 🔴 Not Started Download/select for 7 theme types
Navigation panel 🔴 Not Started Mapbox keys, setup instructions
Data panel 🔴 Not Started Backups, storage, stats
Device panel 🔴 Not Started 2 sub-panels, ~14 controls
Vehicle panel 🔴 Not Started 5 sub-panels, ~20 controls
Wheel panel 🔴 Not Started 4 button controls
Utilities panel 🔴 Not Started 6+ buttons
Maps panel 🔴 Not Started Download, countries, states
Conditional visibility Done Sounds panel Custom Alerts has real conditional visibility rules (BSM, ShowSpeedLimits, SpeedLimitController). Lateral panel has car-state conditional visibility.
Metric unit conversion 🟡 Partial is_metric=True param on value_item exists but conversion logic needs wiring
Onroad overlays 🔴 Not Started 4 features
Developer sidebar 🔴 Not Started 1 feature

9.3 Required: Value Control Factory Functions

The Qt UI uses 6 different control types for value inputs. All need Raylib equivalents:

Control Types in Qt:

  1. StarPilotParamValueControl - Simple value slider

    • Horizontal slider with min/max/step
    • Value display with unit label
    • Optional custom labels (e.g., "Off", "Instant", "X seconds")
    • Optional metric unit conversion
  2. StarPilotParamValueButtonControl - Value slider + button

    • Same as above + "Reset" or "Test" button
    • Used for: SteerDelay, SteerFriction, volumes, ClusterOffset, PauseLateralSpeed, CESignalSpeed
    • Button can trigger: Reset to default, Test sound, etc.
  3. StarPilotDualParamValueControl - Two connected sliders

    • Two value controls displayed together with shared unit
    • Used for: CESpeed (Without Lead + With Lead)
    • Both sliders convert together on metric toggle
  4. StarPilotButtonToggleControl - Toggle + sub-toggles

    • Toggle switch with additional sub-option toggles
    • Used for: CECurves, CELead, MapGears, ToyotaDoors, SLCConfirmation, PedalsOnUI
    • Can be exclusive (selecting one disables others) or additive
  5. StarPilotButtonsControl - Multiple action buttons

    • Row of buttons (DELETE, DOWNLOAD, SELECT, etc.)
    • Used for: Theme downloads, backups, model management, Weather key
    • Button count varies: 2-4 buttons per control
    • Can show/hide individual buttons dynamically
  6. ButtonParamControl - Button selection (not value)

    • Opens dialog to select from options
    • Used for: AccelerationProfile, DecelerationProfile, CameraView, SLCFallback, SLCOverride
  7. LabelControl - Read-only display

    • Shows static text or dynamic values
    • Used for: Vehicle info, Stats, Calibration values, Download status
    • No user interaction
  8. StarPilotManageControl - Opens sub-panel

    • MANAGE/VIEW button that opens nested panel
    • Used for: All main panel categories

Required Factory Functions:

# 1. Simple value slider (StarPilotParamValueControl equivalent)
def value_item(
    title: str | Callable[[], str],
    value: float | Callable[[], float],
    min_val: float = 0,
    max_val: float = 100,
    step: float = 1,
    unit: str = "",
    description: str | Callable[[], str] | None = None,
    callback: Callable[[float], None] | None = None,
    icon: str = "",
    enabled: bool | Callable[[], bool] = True,
    is_metric: bool = False,  # Auto-convert units
    labels: dict[float, str] = None,  # Custom labels: {0: "Off", 1: "1 second", etc.}
    negative: bool = False,  # Allow negative values
) -> ListItem:
    """Slider for value adjustment with optional unit label"""

# 2. Value slider with button (StarPilotParamValueButtonControl equivalent)
def value_button_item(
    title: str | Callable[[], str],
    value: float | Callable[[], float],
    min_val: float = 0,
    max_val: float = 100,
    step: float = 1,
    unit: str = "",
    button_text: str = "Reset",  # "Reset" or "Test"
    button_callback: Callable | None = None,
    description: str | Callable[[], str] | None = None,
    callback: Callable[[float], None] | None = None,
    icon: str = "",
    enabled: bool | Callable[[], bool] = True,
    sub_toggles: list[str] = [],  # Optional sub-toggle param keys
    sub_toggle_names: list[str] = [],  # Display names
    has_sub_toggle: bool = False,  # Has sub-toggle alongside value
    labels: dict[float, str] = None,
    negative: bool = False,
) -> ListItem:
    """Slider with action button (Reset/Test) and optional sub-toggles"""

# 3. Dual value control (StarPilotDualParamValueControl equivalent)
def dual_value_item(
    title: str | Callable[[], str],
    value1: float | Callable[[], float],
    value2: float | Callable[[], float],
    min_val: float = 0,
    max_val: float = 100,
    step: float = 1,
    unit: str = "",
    label1: str = "",  # e.g., "Without Lead"
    label2: str = "",  # e.g., "With Lead"
    description: str | Callable[[], str] | None = None,
    callback1: Callable[[float], None] | None = None,
    callback2: Callable[[float], None] | None = None,
    icon: str = "",
    enabled: bool | Callable[[], bool] = True,
) -> ListItem:
    """Two connected value sliders with labels"""

# 4. Button toggle control (StarPilotButtonToggleControl equivalent)
def button_toggle_item(
    title: str | Callable[[], str],
    state: bool | Callable[[], bool],
    sub_toggles: list[str] = [],  # List of sub-toggle param keys
    sub_toggle_names: list[str] = [],  # Display names
    description: str | Callable[[], str] | None = None,
    callback: Callable[[bool], None] | None = None,
    sub_callbacks: list[Callable] = [],
    icon: str = "",
    enabled: bool | Callable[[], bool] = True,
    exclusive: bool = False,  # If true, selecting one disables others
) -> ListItem:
    """Toggle with optional sub-toggles (e.g., "With Lead", "Lower Limits")"""

# 5. Multi-button control (StarPilotButtonsControl equivalent)
def buttons_item(
    title: str | Callable[[], str],
    buttons: list[str],  # e.g., ["DELETE", "DOWNLOAD", "SELECT"]
    button_callbacks: list[Callable] = [],
    description: str | Callable[[], str] | None = None,
    icon: str = "",
    enabled: bool | Callable[[], bool] = True,
    initial_value: str = "",  # Display current selection
) -> ListItem:
    """Multiple action buttons in a row"""

# 6. Button selection control (ButtonParamControl equivalent)
def selection_button_item(
    title: str | Callable[[], str],
    options: list[str],  # e.g., ["Standard", "Eco", "Sport", "Sport+"]
    selected_index: int = 0,
    description: str | Callable[[], str] | None = None,
    callback: Callable[[int, str], None] = None,  # index, option_text
    icon: str = "",
    enabled: bool | Callable[[], bool] = True,
) -> ListItem:
    """Button that opens selection dialog"""

# 7. Label control (LabelControl equivalent) - read-only display
def label_item(
    title: str | Callable[[], str],
    value: str | Callable[[], str],
    description: str | Callable[[], str] | None = None,
    icon: str = "",
    enabled: bool | Callable[[], bool] = True,
) -> ListItem:
    """Read-only label display"""

Features needed across all value controls:

  • Horizontal slider with min/max/step
  • Current value display with unit
  • Optional "Reset" or "Test" button
  • Metric unit auto-conversion (imperial/metric toggle)
  • Labels update based on metric setting
  • Default value display in title (e.g., "Actuator Delay (Default: 0.5)")
  • Custom labels for special values (e.g., "Off", "Muted", "Auto")
  • Warning labels for certain controls

9.4 Required: Three-Level Navigation System

Each MANAGE button in Qt leads to a sub-panel with more controls. The Longitudinal panel has THREE levels (Weather):

9.4.1 Raylib Implementation Pattern

The Raylib implementation uses a panel stack + callback pattern to achieve hierarchical navigation:

1. StarPilotLayout (Main Container):

class StarPilotLayout(Widget):
  def __init__(self):
    # Panel stack tracks navigation history
    self._panel_stack: list[tuple[StarPilotPanelType, str]] = []
    self._sub_panel_callbacks: dict[str, Callable] = {}
    
    # Set up sub-panel navigation
    self._setup_longitudinal_sub_panels()
    
  def navigate_back(self):
    # Pop from stack, update sub-panel visibility
    if self._panel_stack:
      self._panel_stack.pop()
      self._update_sub_panel_visibility()
    else:
      self._set_current_panel(StarPilotPanelType.MAIN)
      
  def _push_sub_panel(self, sub_panel_name: str):
    self._panel_stack.append((self._current_panel, sub_panel_name))
    self._update_sub_panel_visibility()

2. StarPilotLongitudinalLayout (Sub-Panel Container):

class StarPilotLongitudinalLayout(StarPilotPanel):
  def __init__(self):
    super().__init__()
    # Navigation callbacks and routing are handled by StarPilotPanel.__init__()
    
    self._sub_panels: dict[str, Widget] = {
      "weather": StarPilotWeatherLayout(),
      "low_visibility": StarPilotLowVisibilityLayout(),
    }
    
    for name, panel in self._sub_panels.items():
      if hasattr(panel, 'set_navigate_callback'):
        panel.set_navigate_callback(self._navigate_to)

3. Weather Condition Layout (Leaf Panel):

class StarPilotWeatherLayout(StarPilotPanel):
  def __init__(self):
    super().__init__()
    # Callbacks and state routing are inherited out of the box
    
    items = [
      button_item(
        tr_noop("Low Visibility"),
        lambda: tr("MANAGE"),
        description,
        callback=lambda: self._navigate_to("low_visibility"),
      ),
      # ... more conditions
    ]
    self._scroller = Scroller(items, line_separator=True, spacing=0)

Key Patterns:

  1. Navigation callback chain: Parent → Child → Grandchild flows through callbacks
  2. State-based rendering: Parent panel decides which sub-panel to render based on _current_sub_panel
  3. Back navigation: Settings sidebar's back button pops the panel stack
  4. Separate layout classes: Each sub-panel is its own Widget class with its own Scroller

Navigation Flow:

Driving Controls → GAS/BRAKE (Longitudinal)
  → Weather button calls _navigate_to("weather")
  → _navigate_callback pushes to panel stack
  → Longitudinal renders StarPilotWeatherLayout
  → Low Visibility button calls _navigate("low_visibility")
  → _navigate_callback pushes to panel stack  
  → Longitudinal renders StarPilotLowVisibilityLayout
  → Back button pops stack → returns to Weather
  1. Sub-sub-panel enum: Add StarPilotSubPanelType for controls within sub-panels
  2. Back button: Each sub-panel needs back navigation to parent
  3. Navigation stack: Track depth for proper back navigation
  4. Three-level handling: Weather → 4 weather conditions → offset controls

Navigation Signals in Qt:

// Signals for multi-level navigation
emit openSubPanel();       // Level 1 → Level 2
emit openSubSubPanel();    // Level 2 → Level 3 (personalities, SLC QOL/Offsets/Visuals)
emit openSubSubSubPanel(); // Level 3 → Level 4 (Weather conditions)

// Close signals (from parent window)
emit closeSubPanel();      // Back from Level 2 → Level 1
emit closeSubSubPanel();   // Back from Level 3 → Level 2
emit closeSubSubSubPanel(); // Back from Level 4 → Level 3

Longitudinal Panel Structure (17 sub-panels, ~80 controls):

FROGPILOT LONGITUDINAL LAYOUT (top level - MANAGE buttons)
├── Advanced Longitudinal Tuning → StarPilotAdvancedLongLayout
│   └── 8 value controls
├── Conditional Experimental Mode → StarPilotConditionalExpLayout
│   └── 7 controls + dual slider CESpeed
├── Curve Speed Controller → StarPilotCurveSpeedLayout
│   └── 4 controls
├── Driving Personalities → StarPilotPersonalityLayout (sub-sub)
│   ├── Traffic Personality → StarPilotTrafficLayout (sub-sub-sub)
│   │   └── 7 value controls + reset
│   ├── Aggressive Personality → StarPilotAggressiveLayout
│   │   └── 7 value controls + reset
│   ├── Standard Personality → StarPilotStandardLayout
│   │   └── 7 value controls + reset
│   └── Relaxed Personality → StarPilotRelaxedLayout
│       └── 7 value controls + reset
├── Longitudinal Tuning → StarPilotLongTuneLayout
│   └── 8 controls
├── Quality of Life → StarPilotLongQOLLayout
│   └── 7 controls + Weather Presets → StarPilotWeatherLayout (LEVEL 3)
│       ├── Low Visibility → StarPilotLowVisibilityLayout (LEVEL 4)
│       │   └── 4 offset controls
│       ├── Rain → StarPilotRainLayout
│       │   └── 4 offset controls
│       ├── Rainstorm → StarPilotRainStormLayout
│       │   └── 4 offset controls
│       └── Snow → StarPilotSnowLayout
│           └── 4 offset controls
│       └── Set Weather Key (ADD/TEST buttons)
└── Speed Limit Controller → StarPilotSLCLayout
    ├── Main SLC Controls (~10 toggles/controls)
    ├── SLC Quality of Life → StarPilotSLCQOLLayout (sub-sub)
    │   └── SLCConfirmation (TOGGLE+TOGGLE): Lower Limits + Higher Limits
    ├── SLC Offsets → StarPilotSLCOffsetsLayout (sub-sub)
    │   └── 7 speed range offsets
    └── SLC Visuals → StarPilotSLCVisualsLayout (sub-sub)
        └── 2 toggles

9.5 Complete Panel Implementation Details

9.5.1 Sounds Panel

Qt File: starpilot/ui/qt/offroad/sounds_settings.cc (222 lines)

Raylib Implementation: selfdrive/ui/layouts/settings/starpilot/sounds.py

  • StarPilotSoundsLayout: Main panel with 2 MANAGE buttons
  • StarPilotVolumeControlLayout: 7 volume sliders with Test buttons
  • StarPilotCustomAlertsLayout: 5 toggles with conditional visibility

Main Panel (2 items):

Control Type Description
Alert Volume Controller MANAGE Opens sub-panel with 7 volume sliders
StarPilot Alerts MANAGE Opens sub-panel with 5 toggles

Alert Volume Control Sub-Panel (7 value controls):

Control Range Default Special
Disengage Volume 0-101 100 "Test" button; 0=Muted, 101=Auto
Engage Volume 0-101 100 "Test" button
Prompt Volume 0-101 100 "Test" button
Prompt Distracted Volume 0-101 100 "Test" button
Refuse Volume 0-101 100 "Test" button
Warning Soft Volume 25-101 100 "Test" button; min 25
Warning Immediate Volume 25-101 100 "Test" button; min 25

Values: 0=Muted, 101=Auto, 1-100=percentage

Sound Test Implementation (Raylib):

  • Offroad: Persistent Python subprocess reads path|volume\n from stdin, plays WAV via sounddevice
  • Onroad: Sets TestAlert param in params_memory (handled by soundd.py)
  • Theme sounds checked first (ACTIVE_THEME_PATH / "sounds"), fallback to stock sounds

Custom Alerts Sub-Panel (5 toggles):

Control Param Key Visibility Condition
Goat Scream GoatScream Always visible
Green Light Alert GreenLightAlert Always visible
Lead Departing Alert LeadDepartingAlert Always visible
Loud Blindspot Alert LoudBlindspotAlert Requires hasBSM
Speed Limit Changed Alert SpeedLimitChangedAlert Requires ShowSpeedLimits OR (hasOpenpilotLongitudinal AND SpeedLimitController)

9.5.2 Driving Model Panel

Qt File: starpilot/ui/qt/offroad/model_settings.cc (814 lines)

Main Panel (9 controls):

Control Type Param Key Special
AutomaticallyDownloadModels Toggle AutomaticallyDownloadModels Auto-download new models
Download Driving Models BUTTON DownloadModel DOWNLOAD / DOWNLOAD ALL / CANCEL
Delete Driving Models BUTTON DeleteModel DELETE / DELETE ALL
Model Randomizer Toggle ModelRandomizer Random model each drive
Recovery Power VALUE+RESET RecoveryPower Range 0.5-2.0, step 0.1, Level 3 only
Stop Distance VALUE+RESET StopDistance Range 4-10m, step 0.1, Level 3 only
Manage Blacklisted Models BUTTON ManageBlacklistedModels ADD / REMOVE / REMOVE ALL
Manage Model Ratings BUTTON ManageScores RESET / VIEW
Select Driving Model BUTTON SelectModel Opens model selection dialog

Model Selection Dialog Features:

  • Groups models by series (Custom Series, Driving Policy, etc.)
  • Shows icons: 🗺️ (Navigation), 📡 (Radar), 👀 (VOACC)
  • Supports user favorites and community favorites
  • Sort modes: alphabetical, release date, rating
  • Shows release dates when available
  • Requires reboot after selection if started

Model Delete Dialog:

  • Groups deletable models by series
  • Confirmation dialog before delete

Blacklist Management:

  • ADD: Select from available models to blacklist
  • REMOVE: Select from blacklisted models to remove
  • REMOVE ALL: Clear entire blacklist (with confirmation)

Model Ratings Panel (Sub-panel):

  • Shows each model's: Drives count, Score percentage
  • Read-only LabelControls

Conditional Visibility:

  • ManageBlacklistedModels, ManageScores: Only if ModelRandomizer is ON
  • SelectModel: Only if ModelRandomizer is OFF
  • RecoveryPower, StopDistance: Only if tuningLevel == 3 (Developer)

Button States:

  • Download/Delete buttons: Disabled when no models available or downloading
  • Online/parked requirement for downloads

9.5.3 Lateral Panel - 5 Sub-Panels

Qt File: starpilot/ui/qt/offroad/lateral_settings.cc (428 lines)

Advanced Lateral Tuning (8 controls):

Control Type Range Step Default Special
SteerDelay VALUE+RESET 0.01-1.0 0.01 car-specific seconds, show default in title
SteerFriction VALUE+RESET 0-1 0.01 car-specific
SteerKP VALUE+RESET car×0.5 to car×1.5 0.01 car-specific Auto-scales to car
SteerLatAccel VALUE+RESET car×0.5 to car×1.5 0.01 car-specific Auto-scales to car
SteerRatio VALUE+RESET car×0.5 to car×1.5 0.01 car-specific Auto-scales to car
ForceAutoTune Toggle - - -
ForceAutoTuneOff Toggle - - -
ForceTorqueController Toggle - - -

Car-Specific Ranges (in showEvent):

  • SteerKP: parent->steerKp * 0.5 to parent->steerKp * 1.5
  • SteerLatAccel: parent->latAccelFactor * 0.5 to parent->latAccelFactor * 1.5
  • SteerRatio: parent->steerRatio * 0.5 to parent->steerRatio * 1.5

Always On Lateral (2 controls):

Control Type Range Step Special
AlwaysOnLateralLKAS Toggle - - Enable with LKAS
PauseAOLOnBrake VALUE 0-99 1 mph/kmh auto-convert

Lane Changes (5 controls):

Control Type Range Step Special
LaneChanges MANAGE - - Parent toggle
NudgelessLaneChange Toggle - -
LaneChangeTime VALUE 0-5 0.1 seconds (labels: "Instant", "0.5 seconds", etc.)
MinimumLaneChangeSpeed VALUE 0-99 1 mph/kmh
LaneDetectionWidth VALUE 0-15 0.1 feet/meters (auto-convert)
OneLaneChange Toggle - -

Lateral Tuning (3 controls):

Control Type Special
TurnDesires Toggle
NNFF Toggle Requires NNFF log, not angle car
NNFFLite Toggle

Quality of Life (1 control):

Control Type Range Step Special
PauseLateralSpeed VALUE+TOGGLE 0-99 1 Has "Turn Signal Only" sub-toggle

Reboot Requirements (toggle changed while started):

  • AlwaysOnLateral, ForceTorqueController, NNFF, NNFFLite: All require reboot confirmation

Metric Conversion (updateMetric):

  • LaneDetectionWidth: FOOT_TO_METER (imperial: 0-15 feet, metric: 0-5 meters)
  • MinimumLaneChangeSpeed, PauseAOLOnBrake, PauseLateralSpeed: MILE_TO_KM (imperial: 0-99 mph, metric: 0-150 km/h)

Conditional Visibility (Advanced Lateral):

Control Visibility Condition
SteerDelay Only if != 0
SteerFriction Only if !=0 AND (auto-tune off OR not forcing) AND (torque car OR forcing torque OR using NNFF) AND NOT using NNFF
SteerKP Only if !=0 AND (torque car OR forcing) AND NOT angle car
SteerLatAccel Only if !=0 AND (auto-tune off OR not forcing) AND (torque car OR forcing) AND NOT using NNFF
SteerRatio Only if !=0 AND (auto-tune off OR not forcing)
ForceAutoTune Only if NOT hasAutoTune AND NOT angle car AND (torque car OR forcingTorque OR usingNNFF)
ForceAutoTuneOff Only if hasAutoTune
ForceTorqueController Only if NOT angle car AND NOT torque car

Conditional Visibility (Lane Changes):

  • LaneChangeTime, LaneDetectionWidth: Only if LaneChanges AND NudgelessLaneChange both ON

Conditional Visibility (Lateral Tuning):

  • NNFF: Only if hasNNFFLog AND NOT angle car
  • NNFFLite: Only if NOT usingNNFF AND NOT angle car

9.5.4 Longitudinal Panel - 17 Sub-Panels (THREE LEVELS!)

Qt File: starpilot/ui/qt/offroad/longitudinal_settings.cc (1085 lines)

Structure:

LONGITUDINAL PANEL (Main)
├── Advanced Longitudinal Tuning (8 controls)
├── Conditional Experimental Mode (7 controls + dual slider)
├── Curve Speed Controller (4 controls)
├── Driving Personalities (MANAGE) → Sub-Sub-Panel
│   ├── Traffic Personality (MANAGE) → Sub-Sub-Sub-Panel (7 controls + reset)
│   ├── Aggressive Personality (MANAGE) → Sub-Sub-Sub-Panel (7 controls + reset)
│   ├── Standard Personality (MANAGE) → Sub-Sub-Sub-Panel (7 controls + reset)
│   └── Relaxed Personality (MANAGE) → Sub-Sub-Sub-Panel (7 controls + reset)
├── Longitudinal Tuning (8 controls)
├── Quality of Life (8 controls)
│   └── Weather Presets (MANAGE) → LEVEL 3
│       ├── Low Visibility (MANAGE) → LEVEL 4 (4 offsets)
│       │   └── 4 offset controls
│       ├── Rain (MANAGE) → LEVEL 4 (4 offsets)
│       │   └── 4 offset controls
│       ├── Rainstorm (MANAGE) → LEVEL 4 (4 offsets)
│       │   └── 4 offset controls
│       └── Snow (MANAGE) → LEVEL 4 (4 offsets)
│           └── 4 offset controls
│       └── Set Weather Key (ADD/TEST buttons)
└── Speed Limit Controller (MANAGE) → Sub-Sub-Panel
    ├── Main SLC Controls (~10 toggles/controls)
    ├── SLC Quality of Life (MANAGE) → Sub-Sub-Sub-Panel
    │   └── SLCConfirmation (TOGGLE+TOGGLE): Lower Limits + Higher Limits
    ├── SLC Offsets (MANAGE) → Sub-Sub-Sub-Panel (7 speed range offsets)
    └── SLC Visuals (MANAGE) → Sub-Sub-Sub-Panel (2 toggles)

Advanced Longitudinal Tuning (8 controls):

Control Type Range Step Unit Special
LongitudinalActuatorDelay VALUE 0-1 0.01 seconds show default in title
MaxDesiredAcceleration VALUE 0.1-4.0 0.1 m/s²
StartAccel VALUE 0-4 0.01 m/s² show default in title
VEgoStarting VALUE 0.01-1 0.01 m/s² show default in title
StopAccel VALUE -4-0 0.01 m/s² negative, show default
StoppingDecelRate VALUE 0.001-1 0.001 m/s² show default
VEgoStopping VALUE 0.01-1 0.01 m/s² show default

Conditional Experimental Mode (8 controls - includes dual slider):

Control Type Range Step Special
CESpeed DUAL VALUE 0-99 (0-150 metric) 1 TWO sliders: Without Lead + With Lead
CECurves TOGGLE+TOGGLE - - Main + "With Lead" sub-toggle
CEStopLights Toggle - - "Detected" stop lights/signs
CELead TOGGLE+TOGGLE - - Main + "Slower Lead" + "Stopped Lead"
CEModelStopTime VALUE 0-9 1 seconds (labels: "Off", "1 second", etc.)
CESignalSpeed VALUE+TOGGLE 0-99 (0-150 metric) 1 mph/kmh + "Not For Detected Lanes"
ShowCEMStatus Toggle - - Status widget

Dual Slider Implementation:

  • Uses StarPilotDualParamValueControl wrapping two StarPilotParamValueControl
  • CESpeed: First slider "Below" (no lead), Second slider "With Lead"
  • Both sliders share same unit and get converted together

Curve Speed Controller (4 controls):

Control Type Special
CalibratedLateralAcceleration LABEL Read-only, shows "X.XX m/s²"
CalibrationProgress LABEL Read-only, shows "XX.XX%"
ResetCurveData BUTTON RESET
ShowCSCStatus Toggle Status widget

Driving Personalities (4 sub-panels, each 7 controls + reset):

Each personality has:

  • Follow: 0.5-3s (Traffic) or 1-3s (others), step 0.01
  • JerkAcceleration: 25-200%, step 1
  • JerkDeceleration: 25-200%, step 1
  • JerkDanger: 25-200%, step 1
  • JerkSpeedDecrease: 25-200%, step 1
  • JerkSpeed: 25-200%, step 1
  • Reset button
Personality Follow Range Default Follow Icon
Traffic 0.5-3.0 N/A traffic.png
Aggressive 1-3 1.25s aggressive.png
Standard 1-3 1.45s standard.png
Relaxed 1-3 1.75s relaxed.png

Longitudinal Tuning (8 controls):

Control Type Options Special
AccelerationProfile BUTTON SELECT Standard, Eco, Sport, Sport+
DecelerationProfile BUTTON SELECT Standard, Eco, Sport
HumanAcceleration Toggle
HumanFollowing Toggle
HumanLaneChanges Toggle Requires radar
LeadDetectionThreshold VALUE 25-50%
TacoTune Toggle "Taco Bell Run" turn speed
MapGears TOGGLE+TOGGLE Acceleration + Deceleration mapped to gears

Quality of Life (8 controls):

Control Type Range Step Unit Special
CustomCruise VALUE 1-99 1 mph/kmh Cruise interval
CustomCruiseLong VALUE 1-99 1 mph/kmh Cruise interval (hold)
ForceStops Toggle - - Force stop at detected lights
IncreasedStoppedDistance VALUE 0-10 1 feet/meters
MapGears TOGGLE+TOGGLE - - See LongitudinalTune
SetSpeedOffset VALUE 0-99 1 mph/kmh
ReverseCruise Toggle - - Reverse +/- behavior
WeatherPresets MANAGE - - Opens Weather panel (LEVEL 3)

Weather (4 conditions × 4 controls = 16 controls) - LEVEL 3:

Condition Controls (each) Range
Low Visibility IncreaseFollowing, IncreasedStoppedDistance, ReduceAcceleration, ReduceLateralAccel Following: 0-3s, Stopped: 0-10ft, Accel: 0-99%, Lateral: 0-99%
Rain Same 4 offsets Same
Rainstorm Same 4 offsets Same
Snow Same 4 offsets Same

Weather Key Management:

  • ADD button: Opens InputDialog for 32-character key
  • TEST button: Makes network request to OpenWeatherMap API
  • Shows "Testing..." while request in progress
  • Validates both v2.5 and v3.0 API

Speed Limit Controller (Main + 3 sub-panels):

Main SLC (10+ controls):

Control Type Options Special
SLCFallback BUTTON SELECT Set Speed, Experimental Mode, Previous Limit
SLCOverride BUTTON SELECT None, Set With Gas Pedal, Max Set Speed
SLCQOL MANAGE Opens QOL sub-panel
SLCConfirmation TOGGLE+TOGGLE Lower Limits + Higher Limits
SLCLookaheadHigher VALUE 0-30 seconds
SLCLookaheadLower VALUE 0-30 seconds
SetSpeedLimit Toggle Match speed limit on engage
SLCMapboxFiller Toggle Use Mapbox as fallback
SLCPriority BUTTON SELECT Opens priority dialog
SLCOffsets MANAGE Opens Offsets sub-panel

SLC Priority Dialog:

  • Two-step selection: Primary then Secondary
  • Options: Dashboard (if hasDashSpeedLimits), Map Data, Highest, Lowest, None
  • Stores as SLCPriority1, SLCPriority2

SLC Offsets Sub-Panel (7 controls):

Control Imperial Range Metric Range Special
Offset1 -99-99 mph (0-24 mph range) -150-150 km/h (0-29 km/h range)
Offset2 -99-99 mph (25-34 mph range) -150-150 km/h (30-49 km/h range)
Offset3 -99-99 mph (35-44 mph range) -150-150 km/h (50-59 km/h range)
Offset4 -99-99 mph (45-54 mph range) -150-150 km/h (60-79 km/h range)
Offset5 -99-99 mph (55-64 mph range) -150-150 km/h (80-99 km/h range)
Offset6 -99-99 mph (65-74 mph range) -150-150 km/h (100-119 km/h range)
Offset7 -99-99 mph (75-99 mph range) -150-150 km/h (120-140 km/h range)

SLC Visuals Sub-Panel (2 controls):

Control Type
ShowSLCOffset Toggle
SpeedLimitSources Toggle

Metric Conversion Constants:

FOOT_TO_METER = 0.3048
METER_TO_FOOT = 3.28084
MILE_TO_KM = 1.60934
KM_TO_MILE = 0.621371

Conditional Visibility:

  • CEStopLights: Only if tuningLevel < CEModelStopTime level
  • CustomCruise, CustomCruiseLong, SetSpeedLimit, SetSpeedOffset: Only if NOT hasPCMCruise
  • HumanLaneChanges: Only if hasRadar
  • MapGears: Only if isToyota AND NOT isTSK
  • ReverseCruise: Only if isToyota
  • SLCMapboxFiller: Only if MapboxSecretKey is set
  • StartAccel: Only if NOT (LongitudinalTune AND HumanAcceleration)
  • StoppingDecelRate, VEgoStarting, VEgoStopping: Only if NOT (isGM AND ExperimentalGMTune) AND NOT (isToyota AND FrogsGoMoosTweak)

9.5.5 Visual Panel - 5 Sub-Panels

Qt File: starpilot/ui/qt/offroad/visual_settings.cc (328 lines)

Advanced Custom UI (6 toggles):

Control Param Key
HideSpeed HideSpeed
HideLeadMarker HideLeadMarker
HideMaxSpeed HideMaxSpeed
HideAlerts HideAlerts
HideSpeedLimit HideSpeedLimit
WheelSpeed WheelSpeed

Driving Screen Widgets (7 controls):

Control Type Special
AccelerationPath Toggle Requires longitudinal
AdjacentPath Toggle
BlindSpotPath Toggle Requires BSM
Compass Toggle
OnroadDistanceButton Toggle Requires longitudinal
PedalsOnUI TOGGLE+TOGGLE Dynamic + Static sub-toggles (exclusive)
RotatingWheel Toggle

PedalsOnUI Sub-Toggles:

  • Dynamic: When selected, disables Static
  • Static: When selected, disables Dynamic
  • Uses special exclusive toggle behavior

Model UI (5 value controls):

Control Range Step Unit Metric Range Special
DynamicPathWidth 0-100 1 % same
LaneLinesWidth 0-24 1 inches 0-60 cm Auto-convert to cm
PathEdgeWidth 0-100 1 % same
PathWidth 0-10 0.1 feet 0-3 m Auto-convert to meters
RoadEdgesWidth 0-24 1 inches 0-60 cm Auto-convert to cm

PathEdgeWidth Labels: "Off", "1%" through "100%"

Navigation UI (4 toggles):

Control Param Key
RoadNameUI RoadNameUI
ShowSpeedLimits ShowSpeedLimits
SLCMapboxFiller SLCMapboxFiller
UseVienna UseVienna

Quality of Life (4 controls):

Control Type Options Special
CameraView BUTTON SELECT Auto, Driver, Standard, Wide
DriverCamera Toggle Show in reverse
StoppedTimer Toggle

Metric Conversion:

  • LaneLinesWidth, RoadEdgesWidth: INCH_TO_CM / CM_TO_INCH (imperial: 0-24 inches, metric: 0-60 cm)
  • PathWidth: FOOT_TO_METER (imperial: 0-10 feet, metric: 0-3 meters)

Conditional Visibility:

  • AccelerationPath, OnroadDistanceButton, PedalsOnUI, HideLeadMarker: Only if hasOpenpilotLongitudinal
  • BlindSpotPath: Only if hasBSM
  • HideSpeedLimit: Only if hasOpenpilotLongitudinal AND SpeedLimitController
  • ShowSpeedLimits: Only if NOT SpeedLimitController OR NOT hasOpenpilotLongitudinal
  • SLCMapboxFiller: Only if ShowSpeedLimits AND (NOT SpeedLimitController OR NOT hasOpenpilotLongitudinal) AND MapboxSecretKey set
  • UseVienna: Only if ShowSpeedLimits OR SpeedLimitController

9.5.6 Themes Panel

Qt File: starpilot/ui/qt/offroad/theme_settings.cc (935 lines)

Main Panel:

Control Type Buttons Special
Custom Themes MANAGE - Opens 7 theme types
Holiday Themes TOGGLE -
Rainbow Path Toggle
Random Events Toggle
Random Themes TOGGLE+TOGGLE - Includes "Include Holiday Themes"
Startup Alert BUTTON STOCK, FROGPILOT, CUSTOM, CLEAR
Download Status LABEL - Shows download progress

Custom Themes Sub-Panel (7 theme types, each with DELETE/DOWNLOAD/SELECT):

Theme Type Param Key Buttons Directory
Boot Logo BootLogo DELETE, DOWNLOAD, SELECT boot_logos/
Color Scheme ColorScheme DELETE, DOWNLOAD, SELECT colors/
Distance Icon DistanceIconPack DELETE, DOWNLOAD, SELECT distance_icons/
Icon Pack IconPack DELETE, DOWNLOAD, SELECT (root level)
Signal Animation SignalAnimation DELETE, DOWNLOAD, SELECT signal_animations/
Sound Pack SoundPack DELETE, DOWNLOAD, SELECT sounds/
Wheel Icon WheelIcon DELETE, DOWNLOAD, SELECT steering_wheels/

Theme Button Behaviors:

  • DELETE: Opens selection dialog, then confirmation dialog
  • DOWNLOAD: Opens selection dialog, starts download, shows progress
  • SELECT: Opens selection dialog (includes "Stock" + holidays for Color/Distance)

Theme Download States:

  • Shows "Downloading..." during download
  • Buttons disabled while downloading
  • CANCEL available during download

Holiday Themes (13 holidays):

  • New Year's, Valentine's Day, St. Patrick's Day, World Frog Day, April Fools, Easter, May the Fourth, Cinco de Mayo, Stitch Day, Fourth of July, Halloween, Thanksgiving, Christmas

Random Themes Behavior:

  • Pick random theme between each drive
  • Optionally include holiday themes

Theme Name Parsing:

  • Format: "name~creator" (stored lowercase)
  • User-created themes: "name-user_created"
  • Display with 🌟 for user-created, "- by: creator" for community

9.5.7 Vehicle Panel - 5 Sub-Panels

Qt File: starpilot/ui/qt/offroad/vehicle_settings.cc (467 lines)

Main Panel (3 controls + 5 sub-panel buttons):

Control Type Special
Car Make BUTTON SELECT 28 car makes
Car Model BUTTON SELECT Dynamic based on make
ForceFingerprint Toggle Disable auto fingerprint
DisableOpenpilotLongitudinal Toggle Use stock ACC, requires reboot

Car Makes (28 total): Acura, Audi, Buick, Cadillac, Chevrolet, Chrysler, CUPRA, Dodge, Ford, Genesis, GMC, Holden, Honda, Hyundai, Jeep, Kia, Lexus, Lincoln, MAN, Mazda, Nissan, Peugeot, Ram, Rivian, SEAT, Škoda, Subaru, Tesla, Toyota, Volkswagen

Car Model Selection:

  • Reads from opendbc/car/{folder}/values.py
  • Groups by platform (e.g., "Toyota", "Genesis", etc.)

Sub-Panel Buttons (MANAGE/VIEW):

  • GM Settings, HKG Settings, Subaru Settings, Toyota Settings, Vehicle Info

GM Sub-Panel (4 controls):

Control Type Special
GMPedalLongitudinal Toggle Requires pedal
RemoteStartBootsComma Toggle Flash panda firmware
RemapCancelToDistance Toggle Requires BOLT + pedal
VoltSNG Toggle

Remote Start Flow:

  • Toggle triggers confirmation
  • Shows prompt about Panda firmware update
  • Runs flash in background thread
  • Reboots after flash complete

HKG Sub-Panel (1 control):

Control Type
TacoTuneHacks Toggle

Subaru Sub-Panel (1 control):

Control Type
SubaruSNG Toggle

Toyota Sub-Panel (5 controls):

Control Type Range Step Special
ToyotaDoors TOGGLE+TOGGLE - - Lock + Unlock
ClusterOffset VALUE+RESET 1.000-1.050 0.001 x multiplier
FrogsGoMoosTweak Toggle
LockDoorsTimer VALUE 0-300 5 seconds, WARNING label
SNGHack Toggle

ClusterOffset:

  • Shows "x1.000" to "x1.050"
  • Reset to default 1.000
  • Displayed as multiplication factor

LockDoorsTimer Labels:

  • 0 = "Never"
  • 1-300 = "X seconds"

Warning Label:

  • LockDoorsTimer: "Warning: openpilot can't detect if keys are still inside the car..."

Vehicle Info Sub-Panel (7 read-only labels):

Control Value
Hardware Detected "None" or "comma Pedal, SDSU, ZSS"
Blind Spot Support "Yes" / "No"
Pedal Support "Yes" / "No"
openpilot Longitudinal "Yes" / "No"
Radar Support "Yes" / "No"
SDSU Support "Yes" / "No"
Stop-and-Go Support "Yes" / "No"

Reboot Requirements:

  • TacoTuneHacks: Requires reboot
  • RemapCancelToDistance: Requires reboot
  • DisableOpenpilotLongitudinal: Requires reboot if disabling while started

Conditional Visibility:

  • GMPedalLongitudinal: Only if hasPedal OR (PC and canUsePedal)
  • RemapCancelToDistance: Only if isBolt AND (hasPedal OR (PC and canUsePedal))
  • SubaruSNG: Only if hasSNG
  • TacoTuneHacks: Only if isHKGCanFd
  • VoltSNG: Only if isVolt AND NOT hasSNG
  • SNGHack: Only if NOT hasSNG

9.5.8 Wheel Panel

Qt File: starpilot/ui/qt/offroad/wheel_settings.cc (84 lines)

4 Controls (each opens selection dialog):

Control Param Key Options
Distance Button DistanceButtonControl No Action, Change Personality, Force Coast, Pause Accel/Brake, Toggle Experimental, Toggle Traffic Mode
Distance Button (Long Press) LongDistanceButtonControl Same + Pause Steering
Distance Button (Very Long Press) VeryLongDistanceButtonControl Same
LKAS Button LKASButtonControl No Action, Pause Steering (unless Subaru or LKAS allowed for AOL)

Conditional Visibility:

  • LKASButtonControl: NOT Subaru AND NOT (LKAS allowed for AOL AND AlwaysOnLateral AND AlwaysOnLateralLKAS)

9.5.9 Navigation Panel

Qt File: starpilot/ui/qt/offroad/navigation_settings.cc (330 lines)

Main Panel:

Control Type Special
Manage Your Settings At LABEL Shows "IP:8082" or "Offline..."
Public Mapbox Key BUTTON ADD/TEST
Secret Mapbox Key BUTTON ADD/TEST
Mapbox Setup Instructions VIEW Opens instructions sub-panel
Speed Limit Filler BUTTON TOGGLE CANCEL / Manually Update Speed Limits

Key Management:

  • ADD: Opens InputDialog for key entry
  • TEST: Validates key works
  • Keys stored in params (Public/Secret Mapbox)

Instructions Sub-Panel:

  • Displays setup images based on current step
  • Step-based instruction flow

9.5.10 Data Panel

Qt File: starpilot/ui/qt/offroad/data_settings.cc (872 lines)

Main Panel:

Control Type Special
Delete Driving Data BUTTON DELETE
Delete Error Logs BUTTON DELETE
Screen Recordings BUTTONS DELETE / DELETE ALL / RENAME
StarPilot Backups BUTTONS BACKUP / DELETE / DELETE ALL / RESTORE
Toggle Backups BUTTONS BACKUP / DELETE / DELETE ALL / RESTORE
StarPilot Stats BUTTONS RESET / VIEW

Stats Sub-Panel (20+ read-only labels):

  • Drives, Distance, Time, Events, Time percentages, etc.

9.5.11 Device Panel - 2 Sub-Panels

Qt File: starpilot/ui/qt/offroad/device_settings.cc (247 lines)

Device Management Sub-Panel (7 controls):

Control Type Range Step Unit Special
DeviceShutdown VALUE 0-33 1 - Labels: 0=5min, 1-3=15-45min, 4+=hours
NoLogging Toggle - - WARNING toggle
NoUploads TOGGLE+TOGGLE - - Main + "Disable Onroad Only"
HigherBitrate Toggle - -
LowVoltageShutdown VALUE 11.8-12.5 0.1 volts
IncreaseThermalLimits Toggle - - WARNING toggle
UseKonikServer Toggle - -

DeviceShutdown Labels:

  • 0 = "5 minutes"
  • 1-3 = "15/30/45 minutes"
  • 4+ = "X hours" (4=4h, 5=5h, etc.)

Screen Management Sub-Panel (7 controls):

Control Type Range Step Special
ScreenBrightness VALUE 1-101 1 0=Off, 101=Auto
ScreenBrightnessOnroad VALUE 0-101 1 0=Off, 101=Auto
ScreenRecorder BUTTON TOGGLE - - START/STOP recording
ScreenTimeout VALUE 5-60 5 seconds
ScreenTimeoutOnroad VALUE 5-60 5 seconds
StandbyMode Toggle - -

Screen Brightness Labels:

  • 0 = "Off"
  • 1-100 = "X%"
  • 101 = "Auto"

9.5.12 Utilities Panel

Qt File: starpilot/ui/qt/offroad/utilities.cc (369 lines)

Main Panel (6 controls):

Control Type Special
Debug Mode Toggle
Flash Panda BUTTON FLASH
Force Drive State BUTTONS OFFROAD / ONROAD / OFF
Pair to "The Pond" BUTTON PAIR/UNPAIR
Report a Bug BUTTON REPORT
Reset Toggles to Default BUTTON RESET
Reset Toggles to Stock BUTTON RESET

9.5.13 Maps Panel

Qt File: starpilot/ui/qt/offroad/maps_settings.cc (278 lines)

Main Panel:

Control Type Special
Automatically Update Maps BUTTON SELECT Manually/Weekly/Monthly
Download Maps BUTTON DOWNLOAD/CANCEL
Last Updated LABEL Shows date or "Never"
Map Sources BUTTONS COUNTRIES / STATES
Progress LABEL Shows during download
Time Elapsed LABEL
Time Remaining LABEL
Remove Maps BUTTON REMOVE
Storage Used LABEL Shows MB

Countries Sub-Panel: 7 continents with checkbox lists States Sub-Panel: 5 US regions with checkbox lists

9.6 Conditional Visibility Logic

Qt implements complex visibility rules based on multiple factors:

Metric Conversion Constants (from Qt):

// Distance conversions
FOOT_TO_METER = 0.3048
METER_TO_FOOT = 3.28084
INCH_TO_CM = 2.54
CM_TO_INCH = 0.393701

// Speed conversions
MILE_TO_KM = 1.60934
KM_TO_MILE = 0.621371

Value Range Conversion by Unit Type:

Unit Type Imperial Range Metric Range Conversion
Speed (mph/kmh) 0-99 0-150 MILE_TO_KM
Speed (mph/kmh) offset -99 to 99 -150 to 150 MILE_TO_KM
Distance (feet) 0-10 0-3 FOOT_TO_METER
Distance (feet) offset 0-10 0-3 FOOT_TO_METER
Small distance (inches) 0-24 0-60 INCH_TO_CM
Lane width (feet) 0-15 0-5 FOOT_TO_METER

Special Value Labels:

// Volume (0-101)
0 = "Muted"
1-100 = "X%"
101 = "Auto"

// Device shutdown (0-33)
0 = "5 minutes"
1 = "15 minutes"
2 = "30 minutes"
3 = "45 minutes"
4+ = "X hours"

// Speed/time values
0 = "Off" (for many controls)
0 = "Instant" (for LaneChangeTime)

Visibility Factors in Qt:

  1. Tuning Level (0-3): Base visibility threshold
  2. Car Type: isGM, isHKG, isToyota, isSubaru, isAngleCar, isTorqueCar, isVolt, isBolt, isTSK, isHKGCanFd
  3. Car Capabilities: hasBSM, hasRadar, hasPedal, hasSNG, hasNNFFLog, hasAutoTune, hasDashSpeedLimits, hasZSS, canUsePedal, canUseSDSU
  4. Feature Flags: Other toggles states (LaneChanges, NudgelessLaneChange, ModelRandomizer, etc.)
  5. Longitudinal Control: hasOpenpilotLongitudinal, hasPCMCruise
  6. Device State: started (driving), parked, online, isFrogsGoMoo
  7. Special Conditions: hasOpenpilotLongitudinalControlDisabled, hasAlphaLongitudinal

Additional Car State Properties Needed:

class StarPilotCarState:
    # Car detection
    isGM: bool = False
    isHKG: bool = False  # Hyundai/Kia/Genesis
    isToyota: bool = False
    isSubaru: bool = False
    isVolt: bool = False  # Specific GM model
    isBolt: bool = False  # Specific GM model
    isAngleCar: bool = False
    isTorqueCar: bool = False
    isTSK: bool = False  # Toyota Safety Connect
    isHKGCanFd: bool = False
    
    # Capabilities
    hasBSM: bool = False  # Blind spot monitoring
    hasRadar: bool = False
    hasPedal: bool = False
    hasSNG: bool = False  # Stop and Go
    hasNNFFLog: bool = False
    hasAutoTune: bool = False
    hasOpenpilotLongitudinal: bool = False
    hasDashSpeedLimits: bool = False
    hasZSS: bool = False  # ZSS steering sensor
    canUsePedal: bool = False
    canUseSDSU: bool = False
    
    # Device/car state
    isFrogsGoMoo: bool = False
    hasPCMCruise: bool = False
    lkasAllowedForAOL: bool = False
    openpilotLongitudinalControlDisabled: bool = False
    hasAlphaLongitudinal: bool = False
    
    # Car values for range calculations
    steerActuatorDelay: float = 0.0
    friction: float = 0.0
    steerKp: float = 0.0
    latAccelFactor: float = 0.0
    steerRatio: float = 0.0
    longitudinalActuatorDelay: float = 0.0
    startAccel: float = 0.0
    stopAccel: float = 0.0
    stoppingDecelRate: float = 0.0
    vEgoStarting: float = 0.0
    vEgoStopping: float = 0.0

Examples from Qt:

From lateral_settings.cc:

def updateToggles(self):
    for key, toggle in toggles.items():
        min_level = parent.starpilotToggleLevels[key]
        visible = parent.tuningLevel >= min_level.toDouble()
        
        # AlwaysOnLateralLKAS
        if key == "AlwaysOnLateralLKAS":
            visible &= parent.lkasAllowedForAOL
            
        # ForceAutoTune
        elif key == "ForceAutoTune":
            visible &= not parent.hasAutoTune
            visible &= not parent.isAngleCar
            visible &= parent.isTorqueCar or forcingTorqueController or usingNNFF
            
        # NNFF
        elif key == "NNFF":
            visible &= parent.hasNNFFLog
            visible &= not parent.isAngleCar
            
        # LaneChangeTime
        elif key == "LaneChangeTime":
            visible &= params.getBool("LaneChanges")
            visible &= params.getBool("NudgelessLaneChange")
            
        # SteerKP
        elif key == "SteerKP":
            visible &= parent.steerKp != 0
            visible &= parent.isTorqueCar or forcingTorqueController or usingNNFF
            visible &= not parent.isAngleCar
            
        toggle.setVisible(visible)

From longitudinal_settings.cc:

# Conditional Experimental Mode visibility
if key == "CEStopLights":
    visible &= parent.tuningLevel < parent.starpilotToggleLevels["CEModelStopTime"].toDouble()

# Cruise controls
elif key in ["CustomCruise", "CustomCruiseLong", "SetSpeedLimit", "SetSpeedOffset"]:
    visible &= not parent.hasPCMD

# MapGears
elif key == "MapGears":
    visible &= parent.isToyota
    visible &= not parent.isTSK

# HumanLaneChanges
elif key == "HumanLaneChanges":
    visible &= parent.hasRadar

# StartAccel
elif key == "StartAccel":
    visible &= not (params.getBool("LongitudinalTune") and params.getBool("HumanAcceleration"))

From visual_settings.cc:

if key == "AccelerationPath":
    visible &= parent.hasOpenpilotLongitudinal
    
elif key == "BlindSpotPath":
    visible &= parent.hasBSM
    
elif key == "PedalsOnUI":
    visible &= parent.hasOpenpilotLongitudinal
    
elif key == "HideSpeedLimit":
    visible &= parent.hasOpenpilotLongitudinal AND SpeedLimitController
    
elif key == "ShowSpeedLimits":
    visible &= not params.getBool("SpeedLimitController") or not parent.hasOpenpilotLongitudinal
    
elif key == "SLCMapboxFiller":
    visible &= params.getBool("ShowSpeedLimits")
    visible &= not params.getBool("SpeedLimitController") or not parent.hasOpenpilotLongitudinal
    visible &= not params.get("MapboxSecretKey").empty()

From vehicle_settings.cc:

if key in gmKeys:
    visible &= parent.isGM
elif key in hkgKeys:
    visible &= parent.isHKG
elif key in subaruKeys:
    visible &= parent.isSubaru
elif key in toyotaKeys:
    visible &= parent.isToyota

# Specific conditions
if key == "SNGHack":
    visible &= not parent.hasSNG
    
elif key == "GMPedalLongitudinal":
    visible &= parent.hasPedal or (Hardware.PC() and parent.canUsePedal)
    
elif key == "RemapCancelToDistance":
    visible &= parent.isBolt and (parent.hasPedal or (Hardware.PC() and parent.canUsePedal))

9.7 Onroad Overlays (Priority 4)

Feature Qt File Implementation
Blind spot visualization starpilot_onroad.cc Read BSM from car state, draw indicator
FPS counter overlay hud_renderer.py Already partial, add toggle
Steering torque hud_renderer.py Read from car state, display value
Turn signals hud_renderer.py Read signal state, show indicator

9.8 Developer Sidebar (Priority 5)

File: selfdrive/ui/layouts/sidebar.py

Add toggle in sidebar for developer metrics. Show additional metrics when enabled (CPU, memory, etc. with more detail).


9.9 Implementation Order

  1. Phase 1: Add value factory functions to list_view.py (7 types done!)
  2. Phase 2: Implement three-level navigation system (sub-sub-sub-panels for Weather)
  3. Phase 3: Create StarPilotState singleton (car state parsing + desktop fallback)
  4. Phase 4: Create InputDialog & SelectionDialog widgets
  5. Phase 5: Extend Params with get_int/get_float/put_int/put_float
  6. Phase 6: Implement Lateral 5 sub-panels (~21 controls, car-specific ranges, conditional visibility)
  7. Phase 7: Implement Weather sub-panels with real value_item controls + put_int callbacks
  8. Phase 8: Complete Sounds panel (7 volume sliders + Test buttons + sub-panel + custom alerts with conditional visibility)
  9. Phase 9: Complete Driving Model (model selection dialog, blacklist, ratings)
  10. Phase 10: Implement Longitudinal remaining sub-panels (~60+ controls)
  11. Phase 11: Complete Visual 5 sub-panels (~35 controls)
  12. Phase 12: Complete Themes (7 theme types, download/delete/select buttons)
  13. Phase 13: Complete Vehicle 5 sub-panels (~20 controls, car make/model selection)
  14. Phase 14: Complete remaining panels (Device, Navigation, Data, Maps, Utilities, Wheel)
  15. Phase 15: Add conditional visibility logic to ALL panels (Lateral done, rest pending)
  16. Phase 16: Implement real button actions (download, delete, reset, dialogs)
  17. Phase 17: Add metric unit conversion support (constants and label updates)
  18. Phase 18: Onroad overlays (4 features)
  19. Phase 19: Developer sidebar toggle

9.10 Qt Reference Files Summary

Panel Qt File Lines Sub-Panels Controls Special Features
Sounds sounds_settings.cc 222 2 14+ Test buttons, sub-panel navigation
Model model_settings.cc 814 2 9+ Download/delete dialogs, model selection
Lateral lateral_settings.cc 428 5 ~25 Car-specific ranges, metric conversion
Longitudinal longitudinal_settings.cc 1085 17 ~100+ THREE-LEVEL NAV, dual sliders, weather
Visual visual_settings.cc 328 5 ~35 Auto unit conversion
Themes theme_settings.cc 935 1 ~50+ Download/select for 7 types
Navigation navigation_settings.cc 330 1 5+ Key management, setup instructions
Data data_settings.cc 872 1 6+ Backup/restore, stats viewing
Device device_settings.cc 247 2 ~14 Screen recording
Vehicle vehicle_settings.cc 467 5 ~20 Car-specific visibility
Wheel wheel_settings.cc 84 0 4 Dynamic options based on car
Utilities utilities.cc 369 0 7 Network pairing, reporting
Maps maps_settings.cc 278 2 9+ Country/state selection
TOTAL ~5559 43 ~350+

9.11 Key Qt Classes to Port

Qt Class Purpose Where Used
StarPilotParamValueControl Simple value slider Lateral, Longitudinal, Visual, Device
StarPilotParamValueButtonControl Value + Reset/Test button Sounds, Model, Lateral, Vehicle
StarPilotDualParamValueControl Two connected sliders Longitudinal (CESpeed)
StarPilotButtonToggleControl Toggle + sub-toggles Longitudinal, Visual, Vehicle
StarPilotButtonsControl Multiple action buttons Themes, Data, Device, Model
ButtonParamControl Option selection button Longitudinal (profiles), Visual (camera)
StarPilotManageControl Opens sub-panel All main panels
LabelControl Read-only display Device info, Stats, Calibration
MapSelectionControl Checkbox list Maps panel

9.12 Special UI Behaviors

Reboot Requirements

Some toggles require a system reboot when changed while driving:

// Toggles that require reboot
QSet<QString> rebootKeys = {
    "AlwaysOnLateral",        // Lateral
    "ForceTorqueController", // Lateral
    "NNFF",                  // Lateral
    "NNFFLite",              // Lateral
    "DisableOpenpilotLongitudinal", // Vehicle
    "TacoTuneHacks",         // Vehicle (HKG)
    "RemapCancelToDistance", // Vehicle (GM)
    "RemoteStartBootsComma", // Vehicle (GM - triggers flash)
};

// Confirmation pattern
if (StarPilotConfirmationDialog::toggleReboot(this)) {
    Hardware::reboot();
}

Warning Toggles

Some toggles have warning labels:

  • NoLogging: "Warning: ..."
  • IncreaseThermalLimits: "Warning: ..."
  • LockDoorsTimer: "Warning: openpilot can't detect if keys are still inside the car..."

Network Operations

Weather key testing:

// Tests OpenWeatherMap API v2.5 and v3.0
QString url30 = "https://api.openweathermap.org/data/3.0/onecall?lat=...&appid=" + key;
QString url25 = "https://api.openweathermap.org/data/2.5/weather?lat=...&appid=" + key;

Sound Testing

Sound alert testing:

// Offroad: Play via Python sounddevice
QString stockPath = "selfdrive/assets/sounds/" + snakeCaseAlert + ".wav";
QString themePath = "starpilot/assets/active_theme/sounds/" + snakeCaseAlert + ".wav";
float volume = params.getFloat(key) / 100.0f;

// Onroad: Send to params_memory for onroad testing
params_memory.put("TestAlert", camelCaseAlert);

Theme Name Parsing

// Format: "name~creator" (stored lowercase)
// User-created: "name-user_created"
// Display: "Name 🌟" or "Name - by: creator"
QString baseName = value.split("~")[0];
bool userCreated = value.endsWith("-user_created");

Personality Reset Flow

Each personality has a reset button:

// Example: Aggressive personality reset
params.putFloat("AggressiveFollow", defaultValue);
params.putFloat("AggressiveJerkAcceleration", defaultValue);
// ... other values
aggressiveFollowToggle->refresh();
aggressiveAccelerationToggle->refresh();
// ... refresh all toggles

Button State Management

Buttons can be dynamically shown/hidden/enabled:

downloadButton->setEnabledButtons(0, !allDownloaded && online && parked);
downloadButton->setVisibleButton(1, !downloading);
downloadButton->setText(0, downloading ? "CANCEL" : "DOWNLOAD");
downloadButton->setValue("Downloading...");

Description Expansion

Qt panels track which descriptions are expanded:

// Toggle description visibility
toggle->showDescription();  // Expand
toggle->hideDescription();  // Collapse

// Update layout after description change
connect(toggle, &AbstractControl::showDescriptionEvent, this, &Panel::update);

Model Selection Features

  • Groups by series (Custom Series, Driving Policy, etc.)
  • Icons: 🗺️ (Navigation), 📡 (Radar), 👀 (VOACC)
  • Sort modes: alphabetical, release date, rating
  • User favorites (stored in params)
  • Community favorites (stored in params)
  • Release dates (displayed in dialog)

9.12 Required Car State Properties

For conditional visibility, need to track:

class StarPilotCarState:
    # ========== Car Type Detection ==========
    isGM: bool = False           # General Motors
    isHKG: bool = False          # Hyundai/Kia/Genesis
    isToyota: bool = False       # Toyota/Lexus
    isSubaru: bool = False       # Subaru
    isVolt: bool = False         # 2017 Chevy Volt (specific GM)
    isBolt: bool = False         # Chevy Bolt (specific GM)
    isAngleCar: bool = False     # Uses angle-based steering
    isTorqueCar: bool = False    # Uses torque-based steering
    isTSK: bool = False          # Toyota Safety Connect
    isHKGCanFd: bool = False     # Hyundai/Kia with CanFD
    
    # ========== Car Capabilities ==========
    hasBSM: bool = False         # Blind spot monitoring
    hasRadar: bool = False       # Radar sensor
    hasPedal: bool = False      # Pedal interceptor
    hasSNG: bool = False         # Stop and Go
    hasNNFFLog: bool = False    # NNFF log file exists
    hasAutoTune: bool = False    # Has auto-tune capability
    hasOpenpilotLongitudinal: bool = False  # Can control accel/brake
    hasDashSpeedLimits: bool = False  # Dashboard speed limits
    hasZSS: bool = False         # ZSS steering sensor
    canUsePedal: bool = False    # Can use pedal for longitudinal
    canUseSDSU: bool = False     # Can use SDSU
    
    # ========== Device/Car State ==========
    isFrogsGoMoo: bool = False   # FrogsGoMoo device
    hasPCMCruise: bool = False   # Has PCM cruise control
    lkasAllowedForAOL: bool = False  # LKAS allowed for Always On Lateral
    openpilotLongitudinalControlDisabled: bool = False
    hasAlphaLongitudinal: bool = False
    
    # ========== Car Values for Range Calculation ==========
    # These are read from car params and used to calculate slider ranges
    steerActuatorDelay: float = 0.0   # Default: car-specific
    friction: float = 0.0             # Default: car-specific
    steerKp: float = 0.0              # Default: car-specific
    latAccelFactor: float = 0.0       # Default: car-specific
    steerRatio: float = 0.0           # Default: car-specific
    
    # Longitudinal values
    longitudinalActuatorDelay: float = 0.0
    startAccel: float = 0.0
    stopAccel: float = 0.0
    stoppingDecelRate: float = 0.0
    vEgoStarting: float = 0.0
    vEgoStopping: float = 0.0

How Car Values Are Used:

# In showEvent(), ranges are calculated based on car values
steerKpMin = parent.steerKp * 0.5
steerKpMax = parent.steerKp * 1.5

# Title shows default value
title = f"Actuator Delay (Default: {parent.steerActuatorDelay})"

How to Get Car Values:

// From Qt - reads from params
steerActuatorDelay = params.getFloat("SteerActuatorDelay");
if (steerActuatorDelay == 0) {
    steerActuatorDelay = params.getFloat("SteerActuatorDelayDefault");
}

10. Technical Reference

10.1 Core Application Class

File: system/ui/lib/application.py

Key Properties:

  • width - Screen width (2160 or 536)
  • height - Screen height (1080 or 240)
  • target_fps - Target frame rate (default: 60, tizi: 20)
  • frame - Current frame number

Key Methods:

  • init_window(title, fps) - Initialize raylib window
  • render() - Main render loop as generator
  • texture(asset_path, width, height) - Load and cache textures from openpilot assets (selfdrive/assets/)
  • starpilot_texture(asset_path, width, height) - Load and cache textures from StarPilot assets (starpilot/assets/)
  • font(font_weight) - Get font by weight
  • set_modal_overlay(overlay, callback) - Show modal dialogs

Recent Changes:

  • _load_image_from_path now returns early if image.width == 0 or image.height == 0 (zero-size guard)
  • starpilot_texture() uses importlib.resources to resolve starpilot/assets/ path

10.2 Widget Base Class

File: system/ui/widgets/__init__.py

Key Methods:

  • render(rect) - Main render entry point
  • _render(rect) - Abstract method for subclasses
  • _process_mouse_events() - Handle touch/click input
  • show_event() / hide_event() - Lifecycle hooks
  • set_visible(visible) - Show/hide widget
  • set_enabled(enabled) - Enable/disable widget

10.3 UI State Management

File: selfdrive/ui/ui_state.py

UIState Singleton:

  • Subscribes to message bus via cereal.messaging.SubMaster
  • Properties: started, engaged, status, is_metric, has_longitudinal_control
  • Methods: update(), update_params()

Device Class:

  • Manages screen brightness, wakefulness, interactive timeouts

10.4 Environment Variables

Variable Description Default
BIG=1 Force big UI on any device 0 (false)
FPS Target FPS 60 (20 on tizi)
SCALE UI scaling factor 1.0
SHOW_FPS=1 Display FPS counter 0 (false)
SHOW_TOUCHES=1 Show touch points 0 (false)
ENABLE_VSYNC=1 Enable vertical sync 0 (false)

10.5 Device Detection

File: system/ui/lib/application.py

@staticmethod
def big_ui() -> bool:
    return HARDWARE.get_device_type() in ('tici', 'tizi') or BIG_UI

Device Types:

  • tici - Big UI
  • tizi - Big UI
  • mici - Small UI
  • pc - Small UI (unless BIG=1)

10.6 New Dialog Widgets Reference

InputDialog (system/ui/widgets/input_dialog.py):

InputDialog(
  title: str,              # Dialog title
  default_text: str = "",  # Pre-filled text
  hint_text: str = "",     # Placeholder text (gray)
  on_close: Callable[[DialogResult, str], None] | None = None  # (result, entered_text)
)
# Usage: text entry for Mapbox keys, weather API keys, naming themes, etc.
# Features: Keyboard widget, blinking cursor, dimmed background

SelectionDialog (system/ui/widgets/selection_dialog.py):

SelectionDialog(
  title: str,
  options: list[str],                # Selectable options
  current_selection: int = 0,        # Initially selected index
  on_close: Callable[[DialogResult, int, str], None] | None = None  # (result, index, text)
)
# Usage: model selection, car make/model, profile selection, theme selection
# Features: Radio-button UI (green dot), scrollable list, dimmed background

ConfirmDialog (system/ui/widgets/confirm_dialog.py):

ConfirmDialog(
  text: str,
  confirm_text: str,
  cancel_text: str | None = None,    # None defaults to "Cancel"
  rich: bool = False,                # NEW: Enable HtmlRenderer for rich text
  on_close: Callable[[DialogResult], None] | None = None
)
# alert_dialog(message, button_text) - convenience for single-button alerts
# Features: Rich text with HtmlRenderer + Scroller, keyboard shortcuts (Enter/Escape)

Tip

Pro Tip: Dynamic Ranges with Lambda Capture For settings that depend on car-specific values (like PID/### Use the StarPilotState Singleton Don't parse Params in individual widgets. Use the starpilot_state instance.

from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state

# Access car state or tuning levels
if starpilot_state.car_state.isGM:
  ...

ensures that if the car state changes (e.g. from a CarParams update), the UI reflects the new limits immediately without needing a layout reset.

min_val=lambda: starpilot_state.car_state.steerKp * 0.5,
max_val=lambda: starpilot_state.car_state.steerKp * 1.5,

10.7 StarPilotState Singleton Reference

File: selfdrive/ui/lib/starpilot_state.py

Global import: from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state

Key properties:

  • starpilot_state.car_stateStarPilotCarState dataclass with all car type/capability/value fields
  • starpilot_state.tuning_level — Current tuning level (0-3)
  • starpilot_state.toggle_levels — Dict of toggle key → minimum tuning level

Data sources parsed:

  1. CarParamsPersistent — Car fingerprint, lateral tuning, capabilities
  2. FrogPilotCarParamsPersistent — canUsePedal, canUseSDSU, openpilotLongitudinalControlDisabled
  3. LiveTorqueParameters — hasAutoTune (useParams)
  4. FrogPilotToggles — JSON blob with supplementary toggles

Update throttling: 2.0s minimum interval between heavy param parsing

Desktop fallback: When PC=True and car is "mock", applies configurable fallback defaults (GM Bolt by default)

Usage pattern in sub-panels:

from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state

# Dynamic range from car state
value_button_item(
  lambda: tr("Kp Factor") + f" (Default: {starpilot_state.car_state.steerKp:.2f})",
  "SteerKP",
  min_val=lambda: starpilot_state.car_state.steerKp * 0.5,
  max_val=lambda: starpilot_state.car_state.steerKp * 1.5,
  ...
)

# Conditional visibility from car state
toggle_item(
  ...,
  enabled=lambda: not starpilot_state.car_state.isAngleCar and not starpilot_state.car_state.isTorqueCar
)

10.8 Params Helpers Reference

File: common/params.py

# Reading typed params (with safe fallbacks)
params.get_int("TuningLevel", return_default=True, default=1)   # → int
params.get_float("SteerDelay", return_default=True, default=0.0) # → float

# Writing typed params (auto-detects ParamKeyType)
params.put_int("IncreaseFollowingLowVisibility", 2)   # Checks get_type(), casts appropriately
params.put_float("SteerDelay", 0.5)                    # Same type-aware behavior

# Type detection order: FLOAT → float(), INT → int(), BOOL → bool(), else → str()

10.9 Widget Factory Functions Reference

File: system/ui/widgets/list_view.py

These factory functions create standardized list items for settings panels:

# Toggle item (on/off switch)
def toggle_item(
  title: str | Callable[[], str],
  description: str | Callable[[], str] | None = None,
  initial_state: bool = False,
  callback: Callable | None = None,
  icon: str = "",
  enabled: bool | Callable[[], bool] = True,
) -> ListItem:
  # Creates a toggle switch with icon and description
  action = ToggleAction(initial_state=initial_state, enabled=enabled, callback=callback)
  return ListItem(title=title, description=description, action_item=action, icon=icon)

# Button item (navigation/ action)
def button_item(
  title: str | Callable[[], str],
  button_text: str | Callable[[], str],
  description: str | Callable[[], str] | None = None,
  callback: Callable | None = None,
  enabled: bool | Callable[[], bool] = True,
  icon: str = "",
  starpilot_icon: bool = False,  # For StarPilot assets
) -> ListItem:
  # Creates a button with title, description, and button text
  action = ButtonAction(text=button_text, enabled=enabled)
  item = ListItem(title=title, description=description, action_item=action, callback=callback, icon=icon if not starpilot_icon else "")
  if icon and starpilot_icon:
    item.set_icon(icon, starpilot=True)
  return item

# Text item (read-only display)
def text_item(
  title: str | Callable[[], str],
  value: str | Callable[[], str],
  description: str | Callable[[], str] | None = None,
  callback: Callable | None = None,
  enabled: bool | Callable[[], bool] = True,
) -> ListItem:
  # Displays title and value side by side
  action = TextAction(text=value, color=ITEM_TEXT_VALUE_COLOR, enabled=enabled)
  return ListItem(title=title, description=description, action_item=action, callback=callback)

# Dual button item (two buttons - e.g., Reboot | Power Off)
def dual_button_item(
  left_text: str | Callable[[], str],
  right_text: str | Callable[[], str],
  left_callback: Callable = None,
  right_callback: Callable = None,
  description: str | Callable[[], str] | None = None,
  enabled: bool | Callable[[], bool] = True,
) -> ListItem:
  # Creates two buttons in one row
  action = DualButtonAction(left_text, right_text, left_callback, right_callback, enabled)
  return ListItem(title="", description=description, action_item=action)

# Multiple button item (selection buttons - e.g., Aggressive | Standard | Relaxed)
def multiple_button_item(
  title: str | Callable[[], str],
  description: str | Callable[[], str],
  buttons: list[str | Callable[[], str]],
  selected_index: int,
  button_width: int = BUTTON_WIDTH,
  callback: Callable = None,
  icon: str = "",
):
  # Creates multiple selectable buttons in a row
  action = MultipleButtonAction(buttons, button_width, selected_index, callback=callback)
  return ListItem(title=title, description=description, icon=icon, action_item=action)

# Simple item (just title, no action)
def simple_item(title: str | Callable[[], str], callback: Callable | None = None) -> ListItem:
  return ListItem(title=title, callback=callback)

Value Control Factory Functions (StarPilot-specific):

# Value slider (param_key-based, reads/writes automatically)
def value_item(title, param_key, min_val, max_val, step, unit, description, callback, icon, enabled, is_metric, labels, negative)

# Value slider + action button (Reset/Test)
def value_button_item(title, param_key, min_val, max_val, step, button_text, button_callback, description, callback, icon, enabled, sub_toggles, labels, is_metric, negative)

# Two connected sliders (e.g., CESpeed "Without Lead" + "With Lead")
def dual_value_item(title, value1, value2, min_val, max_val, step, unit, label1, label2, description, callback1, callback2, icon, enabled)

# Toggle + sub-toggles (e.g., CECurves with "With Lead" sub-toggle)
def button_toggle_item(title, state, sub_toggles, sub_toggle_names, description, callback, sub_callbacks, icon, enabled, exclusive)

# Multiple action buttons (DELETE/DOWNLOAD/SELECT)
def buttons_item(title, buttons, button_callbacks, description, icon, enabled, initial_value)

# Button that opens selection dialog
def selection_button_item(title, options, selected_index, description, callback, icon, enabled)

# Read-only label display
def label_item(title, value, description, icon, enabled)

# Category buttons (horizontal row of buttons next to title)
def category_buttons_item(title, buttons, description, icon, button_width, enabled, starpilot_icon)

# Multiple selectable buttons with starpilot_icon support
def multiple_button_item(title, description, buttons, selected_index, button_width, callback, icon, starpilot_icon)

New Factor Functions Details (Added March 17, 2026)

dual_value_item:

  • Parameters: title, value1, value2, min_val, max_val, step, unit, label1, label2, description, callback1, callback2, icon, enabled, labels, is_metric.
  • Usage: Two connected sliders in one row (e.g. CESpeed "Without Lead" and "With Lead").

button_toggle_item:

  • Parameters: title, state, sub_toggles, sub_toggle_names, description, callback, sub_callbacks, icon, enabled, exclusive.
  • Usage: Main toggle with a "sub-menu" of associated buttons/toggles (e.g. CECurves with "With Lead").

buttons_item:

  • Parameters: title, buttons, button_callbacks, description, icon, enabled, initial_value.
  • Usage: Row of action buttons (e.g. themes Download/Delete/Select).

selection_button_item:

  • Parameters: title, options, selected_index, description, callback, icon, enabled.
  • Usage: Button that triggers a SelectionDialog.

label_item:

  • Parameters: title, value, description, icon, enabled.
  • Usage: Passive display of information (read-only).

Key Constants:

ITEM_BASE_WIDTH = 1840  # Default item width
ITEM_BASE_HEIGHT = 126   # Default item height
ICON_SIZE = 80          # Icon dimensions
BUTTON_WIDTH = 255      # Default button width
ITEM_PADDING = 50       # Padding inside items
RIGHT_ITEM_PADDING = 20 # Spacing between items
BUTTON_FONT_SIZE = 35   # Category button text size

10.10 ListItem Class Reference

File: system/ui/widgets/list_view.py

The ListItem class is the base widget for all settings list items:

class ListItem(Widget):
  def __init__(self, title: str | Callable[[], str] = "", 
               icon: str | None = None, 
               description: str | Callable[[], str] | None = None,
               description_visible: bool = False, 
               callback: Callable | None = None,
               action_item: ItemAction | None = None):
    # title: Display title (string or callable for dynamic text)
    # icon: Icon filename (auto-loaded from icons/ or StarPilot assets)
    # description: Expandable description text (supports HTML bold tags)
    # callback: Called when action_item is activated (via set_click_callback)
    # action_item: The right-side widget (Toggle, Button, Text, CategoryButtons, etc.)

Key Methods:

  • set_icon(icon, starpilot=False) - Set icon, optionally from StarPilot assets
  • set_description(description) - Update description text
  • set_visible(visible) - Show/hide item
  • set_enabled(enabled) - Enable/disable item
  • set_click_callback(callback) - Set callback (renamed from .callback attribute)
  • get_item_height(font, max_width) - Calculate item height (including expanded description)
  • Sound Test Process: Uses a persistent Python subprocess to play sounds without blocking the UI thread or or causing sounddevice crashes. Spawned in sounds.py.
  • Panel Modularization: The massive starpilot.py was successfully split into 14 distinct modules in selfdrive/ui/layouts/settings/starpilot/ for better maintainability (March 2026).

Important

The Golden Rule of UI Parity When porting from Qt (C++), functional parity is not enough. You must achieve visual parity.

  • If the Qt UI has a 3px border, the Raylib UI must have a 3px border.
  • If the Qt UI uses a specific hex color (e.g. #333333), DO NOT use a generic Gray. Use the exact hex.
  • Premium interfaces feel "alive" through responsive feedback (instant label changes) and high-fidelity styling.
  • Avoid "patchwork" designs; if a widget doesn't support the required look, extend the widget rather than hacking the layout.

Appendix A: File Location Reference

Qt Files (for reference)

  • selfdrive/ui/qt/window.cc/h - Main window
  • selfdrive/ui/qt/home.cc/h - Home window container
  • selfdrive/ui/qt/sidebar.cc/h - Sidebar
  • selfdrive/ui/qt/onroad/onroad_home.cc/h - Onroad UI
  • selfdrive/ui/qt/offroad/settings.cc/h - Settings window

Raylib Files (current implementation)

  • common/params.py - Extended Params wrapper (get_int, get_float, put_int, put_float)
  1. Inherit from StarPilotPanel: Sub-panels should inherit from StarPilotPanel and override _render (usually just self._scroller.render(rect)).
    from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
    

Appendix B: StarPilot-Specific Qt Files Reference

Onroad

  • starpilot/ui/qt/onroad/starpilot_onroad.cc/h - Main StarPilot onroad
  • starpilot/ui/qt/onroad/starpilot_buttons.cc/h - Custom buttons
  • starpilot/ui/qt/onroad/starpilot_annotated_camera.cc/h - Camera with overlays

Offroad Settings

  • starpilot/ui/qt/offroad/starpilot_settings.cc/h - Main StarPilot settings
  • starpilot/ui/qt/offroad/lateral_settings.cc/h - Steering settings
  • starpilot/ui/qt/offroad/longitudinal_settings.cc/h - Gas/brake settings
  • starpilot/ui/qt/offroad/theme_settings.cc/h - Theme settings
  • starpilot/ui/qt/offroad/visual_settings.cc/h - Visual settings

Widgets

  • starpilot/ui/qt/widgets/drive_stats.cc/h - Drive statistics
  • starpilot/ui/qt/widgets/drive_summary.cc/h - Drive summary
  • starpilot/ui/qt/widgets/developer_sidebar.cc/h - Developer sidebar

Appendix C: Reboot Confirmation Pattern

Used by toggles that require reboot when changed while driving (ALwaysOnLateral, NNFF, ForceTorqueController, etc.):

def _on_reboot_toggle(self, key, state):
    self._params.put_bool(key, state)
    from openpilot.selfdrive.ui.ui_state import ui_state
    if ui_state.started:
        from openpilot.system.ui.lib.application import gui_app
        from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
        from openpilot.system.ui.widgets import DialogResult
        def _confirm_reboot(res):
            gui_app.set_modal_overlay(None)
            if res == DialogResult.CONFIRM:
                from openpilot.system.hardware import HARDWARE
                HARDWARE.reboot()
        dialog = ConfirmDialog("Reboot required to take effect. Reboot now?", "Reboot", "Cancel", on_close=_confirm_reboot)
        gui_app.set_modal_overlay(dialog)

Appendix D: Sub-Panel Navigation Pattern (Lateral Example)

Pattern for implementing sub-panels within a StarPilot panel:

class StarPilotLateralLayout(StarPilotPanel):
    def __init__(self):
        super().__init__()
        
        self._sub_panels = {
            "advanced_lateral": StarPilotAdvancedLateralLayout(),
            "always_on_lateral": StarPilotAlwaysOnLateralLayout(),
            # ... more sub-panels
        }
        
        # Wire up navigation for sub-panels that have their own sub-panels
        for name, panel in self._sub_panels.items():
            if hasattr(panel, 'set_navigate_callback'):
                panel.set_navigate_callback(self._navigate_to)
        
        # Main panel items are button_items with callback=lambda: self._navigate_to("key")
        items = [
            button_item("Advanced Lateral Tuning", lambda: tr("MANAGE"), ...,
                        callback=lambda: self._navigate_to("advanced_lateral")),
        ]
        
        # All underlying routing and render/show_event forwarding is handled by StarPilotPanel automatically!

Document generated for StarPilot UI porting efforts Last updated: March 17, 2026


11. RayGUI Analysis (Aborted)

11.1 Investigation Hypothesis

RayGUI is an immediate-mode GUI auxiliary module natively shipped with Raylib. A hypothesis was formed that swapping the custom python-based widgets (Button, Toggle, Slider) for pyray.Gui* calls (gui_button, gui_toggle, gui_check_box) would simplify the code while providing parity.

11.2 Line-By-Line Line Findings

A systematic, line-by-line analysis of system/ui/widgets/ was conducted. Conclusion: RayGUI does not possess the native capability to replace the existing custom UI.

  1. toggle.py (Toggle)

    • Custom Implementation: Draws a perfect pill shape natively using draw_rectangle_rounded(..., 1.0, ...), a distinct white knob via draw_circle(), and calculates smooth interpolation between On/Off states across frame ticks. Uses Qt legacy color palettes.
    • RayGUI Limitation: Native gui_toggle is a flat button. Native gui_toggle_slider does not natively interpolate its knob tracking smoothly; it snaps instantly. It also inherently demands text be drawn inside the slider pill.
  2. button.py (Button, SmallButton, WideRoundedButton)

    • Custom Implementation: Uses an 11-style ButtonStyle enum mixing disparate border thicknesses, hover alpha filters, text, and base colors dynamically. Many buttons (SmallButton, WidishRoundedButton, IconButton) natively map to explicit texture assets for standard, pressed, and disabled states (e.g. "icons_mici/setup/small_red_pill_pressed.png").
    • RayGUI Limitation: gui_button relies on a global style memory object. Swapping the entire structural geometry and color arrays via GuiSetStyle() before every button draw is messy and slower. Natively mapping 3D-shaded .png button textures natively to RayGUI forms is not officially supported without deep customization that defeats the simplicity of immediate-mode wrapping.
  3. slider.py (SmallSlider, LargerSlider)

    • Custom Implementation: These are highly specialized "Swipe-to-Confirm" controls incorporating FirstOrderFilter physics modeling for bounce-back, drag thresholds, and custom red circle draggable knobs.
    • RayGUI Limitation: RayGUI does not offer a native swipe-to-confirm touch primitive or drag physics.
  4. input_dialog.py & On-Screen Interactions

    • Custom Implementation: Custom Touch injected keyboard.
    • RayGUI Limitation: gui_text_box binds to GLFW and physical hardware events natively, breaking compatibility with our pure touch-first context.

11.3 Conclusion

The custom widgets authored in Python are mechanically superior, highly tailored to the automotive Qt styling aesthetics, and strictly capable of things RayGUI primitives are not (smooth physics, pure texture-mapping, swipe-to-confirm). The Raylib port will continue relying natively on the existing python system/ui/widgets/ suite.