128 KiB
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
- High-Level Architecture Comparison
- Key Parallels: Qt to Raylib
- Code Structure Patterns
- Onroad UI Comparison
- Sidebar Comparison
- Settings Layout Comparison
- Implementation Differences
- Files Requiring Work for Full Port
- StarPilot Complete Implementation Roadmap
- Technical Reference
- 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(pluslateral_settings.cc,longitudinal_settings.cc, etc.) - Raylib Port:
selfdrive/ui/layouts/settings/starpilot(Modularized!) - UI State Singleton:
selfdrive/ui/lib/starpilot_state.py(CentralizedStarPilotState) 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
SettingsWindowemits 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
StackedLayout≈ Raylib_panelsdictionary inmain_panel.py - Qt
StarPilotPanelType≈ RaylibStarPilotPanelTypeinpanel.py(Integer-based routing) - Qt
Panel::showEvent()≈ RaylibWidget.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
QPropertyAnimationfor 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.ccselfdrive/ui/qt/onroad/annotated_camera.ccselfdrive/ui/qt/onroad/hud.ccselfdrive/ui/qt/onroad/alerts.ccselfdrive/ui/qt/onroad/buttons.ccstarpilot/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.pyselfdrive/ui/onroad/hud_renderer.py(180 lines)selfdrive/ui/onroad/alert_renderer.pyselfdrive/ui/onroad/driver_state.pyselfdrive/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
StarPilotLayoutentry tosettings/settings.py - Created
layouts/settings/starpilot.pywith full sub-panel navigation system - Added
StarPilotPanelTypeenum 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
StarPilotSoundsLayoutwith 6 toggle items for custom alerts - Implemented
StarPilotDrivingModelLayoutwith toggles + buttons - Implemented
StarPilotLongitudinalLayout,StarPilotLateralLayoutwith 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_itemwith "MANAGE" button text matching Qt UI - Added
starpilot_texture()method toGuiApplicationfor loading StarPilot assets - Added
starpilot_iconparameter tobutton_item()andtoggle_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
StarPilotStatesingleton (selfdrive/ui/lib/starpilot_state.py) with:StarPilotCarStatedataclass with all car type, capability, and value fields- Reads
CarParamsPersistent,FrogPilotCarParamsPersistent,LiveTorqueParameters,FrogPilotTogglesfrom 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: 5value_button_itemcontrols with car-specific dynamic ranges (starpilot_state.car_state.steerKp * 0.5to* 1.5) + Reset buttons + 3 toggle items with conditional visibility based on car stateStarPilotAlwaysOnLateralLayout: 2 toggles (with reboot confirmation) + 1 value sliderStarPilotLaneChangesLayout: 6 controls with conditional visibility (LaneChanges AND NudgelessLaneChange)StarPilotLateralTuneLayout: 3 toggles (NNFF, NNFFLite, TurnDesires) with reboot confirmationStarPilotLateralQOLLayout: 1value_button_itemwith sub-toggle
- Weather sub-panels now use real
value_item()controls withput_int()callbacks - Created
InputDialogwidget (system/ui/widgets/input_dialog.py):- Full keyboard input with
Keyboardwidget integration - Text input field with hint text, blinking cursor
- Confirm/Cancel buttons, dimmed background overlay
- Callback:
on_close(DialogResult, str)returns entered text
- Full keyboard input with
- Created
SelectionDialogwidget (system/ui/widgets/selection_dialog.py):- Radio-button selection from a list of options
- Scrollable list using
Scrollerwidget - Visual feedback: green dot for selected, circle outline for unselected
- Callback:
on_close(DialogResult, int, str)returns index and text
- Extended
Paramswrapper (common/params.py):get_int(key, block, return_default, default)— Parses string param as intget_float(key, block, return_default, default)— Parses string param as floatput_int(key, val)— Type-aware save (checksget_type()for FLOAT/INT/BOOL/string)put_float(key, val)— Same type-aware save for float values- Fixes
TypeErrorwhen saving FLOAT-typed params likeIncreaseFollowingLowVisibility
- Updated
ConfirmDialog(system/ui/widgets/confirm_dialog.py):- Added
richmode withHtmlRenderer+Scrollerfor rich text content - Scrollable text area for long messages
- Keyboard shortcut support: Enter (confirm), Escape (cancel)
- Added
- Settings back button icon changed from
icons/close2.png→icons/backspace.png - Zero-size image guard in
application.py:_load_image_from_pathnow returns early ifimage.width == 0 or image.height == 0, preventing potential crashes from empty textures
Completed (March 17, 2026 - Late Session)
- Fully Implemented Driving Model Panel:
- Integrated
ModelManagerfor auto-fetching model lists if empty. - Implemented managed background download thread in
starpilot.pyfor desktop/PC environments (Daemon-less). - Added blacklist and ratings management dialogs.
- Integrated
- High-Fidelity SelectionDialog Enhancements:
- Favorites (Star) System: Every item can be starred (♥/♡). Persisted in
UserFavoritesandCommunityFavorites. - Premium Styling: Matched Qt header (
#333333BG) and selection (#465BEABG + 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.
- Favorites (Star) System: Every item can be starred (♥/♡). Persisted in
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\nlines from stdin - Uses
sounddeviceto 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
TestAlertparam inparams_memory(handled bysoundd.py)
- Offroad: Uses persistent Python subprocess (matching Qt's
- Custom Alerts conditional visibility:
LoudBlindspotAlert: Visible only ifstarpilot_state.car_state.hasBSMSpeedLimitChangedAlert: Visible ifShowSpeedLimitsOR (hasOpenpilotLongitudinalANDSpeedLimitController)
- Sub-panel navigation:
_setup_sounds_sub_panels()inStarPilotLayoutwires 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:
- Create a new module in
selfdrive/ui/layouts/settings/starpilot/(e.g.,new_feature.py). - Inherit from
StarPilotPanel(frompanel.py):from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel class StarPilotNewFeatureLayout(StarPilotPanel): def __init__(self): super().__init__() # Build Layout... - 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()
- Register in
main_panel.py:- Import the new class.
- Add a key to
StarPilotPanelType. - Add to the
_panelsdictionary inStarPilotLayout.
- Update
refresh_visibilityinmain_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
-
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
-
Sub-Panel Navigation & Boilerplate Reduction: StarPilot uses a DRY architecture via the
StarPilotPanel(Widget)base class:- Inheriting from
StarPilotPanelautomatically provides:self._params,self._params_memory,self._tuning_levels, and navigation lifecycle functions. - It also automatically handles standard
_render()andshow_event()routing forself._scrollerandself._sub_panelsmaps. - Main categories route to sub-panels via a two-level navigation system using
StarPilotPanelTypeenums.
- Inheriting from
-
Asset Loading for External Packages:
- Use
gui_app.texture()for openpilot assets (selfdrive/assets/) - Use
gui_app.starpilot_texture()for assets located in the corestarpilotassets folder.
- Use
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/`
-
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! ) -
Description Formatting: Use HTML bold tags to match Qt:
description=tr_noop("<b>Description text here</b>") -
Button Text Patterns:
- Navigation/management: "MANAGE", "VIEW", "CHANGE"
- NOT ">" or similar symbols
-
Python 3.12 Compatibility:
- Use
from __future__ import annotationsat top of files - Use
Optional[X]instead ofX | Nonefor type hints - Use
from typing import Callablefor callbacks
- Use
-
Known Issues:
- Empty string handling in
set_icon()can cause division by zero if texture loading fails - Icons from StarPilot assets require
starpilot_icon=Trueparameter
- Empty string handling in
-
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_itemwith a "<" or "Back" button text for the back action - See
starpilot.pyfor the complete implementation
-
Premium UI "TLC" (High-Fidelity Parity):
- Colors: Use specific Qt hex codes (e.g.,
#333333for headers,#465BEAfor 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.
- Colors: Use specific Qt hex codes (e.g.,
-
Background Process Management:
- Daemon-less Downloads: On PC/Desktop where
starpilot_process.pyisn't running, the UI must manage its own background threads. - Thread Safety: Always wrap long-running UI-initiated threads in
try-exceptblocks. If they crash, they fail silently in Raylib. - Progress Tracking: Poll
Params(memory) for progress updates (ModelDownloadProgress) to keep the UI responsive.
- Daemon-less Downloads: On PC/Desktop where
-
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
starpilotToggleLevelsmap) - 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:
StarPilotLayoutstores reference toSettingsLayoutviaset_settings_layout()- When tuning level changes, calls
settings_layout.refresh_developer_visibility() - Developer panel checked dynamically on each render
-
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 horizontallyFactory 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 buttonpanel_key: Which panel to openmin_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:
-
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
-
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.
-
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
-
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
-
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
-
ButtonParamControl- Button selection (not value)- Opens dialog to select from options
- Used for: AccelerationProfile, DecelerationProfile, CameraView, SLCFallback, SLCOverride
-
LabelControl- Read-only display- Shows static text or dynamic values
- Used for: Vehicle info, Stats, Calibration values, Download status
- No user interaction
-
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:
- Navigation callback chain: Parent → Child → Grandchild flows through callbacks
- State-based rendering: Parent panel decides which sub-panel to render based on
_current_sub_panel - Back navigation: Settings sidebar's back button pops the panel stack
- 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
- Sub-sub-panel enum: Add
StarPilotSubPanelTypefor controls within sub-panels - Back button: Each sub-panel needs back navigation to parent
- Navigation stack: Track depth for proper back navigation
- 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 buttonsStarPilotVolumeControlLayout: 7 volume sliders with Test buttonsStarPilotCustomAlertsLayout: 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\nfrom stdin, plays WAV via sounddevice - Onroad: Sets
TestAlertparam in params_memory (handled bysoundd.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 ifModelRandomizeris ONSelectModel: Only ifModelRandomizeris OFFRecoveryPower,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.5toparent->steerKp * 1.5 - SteerLatAccel:
parent->latAccelFactor * 0.5toparent->latAccelFactor * 1.5 - SteerRatio:
parent->steerRatio * 0.5toparent->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 carNNFFLite: 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
StarPilotDualParamValueControlwrapping twoStarPilotParamValueControl - 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 levelCustomCruise,CustomCruiseLong,SetSpeedLimit,SetSpeedOffset: Only if NOT hasPCMCruiseHumanLaneChanges: Only if hasRadarMapGears: Only if isToyota AND NOT isTSKReverseCruise: Only if isToyotaSLCMapboxFiller: Only if MapboxSecretKey is setStartAccel: 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 hasOpenpilotLongitudinalBlindSpotPath: Only if hasBSMHideSpeedLimit: Only if hasOpenpilotLongitudinal AND SpeedLimitControllerShowSpeedLimits: Only if NOT SpeedLimitController OR NOT hasOpenpilotLongitudinalSLCMapboxFiller: Only if ShowSpeedLimits AND (NOT SpeedLimitController OR NOT hasOpenpilotLongitudinal) AND MapboxSecretKey setUseVienna: 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 hasSNGTacoTuneHacks: Only if isHKGCanFdVoltSNG: Only if isVolt AND NOT hasSNGSNGHack: 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:
- Tuning Level (0-3): Base visibility threshold
- Car Type: isGM, isHKG, isToyota, isSubaru, isAngleCar, isTorqueCar, isVolt, isBolt, isTSK, isHKGCanFd
- Car Capabilities: hasBSM, hasRadar, hasPedal, hasSNG, hasNNFFLog, hasAutoTune, hasDashSpeedLimits, hasZSS, canUsePedal, canUseSDSU
- Feature Flags: Other toggles states (LaneChanges, NudgelessLaneChange, ModelRandomizer, etc.)
- Longitudinal Control: hasOpenpilotLongitudinal, hasPCMCruise
- Device State: started (driving), parked, online, isFrogsGoMoo
- 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
- Phase 1: ✅ Add value factory functions to list_view.py (7 types done!)
- Phase 2: ✅ Implement three-level navigation system (sub-sub-sub-panels for Weather)
- Phase 3: ✅ Create StarPilotState singleton (car state parsing + desktop fallback)
- Phase 4: ✅ Create InputDialog & SelectionDialog widgets
- Phase 5: ✅ Extend Params with get_int/get_float/put_int/put_float
- Phase 6: ✅ Implement Lateral 5 sub-panels (~21 controls, car-specific ranges, conditional visibility)
- Phase 7: ✅ Implement Weather sub-panels with real value_item controls + put_int callbacks
- Phase 8: ✅ Complete Sounds panel (7 volume sliders + Test buttons + sub-panel + custom alerts with conditional visibility)
- Phase 9: Complete Driving Model (model selection dialog, blacklist, ratings)
- Phase 10: Implement Longitudinal remaining sub-panels (~60+ controls)
- Phase 11: Complete Visual 5 sub-panels (~35 controls)
- Phase 12: Complete Themes (7 theme types, download/delete/select buttons)
- Phase 13: Complete Vehicle 5 sub-panels (~20 controls, car make/model selection)
- Phase 14: Complete remaining panels (Device, Navigation, Data, Maps, Utilities, Wheel)
- Phase 15: Add conditional visibility logic to ALL panels (Lateral done, rest pending)
- Phase 16: Implement real button actions (download, delete, reset, dialogs)
- Phase 17: Add metric unit conversion support (constants and label updates)
- Phase 18: Onroad overlays (4 features)
- 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 windowrender()- Main render loop as generatortexture(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 weightset_modal_overlay(overlay, callback)- Show modal dialogs
Recent Changes:
_load_image_from_pathnow returns early ifimage.width == 0 or image.height == 0(zero-size guard)starpilot_texture()usesimportlib.resourcesto resolvestarpilot/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 inputshow_event()/hide_event()- Lifecycle hooksset_visible(visible)- Show/hide widgetset_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 UItizi- Big UImici- Small UIpc- 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
Paramsin individual widgets. Use thestarpilot_stateinstance.
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_state—StarPilotCarStatedataclass with all car type/capability/value fieldsstarpilot_state.tuning_level— Current tuning level (0-3)starpilot_state.toggle_levels— Dict of toggle key → minimum tuning level
Data sources parsed:
CarParamsPersistent— Car fingerprint, lateral tuning, capabilitiesFrogPilotCarParamsPersistent— canUsePedal, canUseSDSU, openpilotLongitudinalControlDisabledLiveTorqueParameters— hasAutoTune (useParams)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 assetsset_description(description)- Update description textset_visible(visible)- Show/hide itemset_enabled(enabled)- Enable/disable itemset_click_callback(callback)- Set callback (renamed from.callbackattribute)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
sounddevicecrashes. Spawned insounds.py. - Panel Modularization: The massive
starpilot.pywas successfully split into 14 distinct modules inselfdrive/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 windowselfdrive/ui/qt/home.cc/h- Home window containerselfdrive/ui/qt/sidebar.cc/h- Sidebarselfdrive/ui/qt/onroad/onroad_home.cc/h- Onroad UIselfdrive/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)
- Inherit from
StarPilotPanel: Sub-panels should inherit fromStarPilotPaneland override_render(usually justself._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 onroadstarpilot/ui/qt/onroad/starpilot_buttons.cc/h- Custom buttonsstarpilot/ui/qt/onroad/starpilot_annotated_camera.cc/h- Camera with overlays
Offroad Settings
starpilot/ui/qt/offroad/starpilot_settings.cc/h- Main StarPilot settingsstarpilot/ui/qt/offroad/lateral_settings.cc/h- Steering settingsstarpilot/ui/qt/offroad/longitudinal_settings.cc/h- Gas/brake settingsstarpilot/ui/qt/offroad/theme_settings.cc/h- Theme settingsstarpilot/ui/qt/offroad/visual_settings.cc/h- Visual settings
Widgets
starpilot/ui/qt/widgets/drive_stats.cc/h- Drive statisticsstarpilot/ui/qt/widgets/drive_summary.cc/h- Drive summarystarpilot/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.
-
toggle.py(Toggle)- Custom Implementation: Draws a perfect pill shape natively using
draw_rectangle_rounded(..., 1.0, ...), a distinct white knob viadraw_circle(), and calculates smooth interpolation between On/Off states across frame ticks. Uses Qt legacy color palettes. - RayGUI Limitation: Native
gui_toggleis a flat button. Nativegui_toggle_sliderdoes not natively interpolate its knob tracking smoothly; it snaps instantly. It also inherently demands text be drawn inside the slider pill.
- Custom Implementation: Draws a perfect pill shape natively using
-
button.py(Button,SmallButton,WideRoundedButton)- Custom Implementation: Uses an 11-style
ButtonStyleenum 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_buttonrelies on a global style memory object. Swapping the entire structural geometry and color arrays viaGuiSetStyle()before every button draw is messy and slower. Natively mapping 3D-shaded.pngbutton textures natively to RayGUI forms is not officially supported without deep customization that defeats the simplicity of immediate-mode wrapping.
- Custom Implementation: Uses an 11-style
-
slider.py(SmallSlider,LargerSlider)- Custom Implementation: These are highly specialized "Swipe-to-Confirm" controls incorporating
FirstOrderFilterphysics 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.
- Custom Implementation: These are highly specialized "Swipe-to-Confirm" controls incorporating
-
input_dialog.py& On-Screen Interactions- Custom Implementation: Custom Touch injected keyboard.
- RayGUI Limitation:
gui_text_boxbinds 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.