From 47d848293b54a6adf83c964120ed74dfbdf58359 Mon Sep 17 00:00:00 2001 From: nayan Date: Sat, 15 Nov 2025 09:28:43 -0500 Subject: [PATCH 01/18] param to control stock vs sp ui --- common/params_keys.h | 1 + 1 file changed, 1 insertion(+) diff --git a/common/params_keys.h b/common/params_keys.h index 38578d7ddc..fe08062987 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -173,6 +173,7 @@ inline static std::unordered_map keys = { {"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}}, {"ShowTurnSignals", {PERSISTENT | BACKUP, BOOL, "0"}}, {"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}}, + {"sunnypilot_ui", {PERSISTENT, BOOL, "1"}}, {"TrueVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}}, // MADS params From 121b304fe0dcd8bfad02a05aaba6d3e0c9d3029e Mon Sep 17 00:00:00 2001 From: nayan Date: Sat, 15 Nov 2025 09:28:58 -0500 Subject: [PATCH 02/18] init styles --- system/ui/sunnypilot/lib/styles.py | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 system/ui/sunnypilot/lib/styles.py diff --git a/system/ui/sunnypilot/lib/styles.py b/system/ui/sunnypilot/lib/styles.py new file mode 100644 index 0000000000..851a52817d --- /dev/null +++ b/system/ui/sunnypilot/lib/styles.py @@ -0,0 +1,35 @@ +import pyray as rl +from dataclasses import dataclass + +@dataclass +class Default: + # Base Colors + BASE_BG_COLOR = rl.Color(57, 57, 57, 255) # Grey + ON_BG_COLOR = rl.Color(28, 101, 186, 255) # Blue + OFF_BG_COLOR = BASE_BG_COLOR + ON_HOVER_BG_COLOR = rl.Color(17, 78, 150, 255) # Dark Blue + OFF_HOVER_BG_COLOR = rl.Color(21, 21, 21, 255) # Dark gray + DISABLED_ON_BG_COLOR = rl.Color(37, 70, 107, 255) # Dull Blue + DISABLED_OFF_BG_COLOR = rl.Color(39, 39, 39, 255) # Grey + ITEM_TEXT_COLOR = rl.WHITE + ITEM_DISABLED_TEXT_COLOR = rl.Color(88, 88, 88, 255) + ITEM_DESC_TEXT_COLOR = rl.Color(128, 128, 128, 255) + + # Widget/Control Base Dimensions + ITEM_BASE_HEIGHT = 170 + ITEM_PADDING = 20 + ITEM_TEXT_FONT_SIZE = 50 + ITEM_DESC_FONT_SIZE = 40 + ITEM_DESC_V_OFFSET = 150 + + # Toggle Control + TOGGLE_HEIGHT = 100 + TOGGLE_WIDTH = int(TOGGLE_HEIGHT * 1.75) + TOGGLE_BG_HEIGHT = TOGGLE_HEIGHT - 20 + + TOGGLE_ON_COLOR = ON_BG_COLOR + TOGGLE_OFF_COLOR = OFF_BG_COLOR + TOGGLE_KNOB_COLOR = rl.WHITE + TOGGLE_DISABLED_ON_COLOR = DISABLED_ON_BG_COLOR + TOGGLE_DISABLED_OFF_COLOR = DISABLED_OFF_BG_COLOR + TOGGLE_DISABLED_KNOB_COLOR = rl.Color(88, 88, 88, 255) # Lighter Grey From 9c5acf61c047c81fd17bd02e9354225aabf5f7e8 Mon Sep 17 00:00:00 2001 From: nayan Date: Sat, 15 Nov 2025 09:29:07 -0500 Subject: [PATCH 03/18] SP Toggles --- selfdrive/ui/layouts/settings/developer.py | 3 + selfdrive/ui/layouts/settings/toggles.py | 3 + system/ui/sunnypilot/widgets/list_view.py | 97 ++++++++++++++++++++ system/ui/sunnypilot/widgets/toggle.py | 100 +++++++++++++++++++++ 4 files changed, 203 insertions(+) create mode 100644 system/ui/sunnypilot/widgets/list_view.py create mode 100644 system/ui/sunnypilot/widgets/toggle.py diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index 9ea1019f54..af00d98e73 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -9,6 +9,9 @@ from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.multilang import tr, tr_noop from openpilot.system.ui.widgets import DialogResult +if Params().get_bool("sunnypilot_ui"): + from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp as toggle_item + # Description constants DESCRIPTIONS = { 'enable_adb': tr_noop( diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index 3a1265e0fe..06005c5f84 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -9,6 +9,9 @@ from openpilot.system.ui.lib.multilang import tr, tr_noop from openpilot.system.ui.widgets import DialogResult from openpilot.selfdrive.ui.ui_state import ui_state +if Params().get_bool("sunnypilot_ui"): + from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp as toggle_item + PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants # Description constants diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py new file mode 100644 index 0000000000..9d3e973c63 --- /dev/null +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -0,0 +1,97 @@ +import pyray as rl + +from collections.abc import Callable +from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.sunnypilot.widgets.toggle import ToggleSP +from openpilot.system.ui.widgets.list_view import ListItem, ToggleAction, ItemAction + + +import openpilot.system.ui.sunnypilot.lib.styles as styles +style = styles.Default + + +class ToggleActionSP(ToggleAction): + def __init__(self, initial_state: bool = False, width: int = style.TOGGLE_WIDTH, enabled: bool | Callable[[], bool] = True, param: str | None = None): + ToggleAction.__init__(self, initial_state, width, enabled) + self.toggle = ToggleSP(initial_state=initial_state, param=param) + +class ListItemSP(ListItem): + def __init__(self, title: str = "", icon: str | None = None, description: str | Callable[[], str] | None = None, + description_visible: bool = False, callback: Callable | None = None, + action_item: ItemAction | None = None): + ListItem.__init__(self, title, icon, description, description_visible, callback, action_item) + + def get_right_item_rect(self, item_rect: rl.Rectangle) -> rl.Rectangle: + if not self.action_item: + return rl.Rectangle(0, 0, 0, 0) + + right_width = self.action_item.rect.width + if right_width == 0: # Full width action (like DualButtonAction) + return rl.Rectangle(item_rect.x + style.ITEM_PADDING, item_rect.y, + item_rect.width - (style.ITEM_PADDING * 2), style.ITEM_BASE_HEIGHT) + + action_width = self.action_item.rect.width + if isinstance(self.action_item, ToggleAction): + action_x = item_rect.x + else: + action_x = item_rect.x + item_rect.width - action_width + action_y = item_rect.y + return rl.Rectangle(action_x, action_y, action_width, style.ITEM_BASE_HEIGHT) + + def _render(self, _): + content_x = self._rect.x + style.ITEM_PADDING + text_x = content_x + left_action_item = isinstance(self.action_item, ToggleAction) + + if left_action_item: + left_rect = rl.Rectangle( + content_x, + self._rect.y + (style.ITEM_BASE_HEIGHT - style.TOGGLE_HEIGHT) // 2, + style.TOGGLE_WIDTH, + style.TOGGLE_HEIGHT + ) + text_x = left_rect.x + left_rect.width + style.ITEM_PADDING + + # Draw title + if self.title: + text_size = measure_text_cached(self._font, self.title, style.ITEM_TEXT_FONT_SIZE) + item_y = self._rect.y + (style.ITEM_BASE_HEIGHT - text_size.y) // 2 + rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), style.ITEM_TEXT_FONT_SIZE, 0, style.ITEM_TEXT_COLOR) + + # Render toggle and handle callback + if self.action_item.render(left_rect) and self.action_item.enabled: + if self.callback: + self.callback() + + else: + if self.title: + # Draw main text + text_size = measure_text_cached(self._font, self.title, style.ITEM_TEXT_FONT_SIZE) + item_y = self._rect.y + (style.ITEM_BASE_HEIGHT - text_size.y) // 2 + rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), style.ITEM_TEXT_FONT_SIZE, 0, style.ITEM_TEXT_COLOR) + + # Draw right item if present + if self.action_item: + right_rect = self.get_right_item_rect(self._rect) + right_rect.y = self._rect.y + if self.action_item.render(right_rect) and self.action_item.enabled: + # Right item was clicked/activated + if self.callback: + self.callback() + + # Draw description if visible + if self.description_visible: + content_width = int(self._rect.width - style.ITEM_PADDING * 2) + description_height = self._html_renderer.get_total_height(content_width) + description_rect = rl.Rectangle( + self._rect.x + style.ITEM_PADDING, + self._rect.y + style.ITEM_DESC_V_OFFSET, + content_width, + description_height + ) + self._html_renderer.render(description_rect) + +def toggle_item_sp(title: str, description: str | Callable[[], str] | None = None, initial_state: bool = False, + callback: Callable | None = None, icon: str = "", enabled: bool | Callable[[], bool] = True, param: str | None = None) -> ListItem: + action = ToggleActionSP(initial_state=initial_state, enabled=enabled, param=param) + return ListItemSP(title=title, description=description, action_item=action, icon=icon, callback=callback) diff --git a/system/ui/sunnypilot/widgets/toggle.py b/system/ui/sunnypilot/widgets/toggle.py new file mode 100644 index 0000000000..a62e51d758 --- /dev/null +++ b/system/ui/sunnypilot/widgets/toggle.py @@ -0,0 +1,100 @@ +import pyray as rl +from openpilot.common.params import Params +from openpilot.system.ui.lib.application import MousePos +from openpilot.system.ui.widgets.toggle import Toggle +import openpilot.system.ui.sunnypilot.lib.styles as styles + +style = styles.Default + +class ToggleSP(Toggle): + def __init__(self, initial_state=False, param: str | None = None): + self.param_key = param + self.params = Params() + if self.param_key: + initial_state = self.params.get_bool(self.param_key) + Toggle.__init__(self, initial_state) + + def _handle_mouse_release(self, mouse_pos: MousePos): + super()._handle_mouse_release(mouse_pos) + if self._enabled and self.param_key: + self.params.put_bool(self.param_key, self._state) + + def _render(self, rect: rl.Rectangle): + self.update() + if self._enabled: + bg_color = self._blend_color(style.TOGGLE_OFF_COLOR, style.TOGGLE_ON_COLOR, self._progress) + knob_color = style.TOGGLE_KNOB_COLOR + else: + bg_color = self._blend_color(style.TOGGLE_DISABLED_OFF_COLOR, style.TOGGLE_DISABLED_ON_COLOR, self._progress) + knob_color = style.TOGGLE_DISABLED_KNOB_COLOR + + # Draw background + bg_rect = rl.Rectangle(self._rect.x, self._rect.y, style.TOGGLE_WIDTH, style.TOGGLE_BG_HEIGHT) + + # Draw outline first + outline_color = style.TOGGLE_ON_COLOR + if not self._enabled: + # Use a more subtle color for disabled state + outline_color = rl.Color(outline_color.r // 2, outline_color.g // 2, outline_color.b // 2, 255) + + # Draw outline by drawing a slightly larger rounded rectangle behind the background + outline_rect = rl.Rectangle(bg_rect.x - 2, bg_rect.y - 2, bg_rect.width + 4, bg_rect.height + 4) + rl.draw_rectangle_rounded(outline_rect, 1.0, 10, outline_color) + + # Draw actual background + rl.draw_rectangle_rounded(bg_rect, 1.0, 10, bg_color) + + # Draw knob to sit inside the background + knob_padding = 5 + knob_radius = style.TOGGLE_BG_HEIGHT / 2 - knob_padding + + left_edge = bg_rect.x + knob_padding + right_edge = bg_rect.x + bg_rect.width - knob_padding + + knob_travel_distance = right_edge - left_edge - 2 * knob_radius + min_knob_x = left_edge + knob_radius + knob_x = min_knob_x + knob_travel_distance * self._progress + knob_y = self._rect.y + style.TOGGLE_BG_HEIGHT / 2 + + rl.draw_circle(int(knob_x), int(knob_y), knob_radius, knob_color) + + symbol_size = knob_radius / 2 + + if self._state and (self._enabled or self._progress > 0.5): + # Draw checkmark when toggle is ON + start_x = knob_x - symbol_size * 0.8 + start_y = knob_y + mid_x = knob_x - symbol_size * 0.1 + mid_y = knob_y + symbol_size * 0.6 + end_x = knob_x + symbol_size * 0.8 + end_y = knob_y - symbol_size * 0.5 + + rl.draw_line_ex( + rl.Vector2(int(start_x), int(start_y)), + rl.Vector2(int(mid_x), int(mid_y)), + 3, + style.TOGGLE_ON_COLOR + ) + rl.draw_line_ex( + rl.Vector2(int(mid_x), int(mid_y)), + rl.Vector2(int(end_x), int(end_y)), + 3, + style.TOGGLE_ON_COLOR + ) + else: + # Draw X when toggle is OFF + x_size_factor = 0.65 + x_offset = symbol_size * x_size_factor + + rl.draw_line_ex( + rl.Vector2(int(knob_x - x_offset), int(knob_y - x_offset)), + rl.Vector2(int(knob_x + x_offset), int(knob_y + x_offset)), + 3, + style.TOGGLE_OFF_COLOR + ) + rl.draw_line_ex( + rl.Vector2(int(knob_x + x_offset), int(knob_y - x_offset)), + rl.Vector2(int(knob_x - x_offset), int(knob_y + x_offset)), + 3, + style.TOGGLE_OFF_COLOR + ) From 0c37a385967c201d7db6e23ca3506bc21a22d698 Mon Sep 17 00:00:00 2001 From: nayan Date: Sat, 15 Nov 2025 09:42:12 -0500 Subject: [PATCH 04/18] Lint --- system/ui/sunnypilot/widgets/list_view.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index 9d3e973c63..c7043db531 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -16,7 +16,7 @@ class ToggleActionSP(ToggleAction): self.toggle = ToggleSP(initial_state=initial_state, param=param) class ListItemSP(ListItem): - def __init__(self, title: str = "", icon: str | None = None, description: str | Callable[[], str] | None = None, + 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): ListItem.__init__(self, title, icon, description, description_visible, callback, action_item) @@ -91,7 +91,7 @@ class ListItemSP(ListItem): ) self._html_renderer.render(description_rect) -def toggle_item_sp(title: str, description: str | Callable[[], str] | None = None, initial_state: bool = False, +def toggle_item_sp(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, param: str | None = None) -> ListItem: action = ToggleActionSP(initial_state=initial_state, enabled=enabled, param=param) return ListItemSP(title=title, description=description, action_item=action, icon=icon, callback=callback) From 3946e643f68fdb5784a2b42839162903a5f4a592 Mon Sep 17 00:00:00 2001 From: nayan Date: Sat, 15 Nov 2025 20:24:20 -0500 Subject: [PATCH 05/18] optimizations --- system/ui/sunnypilot/lib/styles.py | 31 +++++++++++--------- system/ui/sunnypilot/widgets/list_view.py | 5 +--- system/ui/sunnypilot/widgets/toggle.py | 35 ++++++++++------------- 3 files changed, 34 insertions(+), 37 deletions(-) diff --git a/system/ui/sunnypilot/lib/styles.py b/system/ui/sunnypilot/lib/styles.py index 851a52817d..28d50f48d7 100644 --- a/system/ui/sunnypilot/lib/styles.py +++ b/system/ui/sunnypilot/lib/styles.py @@ -2,19 +2,7 @@ import pyray as rl from dataclasses import dataclass @dataclass -class Default: - # Base Colors - BASE_BG_COLOR = rl.Color(57, 57, 57, 255) # Grey - ON_BG_COLOR = rl.Color(28, 101, 186, 255) # Blue - OFF_BG_COLOR = BASE_BG_COLOR - ON_HOVER_BG_COLOR = rl.Color(17, 78, 150, 255) # Dark Blue - OFF_HOVER_BG_COLOR = rl.Color(21, 21, 21, 255) # Dark gray - DISABLED_ON_BG_COLOR = rl.Color(37, 70, 107, 255) # Dull Blue - DISABLED_OFF_BG_COLOR = rl.Color(39, 39, 39, 255) # Grey - ITEM_TEXT_COLOR = rl.WHITE - ITEM_DISABLED_TEXT_COLOR = rl.Color(88, 88, 88, 255) - ITEM_DESC_TEXT_COLOR = rl.Color(128, 128, 128, 255) - +class Base: # Widget/Control Base Dimensions ITEM_BASE_HEIGHT = 170 ITEM_PADDING = 20 @@ -27,9 +15,26 @@ class Default: TOGGLE_WIDTH = int(TOGGLE_HEIGHT * 1.75) TOGGLE_BG_HEIGHT = TOGGLE_HEIGHT - 20 +@dataclass +class SP_Default(Base): + # Base Colors + BASE_BG_COLOR = rl.Color(57, 57, 57, 255) # Grey + ON_BG_COLOR = rl.Color(28, 101, 186, 255) # Blue + OFF_BG_COLOR = BASE_BG_COLOR + ON_HOVER_BG_COLOR = rl.Color(17, 78, 150, 255) # Dark Blue + OFF_HOVER_BG_COLOR = rl.Color(21, 21, 21, 255) # Dark gray + DISABLED_ON_BG_COLOR = rl.Color(37, 70, 107, 255) # Dull Blue + DISABLED_OFF_BG_COLOR = rl.Color(39, 39, 39, 255) # Grey + ITEM_TEXT_COLOR = rl.WHITE + ITEM_DISABLED_TEXT_COLOR = rl.Color(88, 88, 88, 255) + ITEM_DESC_TEXT_COLOR = rl.Color(128, 128, 128, 255) + + # Toggle Control TOGGLE_ON_COLOR = ON_BG_COLOR TOGGLE_OFF_COLOR = OFF_BG_COLOR TOGGLE_KNOB_COLOR = rl.WHITE TOGGLE_DISABLED_ON_COLOR = DISABLED_ON_BG_COLOR TOGGLE_DISABLED_OFF_COLOR = DISABLED_OFF_BG_COLOR TOGGLE_DISABLED_KNOB_COLOR = rl.Color(88, 88, 88, 255) # Lighter Grey + +style = SP_Default diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index c7043db531..6020ab71cb 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -4,10 +4,7 @@ from collections.abc import Callable from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.sunnypilot.widgets.toggle import ToggleSP from openpilot.system.ui.widgets.list_view import ListItem, ToggleAction, ItemAction - - -import openpilot.system.ui.sunnypilot.lib.styles as styles -style = styles.Default +from openpilot.system.ui.sunnypilot.lib.styles import style class ToggleActionSP(ToggleAction): diff --git a/system/ui/sunnypilot/widgets/toggle.py b/system/ui/sunnypilot/widgets/toggle.py index a62e51d758..7d3847d3f0 100644 --- a/system/ui/sunnypilot/widgets/toggle.py +++ b/system/ui/sunnypilot/widgets/toggle.py @@ -2,9 +2,11 @@ import pyray as rl from openpilot.common.params import Params from openpilot.system.ui.lib.application import MousePos from openpilot.system.ui.widgets.toggle import Toggle -import openpilot.system.ui.sunnypilot.lib.styles as styles +from openpilot.system.ui.sunnypilot.lib.styles import style -style = styles.Default +KNOB_PADDING = 5 +KNOB_RADIUS = style.TOGGLE_BG_HEIGHT / 2 - KNOB_PADDING +SYMBOL_SIZE = KNOB_RADIUS / 2 class ToggleSP(Toggle): def __init__(self, initial_state=False, param: str | None = None): @@ -44,30 +46,24 @@ class ToggleSP(Toggle): # Draw actual background rl.draw_rectangle_rounded(bg_rect, 1.0, 10, bg_color) - # Draw knob to sit inside the background - knob_padding = 5 - knob_radius = style.TOGGLE_BG_HEIGHT / 2 - knob_padding + left_edge = bg_rect.x + KNOB_PADDING + right_edge = bg_rect.x + bg_rect.width - KNOB_PADDING - left_edge = bg_rect.x + knob_padding - right_edge = bg_rect.x + bg_rect.width - knob_padding - - knob_travel_distance = right_edge - left_edge - 2 * knob_radius - min_knob_x = left_edge + knob_radius + knob_travel_distance = right_edge - left_edge - 2 * KNOB_RADIUS + min_knob_x = left_edge + KNOB_RADIUS knob_x = min_knob_x + knob_travel_distance * self._progress knob_y = self._rect.y + style.TOGGLE_BG_HEIGHT / 2 - rl.draw_circle(int(knob_x), int(knob_y), knob_radius, knob_color) - - symbol_size = knob_radius / 2 + rl.draw_circle(int(knob_x), int(knob_y), KNOB_RADIUS, knob_color) if self._state and (self._enabled or self._progress > 0.5): # Draw checkmark when toggle is ON - start_x = knob_x - symbol_size * 0.8 + start_x = knob_x - SYMBOL_SIZE * 0.8 start_y = knob_y - mid_x = knob_x - symbol_size * 0.1 - mid_y = knob_y + symbol_size * 0.6 - end_x = knob_x + symbol_size * 0.8 - end_y = knob_y - symbol_size * 0.5 + mid_x = knob_x - SYMBOL_SIZE * 0.1 + mid_y = knob_y + SYMBOL_SIZE * 0.6 + end_x = knob_x + SYMBOL_SIZE * 0.8 + end_y = knob_y - SYMBOL_SIZE * 0.5 rl.draw_line_ex( rl.Vector2(int(start_x), int(start_y)), @@ -83,8 +79,7 @@ class ToggleSP(Toggle): ) else: # Draw X when toggle is OFF - x_size_factor = 0.65 - x_offset = symbol_size * x_size_factor + x_offset = SYMBOL_SIZE * 0.65 rl.draw_line_ex( rl.Vector2(int(knob_x - x_offset), int(knob_y - x_offset)), From 362e9ce04becc1ef1d16d2f23daa01cb2d5303c4 Mon Sep 17 00:00:00 2001 From: nayan Date: Sun, 16 Nov 2025 09:53:28 -0500 Subject: [PATCH 06/18] sp raylib preview --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index f36ad1badb..009c940d1e 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -292,6 +292,7 @@ def create_screenshots(): with OpenpilotPrefix(): params = Params() params.put("DongleId", "123456789012345") + params.put_bool("sunnypilot_ui", True) # Set branch name params.put("UpdaterCurrentDescription", VERSION) From e4e10d4b87d0bdf4fe2e766122935e5ee3706a5b Mon Sep 17 00:00:00 2001 From: nayan Date: Sun, 16 Nov 2025 11:15:22 -0500 Subject: [PATCH 07/18] fix callback --- system/ui/sunnypilot/widgets/list_view.py | 9 +++++---- system/ui/sunnypilot/widgets/toggle.py | 5 +++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index 6020ab71cb..2260a0ba4c 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -8,9 +8,10 @@ from openpilot.system.ui.sunnypilot.lib.styles import style class ToggleActionSP(ToggleAction): - def __init__(self, initial_state: bool = False, width: int = style.TOGGLE_WIDTH, enabled: bool | Callable[[], bool] = True, param: str | None = None): - ToggleAction.__init__(self, initial_state, width, enabled) - self.toggle = ToggleSP(initial_state=initial_state, param=param) + def __init__(self, initial_state: bool = False, width: int = style.TOGGLE_WIDTH, enabled: bool | Callable[[], bool] = True, + callback: Callable[[bool], None] | None = None, param: str | None = None): + ToggleAction.__init__(self, initial_state, width, enabled, callback) + self.toggle = ToggleSP(initial_state=initial_state, callback=callback, param=param) class ListItemSP(ListItem): def __init__(self, title: str | Callable[[], str] = "", icon: str | None = None, description: str | Callable[[], str] | None = None, @@ -90,5 +91,5 @@ class ListItemSP(ListItem): def toggle_item_sp(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, param: str | None = None) -> ListItem: - action = ToggleActionSP(initial_state=initial_state, enabled=enabled, param=param) + action = ToggleActionSP(initial_state=initial_state, enabled=enabled, callback=callback, param=param) return ListItemSP(title=title, description=description, action_item=action, icon=icon, callback=callback) diff --git a/system/ui/sunnypilot/widgets/toggle.py b/system/ui/sunnypilot/widgets/toggle.py index 7d3847d3f0..84e3f9b4f2 100644 --- a/system/ui/sunnypilot/widgets/toggle.py +++ b/system/ui/sunnypilot/widgets/toggle.py @@ -1,4 +1,5 @@ import pyray as rl +from collections.abc import Callable from openpilot.common.params import Params from openpilot.system.ui.lib.application import MousePos from openpilot.system.ui.widgets.toggle import Toggle @@ -9,12 +10,12 @@ KNOB_RADIUS = style.TOGGLE_BG_HEIGHT / 2 - KNOB_PADDING SYMBOL_SIZE = KNOB_RADIUS / 2 class ToggleSP(Toggle): - def __init__(self, initial_state=False, param: str | None = None): + def __init__(self, initial_state=False, callback: Callable[[bool], None] | None = None, param: str | None = None): self.param_key = param self.params = Params() if self.param_key: initial_state = self.params.get_bool(self.param_key) - Toggle.__init__(self, initial_state) + Toggle.__init__(self, initial_state, callback) def _handle_mouse_release(self, mouse_pos: MousePos): super()._handle_mouse_release(mouse_pos) From 423a7d2ed0249e12743c2b36e1053d5a446f8d33 Mon Sep 17 00:00:00 2001 From: nayan Date: Sun, 16 Nov 2025 11:15:28 -0500 Subject: [PATCH 08/18] fix ui preview --- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 009c940d1e..a26c27bcb9 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -148,7 +148,7 @@ def setup_experimental_mode_description(click, pm: PubMaster): def setup_openpilot_long_confirmation_dialog(click, pm: PubMaster): setup_settings_developer(click, pm) - click(2000, 960) # toggle openpilot longitudinal control + click(650, 960) # toggle openpilot longitudinal control def setup_onroad(click, pm: PubMaster): From eb94abaa14a355d65a281b7225180c144677d5fd Mon Sep 17 00:00:00 2001 From: nayan Date: Wed, 19 Nov 2025 23:44:05 -0500 Subject: [PATCH 09/18] better padding --- system/ui/sunnypilot/lib/styles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/sunnypilot/lib/styles.py b/system/ui/sunnypilot/lib/styles.py index 28d50f48d7..2c694a1f38 100644 --- a/system/ui/sunnypilot/lib/styles.py +++ b/system/ui/sunnypilot/lib/styles.py @@ -5,7 +5,7 @@ from dataclasses import dataclass class Base: # Widget/Control Base Dimensions ITEM_BASE_HEIGHT = 170 - ITEM_PADDING = 20 + ITEM_PADDING = 30 ITEM_TEXT_FONT_SIZE = 50 ITEM_DESC_FONT_SIZE = 40 ITEM_DESC_V_OFFSET = 150 From 1e0564b4844acafbb733bfb7ae55b4ed0c93dd07 Mon Sep 17 00:00:00 2001 From: nayan Date: Thu, 20 Nov 2025 08:05:20 -0500 Subject: [PATCH 10/18] this --- system/ui/sunnypilot/lib/styles.py | 2 +- system/ui/sunnypilot/widgets/list_view.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/system/ui/sunnypilot/lib/styles.py b/system/ui/sunnypilot/lib/styles.py index 2c694a1f38..28d50f48d7 100644 --- a/system/ui/sunnypilot/lib/styles.py +++ b/system/ui/sunnypilot/lib/styles.py @@ -5,7 +5,7 @@ from dataclasses import dataclass class Base: # Widget/Control Base Dimensions ITEM_BASE_HEIGHT = 170 - ITEM_PADDING = 30 + ITEM_PADDING = 20 ITEM_TEXT_FONT_SIZE = 50 ITEM_DESC_FONT_SIZE = 40 ITEM_DESC_V_OFFSET = 150 diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index 2260a0ba4c..7e118f7506 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -48,7 +48,7 @@ class ListItemSP(ListItem): style.TOGGLE_WIDTH, style.TOGGLE_HEIGHT ) - text_x = left_rect.x + left_rect.width + style.ITEM_PADDING + text_x = left_rect.x + left_rect.width + style.ITEM_PADDING * 1.5 # Draw title if self.title: From 5b03369a8f1f3450a82097da369302bdddf10fc9 Mon Sep 17 00:00:00 2001 From: nayan Date: Thu, 20 Nov 2025 17:56:26 -0500 Subject: [PATCH 11/18] listitem -> listitemsp --- system/ui/sunnypilot/widgets/list_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index 7e118f7506..219118298e 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -90,6 +90,6 @@ class ListItemSP(ListItem): self._html_renderer.render(description_rect) def toggle_item_sp(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, param: str | None = None) -> ListItem: + callback: Callable | None = None, icon: str = "", enabled: bool | Callable[[], bool] = True, param: str | None = None) -> ListItemSP: action = ToggleActionSP(initial_state=initial_state, enabled=enabled, callback=callback, param=param) return ListItemSP(title=title, description=description, action_item=action, icon=icon, callback=callback) From 7032e4a9727783136c14c7c38ea11092e25a19be Mon Sep 17 00:00:00 2001 From: nayan Date: Thu, 20 Nov 2025 18:00:44 -0500 Subject: [PATCH 12/18] add show_description method --- system/ui/sunnypilot/widgets/list_view.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index 219118298e..86dbde2797 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -19,6 +19,9 @@ class ListItemSP(ListItem): action_item: ItemAction | None = None): ListItem.__init__(self, title, icon, description, description_visible, callback, action_item) + def show_description(self, show: bool): + self._set_description_visible(show) + def get_right_item_rect(self, item_rect: rl.Rectangle) -> rl.Rectangle: if not self.action_item: return rl.Rectangle(0, 0, 0, 0) From e6f5aae2464cea37804eeef87c0aa7793bc6fa89 Mon Sep 17 00:00:00 2001 From: nayan Date: Thu, 20 Nov 2025 18:05:12 -0500 Subject: [PATCH 13/18] =?UTF-8?q?remove=20padding=20from=20line=20separato?= =?UTF-8?q?r.=20like,=20WHY=3F=20=F0=9F=98=A9=F0=9F=98=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- system/ui/widgets/scroller.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index a843010d56..eec75c8592 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -17,8 +17,8 @@ class LineSeparator(Widget): self._rect.width = parent_rect.width def _render(self, _): - rl.draw_line(int(self._rect.x) + LINE_PADDING, int(self._rect.y), - int(self._rect.x + self._rect.width) - LINE_PADDING, int(self._rect.y), + rl.draw_line(int(self._rect.x), int(self._rect.y), + int(self._rect.x + self._rect.width), int(self._rect.y), LINE_COLOR) From 0ba5cbea914ed437f760e8644ac6800aacbe5a4a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 21 Nov 2025 15:55:18 -0500 Subject: [PATCH 14/18] tools: add new Pythons configs to compile & run big/small UIs (#1500) * init * tools: add new Pythons configs to compile & build big/small UIs * Revert "init" This reverts commit 2e0704ace51b5a42a5d4a25664e5f46103f40e3f. * change --- .run/Build_BIG_UI.run.xml | 26 ++++++++++++++++++++++++++ .run/Build_SMALL_UI.run.xml | 23 +++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 .run/Build_BIG_UI.run.xml create mode 100644 .run/Build_SMALL_UI.run.xml diff --git a/.run/Build_BIG_UI.run.xml b/.run/Build_BIG_UI.run.xml new file mode 100644 index 0000000000..b58b1bf1b7 --- /dev/null +++ b/.run/Build_BIG_UI.run.xml @@ -0,0 +1,26 @@ + + + + + \ No newline at end of file diff --git a/.run/Build_SMALL_UI.run.xml b/.run/Build_SMALL_UI.run.xml new file mode 100644 index 0000000000..6c231c83f8 --- /dev/null +++ b/.run/Build_SMALL_UI.run.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file From 91f2bf345956e0710e75a4f94a440d0986a0dc1d Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 21 Nov 2025 16:23:01 -0500 Subject: [PATCH 15/18] ui: `GuiApplicationExt` --- system/ui/lib/application.py | 4 +++- system/ui/sunnypilot/lib/application.py | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 system/ui/sunnypilot/lib/application.py diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index e3370a5f74..7271127838 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -19,6 +19,8 @@ from openpilot.system.hardware import HARDWARE, PC from openpilot.system.ui.lib.multilang import multilang from openpilot.common.realtime import Ratekeeper +from openpilot.system.ui.sunnypilot.lib.application import GuiApplicationExt + _DEFAULT_FPS = int(os.getenv("FPS", {'tizi': 20}.get(HARDWARE.get_device_type(), 60))) FPS_LOG_INTERVAL = 5 # Seconds between logging FPS drops FPS_DROP_THRESHOLD = 0.9 # FPS drop threshold for triggering a warning @@ -186,7 +188,7 @@ class MouseState: self._prev_mouse_event[slot] = ev -class GuiApplication: +class GuiApplication(GuiApplicationExt): def __init__(self, width: int | None = None, height: int | None = None): self._fonts: dict[FontWeight, rl.Font] = {} self._width = width if width is not None else GuiApplication._default_width() diff --git a/system/ui/sunnypilot/lib/application.py b/system/ui/sunnypilot/lib/application.py new file mode 100644 index 0000000000..b39bf31e9d --- /dev/null +++ b/system/ui/sunnypilot/lib/application.py @@ -0,0 +1,15 @@ +""" +Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. + +This file is part of sunnypilot and is licensed under the MIT License. +See the LICENSE.md file in the root directory for more details. +""" +import os + +SUNNYPILOT_UI = os.getenv("SUNNYPILOT_UI", "1") == "1" + + +class GuiApplicationExt: + @staticmethod + def sunnypilot_ui() -> bool: + return SUNNYPILOT_UI From a33497ed19a413931ea4053c4903f15f2ae71d50 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 21 Nov 2025 16:42:49 -0500 Subject: [PATCH 16/18] add to readme --- system/ui/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/system/ui/README.md b/system/ui/README.md index f81cb5573a..796d298626 100644 --- a/system/ui/README.md +++ b/system/ui/README.md @@ -10,6 +10,7 @@ Quick start: * set `BURN_IN=1` to get a burn-in heatmap version of the UI * set `GRID=50` to show a 50-pixel alignment grid overlay * set `MAGIC_DEBUG=1` to show every dropped frames (only on device) +* set `SUNNYPILOT_UI=0` to run the stock UI instead of the sunnypilot UI * https://www.raylib.com/cheatsheet/cheatsheet.html * https://electronstudio.github.io/raylib-python-cffi/README.html#quickstart From 4f13a0f77520d3e04a93f6ae68b00ab26f243fb5 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 21 Nov 2025 17:23:53 -0500 Subject: [PATCH 17/18] ci: fix upstream merge conflicts with prebuilts (#1503) --- SConstruct | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SConstruct b/SConstruct index 48dd044709..f5d5ec6fb8 100644 --- a/SConstruct +++ b/SConstruct @@ -178,7 +178,8 @@ Export('envCython', 'np_version') Export('env', 'arch') # Setup cache dir -cache_dir = '/data/scons_cache' if arch == "larch64" else '/tmp/scons_cache' +default_cache_dir = '/data/scons_cache' if arch == "larch64" else '/tmp/scons_cache' +cache_dir = ARGUMENTS.get('cache_dir', default_cache_dir) CacheDir(cache_dir) Clean(["."], cache_dir) From ed775185f2434f2383bf68887805be3260c24eef Mon Sep 17 00:00:00 2001 From: nayan Date: Fri, 21 Nov 2025 17:49:27 -0500 Subject: [PATCH 18/18] use gui_app.sunnypilot_ui() --- common/params_keys.h | 1 - selfdrive/ui/layouts/settings/developer.py | 2 +- selfdrive/ui/layouts/settings/toggles.py | 2 +- selfdrive/ui/tests/test_ui/raylib_screenshots.py | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index fe08062987..38578d7ddc 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -173,7 +173,6 @@ inline static std::unordered_map keys = { {"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}}, {"ShowTurnSignals", {PERSISTENT | BACKUP, BOOL, "0"}}, {"StandstillTimer", {PERSISTENT | BACKUP, BOOL, "0"}}, - {"sunnypilot_ui", {PERSISTENT, BOOL, "1"}}, {"TrueVEgoUI", {PERSISTENT | BACKUP, BOOL, "0"}}, // MADS params diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index d133a992ad..f47cd310ca 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -9,7 +9,7 @@ from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.multilang import tr, tr_noop from openpilot.system.ui.widgets import DialogResult -if Params().get_bool("sunnypilot_ui"): +if gui_app.sunnypilot_ui(): from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp as toggle_item # Description constants diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index 86c27aa7c8..b7f176e24f 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -9,7 +9,7 @@ from openpilot.system.ui.lib.multilang import tr, tr_noop from openpilot.system.ui.widgets import DialogResult from openpilot.selfdrive.ui.ui_state import ui_state -if Params().get_bool("sunnypilot_ui"): +if gui_app.sunnypilot_ui(): from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp as toggle_item PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py index 3cc5b01541..d3de616cc0 100755 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py @@ -294,7 +294,6 @@ def create_screenshots(): with OpenpilotPrefix(): params = Params() params.put("DongleId", "123456789012345") - params.put_bool("sunnypilot_ui", True) # Set branch name params.put("UpdaterCurrentDescription", VERSION)