From c4f8055da2f7fc314e3b23b11d4ff6275ab9adb2 Mon Sep 17 00:00:00 2001 From: nayan Date: Tue, 1 Jul 2025 13:45:32 -0400 Subject: [PATCH] update toggle & list view --- system/ui/lib/list_view.py | 13 +++ system/ui/sunnypilot/lib/list_view.py | 128 +++++++++++++++++--------- system/ui/sunnypilot/lib/styles.py | 20 ++++ system/ui/sunnypilot/lib/toggle.py | 26 +++--- 4 files changed, 131 insertions(+), 56 deletions(-) create mode 100644 system/ui/sunnypilot/lib/styles.py diff --git a/system/ui/lib/list_view.py b/system/ui/lib/list_view.py index f5581be23e..2d423adb98 100644 --- a/system/ui/lib/list_view.py +++ b/system/ui/lib/list_view.py @@ -8,6 +8,7 @@ from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.lib.button import gui_button, ButtonStyle from openpilot.system.ui.lib.toggle import Toggle, WIDTH as TOGGLE_WIDTH, HEIGHT as TOGGLE_HEIGHT from openpilot.system.ui.lib.widget import Widget +from openpilot.system.ui.sunnypilot.lib.list_view import ListItemSP ITEM_BASE_WIDTH = 600 ITEM_BASE_HEIGHT = 170 @@ -254,6 +255,10 @@ class ListItem(Widget): self._rect.y >= (self._parent_rect.y + self._parent_rect.height)): return + if ListItemSP._render(self, isinstance(self.action_item, ToggleAction)): + return + + content_x = self._rect.x + ITEM_PADDING text_x = content_x @@ -298,6 +303,10 @@ class ListItem(Widget): if not self.is_visible: return 0 + sp_height = ListItemSP.get_item_height(self) + if sp_height: + return sp_height + current_description = self.get_description() if self.description_visible and current_description: if ( @@ -328,6 +337,10 @@ class ListItem(Widget): return rl.Rectangle(item_rect.x + ITEM_PADDING, item_rect.y, item_rect.width - (ITEM_PADDING * 2), ITEM_BASE_HEIGHT) + action_rect = ListItemSP.get_action_item_rect(self, item_rect, isinstance(self.action_item, ToggleAction)) + if action_rect: + return action_rect + right_x = item_rect.x + item_rect.width - right_width right_y = item_rect.y return rl.Rectangle(right_x, right_y, right_width, ITEM_BASE_HEIGHT) diff --git a/system/ui/sunnypilot/lib/list_view.py b/system/ui/sunnypilot/lib/list_view.py index 08f68b8fed..0c0ae7bb11 100644 --- a/system/ui/sunnypilot/lib/list_view.py +++ b/system/ui/sunnypilot/lib/list_view.py @@ -1,59 +1,105 @@ import pyray as rl - -import openpilot.system.ui.lib.list_view as ListItem -from openpilot.system.ui.lib.widget import Widget +from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.lib.text_measure import measure_text_cached +import openpilot.system.ui.sunnypilot.lib.styles as styles + +style = styles.Default -LINE_PADDING = 40 ITEM_BASE_HEIGHT = 170 ITEM_PADDING = 20 ITEM_TEXT_FONT_SIZE = 50 -ITEM_TEXT_COLOR = rl.WHITE -ITEM_DESC_TEXT_COLOR = rl.Color(128, 128, 128, 255) +ITEM_TEXT_COLOR = style.ITEM_TEXT_COLOR +ITEM_DESC_TEXT_COLOR = style.ITEM_DESC_TEXT_COLOR ITEM_DESC_FONT_SIZE = 40 -ITEM_DESC_V_OFFSET = 140 -ICON_SIZE = 80 +ITEM_DESC_V_OFFSET = 150 +TOGGLE_WIDTH = 150 BUTTON_WIDTH = 250 BUTTON_HEIGHT = 100 -BUTTON_FONT_SIZE = 35 -class ListItemSP(Widget): +class ListItemSP: - def __init__(self): - super().__init__() + def get_item_height(self) -> float: + if not self.is_visible: + return 0 - def _render(self, rect: rl.Rectangle): - # Handle click on title/description area for toggling description - if self.description and rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT): - mouse_pos = rl.get_mouse_position() + total_width = self._rect.width - (2 * ITEM_PADDING) # Full width minus padding + max_width = total_width - (2 * ITEM_PADDING) - text_area_width = rect.width - self.get_action_width() - ITEM_PADDING - text_area_x = rect.x - if isinstance(self, ListItem.ToggleItem): - text_area_x = text_area_x + self.get_action_width() + ITEM_PADDING - text_area = rl.Rectangle(text_area_x, rect.y, text_area_width, rect.height) + current_description = self.get_description() + if self.description_visible and current_description: + if ( + not self._wrapped_description + or current_description != self._prev_description + or max_width != self._prev_max_width + ): + self._prev_max_width = max_width + self._prev_description = current_description - if rl.check_collision_point_rec(mouse_pos, text_area): - self.show_desc = not self.show_desc + wrapped_lines = wrap_text(self._font, current_description, ITEM_DESC_FONT_SIZE, max_width) + self._wrapped_description = "\n".join(wrapped_lines) + self._description_height = len(wrapped_lines) * ITEM_DESC_FONT_SIZE + 10 + return ITEM_BASE_HEIGHT + self._description_height - (ITEM_BASE_HEIGHT - ITEM_DESC_V_OFFSET) + ITEM_PADDING + return ITEM_BASE_HEIGHT - # Render title and description - x = rect.x + ITEM_PADDING + def get_action_item_rect(self, item_rect: rl.Rectangle, left_action_item: bool) -> rl.Rectangle: + action_width = self.action_item.rect.width + if left_action_item: + 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, ITEM_BASE_HEIGHT) + + def _render(self, left_action_item: bool): + content_x = self._rect.x + ITEM_PADDING + text_x = content_x + + if left_action_item: + left_rect = rl.Rectangle( + content_x, + self._rect.y + (ITEM_BASE_HEIGHT - BUTTON_HEIGHT) // 2, + TOGGLE_WIDTH, + BUTTON_HEIGHT + ) + text_x = left_rect.x + left_rect.width + ITEM_PADDING + + # Draw title + if self.title: + text_size = measure_text_cached(self._font, self.title, ITEM_TEXT_FONT_SIZE) + item_y = self._rect.y + (ITEM_BASE_HEIGHT - text_size.y) // 2 + rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), ITEM_TEXT_FONT_SIZE, 0, 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, ITEM_TEXT_FONT_SIZE) + item_y = self._rect.y + (ITEM_BASE_HEIGHT - text_size.y) // 2 + rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), ITEM_TEXT_FONT_SIZE, 0, 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.show_desc and self._wrapped_description: - rl.draw_text_ex(self._font, self._wrapped_description, (x, rect.y + ITEM_DESC_V_OFFSET), - ITEM_DESC_FONT_SIZE, 0, ITEM_DESC_TEXT_COLOR) + current_description = self.get_description() + if self.description_visible and current_description and self._wrapped_description: + rl.draw_text_ex( + self._font, + self._wrapped_description, + rl.Vector2(content_x, self._rect.y + ITEM_DESC_V_OFFSET), + ITEM_DESC_FONT_SIZE, + 0, + ITEM_DESC_TEXT_COLOR, + ) - # Render action if needed - action_width = self.get_action_width() - if isinstance(self, ListItem.ToggleItem): - action_rect = rl.Rectangle(rect.x + ITEM_PADDING, rect.y, action_width, ITEM_BASE_HEIGHT) - x += action_width + ITEM_PADDING - else: - action_rect = rl.Rectangle(rect.x + rect.width - action_width, rect.y, action_width, ITEM_BASE_HEIGHT) - - text_size = measure_text_cached(self._font, self.title, ITEM_TEXT_FONT_SIZE) - title_y = rect.y + (ITEM_BASE_HEIGHT - text_size.y) // 2 - rl.draw_text_ex(self._font, self.title, (x, title_y), ITEM_TEXT_FONT_SIZE, 0, ITEM_TEXT_COLOR) - - return action_rect + return True diff --git a/system/ui/sunnypilot/lib/styles.py b/system/ui/sunnypilot/lib/styles.py new file mode 100644 index 0000000000..2371f4e240 --- /dev/null +++ b/system/ui/sunnypilot/lib/styles.py @@ -0,0 +1,20 @@ +import pyray as rl +from dataclasses import dataclass + +@dataclass +class Default: + # Base Colors + ON_BG_COLOR = rl.Color(28, 101, 186, 255) # Blue + OFF_BG_COLOR = rl.Color(39, 39, 39, 255) # Grey + 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_DESC_TEXT_COLOR = rl.Color(128, 128, 128, 255) + + # Toggle Colors + 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 \ No newline at end of file diff --git a/system/ui/sunnypilot/lib/toggle.py b/system/ui/sunnypilot/lib/toggle.py index c709691ace..949234ce97 100644 --- a/system/ui/sunnypilot/lib/toggle.py +++ b/system/ui/sunnypilot/lib/toggle.py @@ -1,12 +1,8 @@ import pyray as rl import openpilot.system.ui.lib.toggle as ToggleOP +import openpilot.system.ui.sunnypilot.lib.styles as styles -ON_COLOR = rl.Color(28, 101, 186, 255) -OFF_COLOR = rl.Color(0x39, 0x39, 0x39, 255) -KNOB_COLOR = rl.WHITE -DISABLED_ON_COLOR = rl.Color(0x22, 0x77, 0x22, 255) # Dark green when disabled + on -DISABLED_OFF_COLOR = rl.Color(0x39, 0x39, 0x39, 255) -DISABLED_KNOB_COLOR = rl.Color(0x88, 0x88, 0x88, 255) +STYLE = styles.Default WIDTH, HEIGHT = 130, 80 BG_HEIGHT = 60 @@ -14,17 +10,17 @@ class ToggleSP: def _render(self, rect: rl.Rectangle): if self._enabled: - bg_color = ToggleOP.Toggle._blend_color(self, OFF_COLOR, ON_COLOR, self._progress) - knob_color = KNOB_COLOR + bg_color = ToggleOP.Toggle._blend_color(self, STYLE.TOGGLE_OFF_COLOR, STYLE.TOGGLE_ON_COLOR, self._progress) + knob_color = STYLE.TOGGLE_KNOB_COLOR else: - bg_color = ToggleOP.Toggle._blend_color(self, DISABLED_OFF_COLOR, DISABLED_ON_COLOR, self._progress) - knob_color = DISABLED_KNOB_COLOR + bg_color = ToggleOP.Toggle._blend_color(self, 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 + 5, self._rect.y + 10, WIDTH - 10, BG_HEIGHT) # Draw outline first - outline_color = ON_COLOR + 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) @@ -65,13 +61,13 @@ class ToggleSP: rl.Vector2(int(start_x), int(start_y)), rl.Vector2(int(mid_x), int(mid_y)), 3, - ON_COLOR + 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, - ON_COLOR + STYLE.TOGGLE_ON_COLOR ) else: # Draw X when toggle is OFF @@ -82,12 +78,12 @@ class ToggleSP: 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, - OFF_COLOR + 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, - OFF_COLOR + STYLE.TOGGLE_OFF_COLOR ) return True