diff --git a/selfdrive/ui/sunnypilot/layouts/settings/device.py b/selfdrive/ui/sunnypilot/layouts/settings/device.py index b00a70d1fe..f2c0e53862 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/device.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/device.py @@ -38,7 +38,7 @@ class DeviceLayoutSP(DeviceLayout): def _hide_item(self, item): return (item.title in {"Driver Camera", "Regulatory", "Review Training Guide", "Change Language"} - or (isinstance(item.action_item, DualButtonAction) and item.action_item.left_text == "Reboot")) + or (isinstance(item.action_item, DualButtonAction))) def _initialize_items(self): diff --git a/selfdrive/ui/sunnypilot/layouts/settings/settings.py b/selfdrive/ui/sunnypilot/layouts/settings/settings.py index 265ed86dd8..2a89a8ea3f 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/settings.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/settings.py @@ -8,9 +8,10 @@ from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout from openpilot.system.ui.lib.application import gui_app,MousePos +from openpilot.system.ui.lib.multilang import tr_noop from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.lib.text_measure import measure_text_cached -from openpilot.system.ui.widgets.network import WifiManagerUI +from openpilot.system.ui.widgets.network import NetworkUI from openpilot.system.ui.lib.wifi_manager import WifiManager from openpilot.system.ui.widgets import Widget from openpilot.selfdrive.ui.sunnypilot.layouts.settings.models import ModelsLayout @@ -60,21 +61,21 @@ class SettingsLayoutSP(OP.SettingsLayout): wifi_manager.set_active(False) self._panels = { - OP.PanelType.DEVICE: PanelInfo("Device", DeviceLayoutSP(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_home.png"), - OP.PanelType.NETWORK: PanelInfo("Network", WifiManagerUI(wifi_manager), icon="icons/network.png"), - OP.PanelType.SUNNYLINK: PanelInfo("sunnylink", SunnylinkLayout(), icon="icons/shell.png"), - OP.PanelType.TOGGLES: PanelInfo("Toggles", TogglesLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_toggle.png"), - OP.PanelType.SOFTWARE: PanelInfo("Software", SoftwareLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_software.png"), - OP.PanelType.MODELS: PanelInfo("Models", ModelsLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_models.png"), - OP.PanelType.STEERING: PanelInfo("Steering", SteeringLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_lateral.png"), - OP.PanelType.CRUISE: PanelInfo("Cruise", CruiseLayout(), icon="icons/speed_limit.png"), - OP.PanelType.VISUALS: PanelInfo("Visuals", VisualsLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_visuals.png"), - OP.PanelType.DISPLAY: PanelInfo("Display", DisplayLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_display.png"), - OP.PanelType.OSM: PanelInfo("OSM", OSMLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_map.png"), - OP.PanelType.TRIPS: PanelInfo("Trips", TripsLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_trips.png"), - OP.PanelType.VEHICLE: PanelInfo("Vehicle", VehicleLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_vehicle.png"), - OP.PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_firehose.png"), - OP.PanelType.DEVELOPER: PanelInfo("Developer", DeveloperLayout(), icon="icons/shell.png"), + OP.PanelType.DEVICE: PanelInfo(tr_noop("Device"), DeviceLayoutSP(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_home.png"), + OP.PanelType.NETWORK: PanelInfo(tr_noop("Network"), NetworkUI(wifi_manager), icon="icons/network.png"), + OP.PanelType.SUNNYLINK: PanelInfo(tr_noop("sunnylink"), SunnylinkLayout(), icon="icons/shell.png"), + OP.PanelType.TOGGLES: PanelInfo(tr_noop("Toggles"), TogglesLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_toggle.png"), + OP.PanelType.SOFTWARE: PanelInfo(tr_noop("Software"), SoftwareLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_software.png"), + OP.PanelType.MODELS: PanelInfo(tr_noop("Models"), ModelsLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_models.png"), + OP.PanelType.STEERING: PanelInfo(tr_noop("Steering"), SteeringLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_lateral.png"), + OP.PanelType.CRUISE: PanelInfo(tr_noop("Cruise"), CruiseLayout(), icon="icons/speed_limit.png"), + OP.PanelType.VISUALS: PanelInfo(tr_noop("Visuals"), VisualsLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_visuals.png"), + OP.PanelType.DISPLAY: PanelInfo(tr_noop("Display"), DisplayLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_display.png"), + OP.PanelType.OSM: PanelInfo(tr_noop("OSM"), OSMLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_map.png"), + OP.PanelType.TRIPS: PanelInfo(tr_noop("Trips"), TripsLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_trips.png"), + OP.PanelType.VEHICLE: PanelInfo(tr_noop("Vehicle"), VehicleLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_vehicle.png"), + OP.PanelType.FIREHOSE: PanelInfo(tr_noop("Firehose"), FirehoseLayout(), icon="../../sunnypilot/selfdrive/assets/offroad/icon_firehose.png"), + OP.PanelType.DEVELOPER: PanelInfo(tr_noop("Developer"), DeveloperLayout(), icon="icons/shell.png"), } def _create_nav_button(self, panel_type: OP.PanelType, panel_info: PanelInfo) -> Widget: @@ -108,7 +109,7 @@ class SettingsLayoutSP(OP.SettingsLayout): content_x, rect.y + (OP.NAV_BTN_HEIGHT - text_size.y) / 2 ) - rl.draw_text_ex(self.parent._font_medium, self.panel_info.name, text_pos, 65, 0, text_color) + rl.draw_text_ex(self.parent._font_medium, self.panel_info.name, text_pos, 55, 0, text_color) # Store button rect for click detection self.panel_info.button_rect = rect @@ -120,7 +121,7 @@ class SettingsLayoutSP(OP.SettingsLayout): # Close button close_btn_rect = rl.Rectangle( - rect.x + (rect.width - OP.CLOSE_BTN_SIZE) / 2, rect.y + 45, OP.CLOSE_BTN_SIZE, OP.CLOSE_BTN_SIZE + rect.x + (rect.width - OP.CLOSE_BTN_SIZE) / 2, rect.y + 60, OP.CLOSE_BTN_SIZE, OP.CLOSE_BTN_SIZE ) pressed = (rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and @@ -128,12 +129,21 @@ class SettingsLayoutSP(OP.SettingsLayout): close_color = OP.CLOSE_BTN_PRESSED if pressed else OP.CLOSE_BTN_COLOR rl.draw_rectangle_rounded(close_btn_rect, 1.0, 20, close_color) - close_text_size = measure_text_cached(self._font_medium, OP.SETTINGS_CLOSE_TEXT, 140) - close_text_pos = rl.Vector2( - close_btn_rect.x + (close_btn_rect.width - close_text_size.x) / 2, - close_btn_rect.y + (close_btn_rect.height - close_text_size.y) / 2, + icon_color = rl.Color(255, 255, 255, 255) if not pressed else rl.Color(220, 220, 220, 255) + icon_dest = rl.Rectangle( + close_btn_rect.x + (close_btn_rect.width - self._close_icon.width) / 2, + close_btn_rect.y + (close_btn_rect.height - self._close_icon.height) / 2, + self._close_icon.width, + self._close_icon.height, ) - rl.draw_text_ex(self._font_medium, OP.SETTINGS_CLOSE_TEXT, close_text_pos, 140, 0, OP.TEXT_SELECTED) + rl.draw_texture_pro( + self._close_icon, + rl.Rectangle(0, 0, self._close_icon.width, self._close_icon.height), + icon_dest, + rl.Vector2(0, 0), + 0, + icon_color, + ) # Store close button rect for click detection self._close_btn_rect = close_btn_rect diff --git a/system/ui/sunnypilot/lib/list_view.py b/system/ui/sunnypilot/lib/list_view.py index 78d8c0499e..89a45ff7c5 100644 --- a/system/ui/sunnypilot/lib/list_view.py +++ b/system/ui/sunnypilot/lib/list_view.py @@ -7,9 +7,10 @@ from collections.abc import Callable from openpilot.common.params import Params from openpilot.system.ui.sunnypilot.lib.toggle import ToggleSP -import openpilot.system.ui.sunnypilot.lib.styles as styles from openpilot.system.ui.sunnypilot.lib.option_control import OptionControlSP +from openpilot.system.ui.widgets.list_view import _resolve_value +import openpilot.system.ui.sunnypilot.lib.styles as styles style = styles.Default @@ -24,29 +25,6 @@ class ListItemSP(ListItem): action_item: ItemAction | None = None): ListItem.__init__(self, title, icon, description, description_visible, callback, action_item) - def get_item_height(self, font: rl.Font, max_width: int) -> float: - if not self.is_visible: - return 0 - - total_width = self._rect.width - (2 * style.ITEM_PADDING) # Full width minus padding - max_width = int(total_width - (2 * style.ITEM_PADDING)) - - 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 - - wrapped_lines = wrap_text(self._font, current_description, style.ITEM_DESC_FONT_SIZE, max_width) - self._wrapped_description = "\n".join(wrapped_lines) - self._description_height = len(wrapped_lines) * style.ITEM_DESC_FONT_SIZE + 10 - return style.ITEM_BASE_HEIGHT + self._description_height - (style.ITEM_BASE_HEIGHT - style.ITEM_DESC_V_OFFSET) + style.ITEM_PADDING - return style.ITEM_BASE_HEIGHT - def get_right_item_rect(self, item_rect: rl.Rectangle) -> rl.Rectangle: if not self.action_item: return rl.Rectangle(0, 0, 0, 0) @@ -106,16 +84,16 @@ class ListItemSP(ListItem): self.callback() # Draw description if visible - 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 + style.ITEM_DESC_V_OFFSET), - style.ITEM_DESC_FONT_SIZE, - 0, - style.ITEM_DESC_TEXT_COLOR, + 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) class MultipleButtonActionSP(MultipleButtonAction): def __init__(self, param: str | None, buttons: list[str], button_width: int, selected_index: int = 0, callback: Callable = None): @@ -130,14 +108,14 @@ class MultipleButtonActionSP(MultipleButtonAction): button_y = rect.y + (rect.height - style.BUTTON_HEIGHT) / 2 clicked = -1 - for i, text in enumerate(self.buttons): + for i, _text in enumerate(self.buttons): button_x = rect.x + i * (self.button_width + spacing) button_rect = rl.Rectangle(button_x, button_y, self.button_width, style.BUTTON_HEIGHT) # Check button state mouse_pos = rl.get_mouse_position() is_hovered = rl.check_collision_point_rec(mouse_pos, button_rect) - is_pressed = is_hovered and rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and self._is_pressed + is_pressed = is_hovered and rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and self.is_pressed is_selected = i == self.selected_button # Button colors @@ -154,13 +132,14 @@ class MultipleButtonActionSP(MultipleButtonAction): rl.draw_rectangle_rounded(button_rect, 1.0, 20, bg_color) # Draw text + text = _resolve_value(_text, "") text_size = measure_text_cached(self._font, text, 40) text_x = button_x + (self.button_width - text_size.x) / 2 text_y = button_y + (style.BUTTON_HEIGHT - text_size.y) / 2 rl.draw_text_ex(self._font, text, rl.Vector2(text_x, text_y), 40, 0, style.ITEM_TEXT_COLOR) # Handle click - if is_hovered and rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) and self._is_pressed: + if is_hovered and rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) and self.is_pressed: clicked = i if clicked >= 0: diff --git a/system/ui/sunnypilot/lib/option_control.py b/system/ui/sunnypilot/lib/option_control.py index 5413c08184..c2823b567a 100644 --- a/system/ui/sunnypilot/lib/option_control.py +++ b/system/ui/sunnypilot/lib/option_control.py @@ -21,15 +21,6 @@ INNER_PADDING = 10 TOP_PADDING = 25 class OptionControlSP(Widget): - """ - Raylib implementation of OptionControlSP widget for adjusting numeric values - with increment/decrement buttons and a label showing the current value. - - This widget provides a user interface component similar to the C++ OptionControlSP - from the sunnypilot project, allowing users to adjust numeric values within a - specified range. - """ - def __init__(self, param: str, min_value: int, max_value: int, value_change_step: int = 1, enabled: bool | Callable[[], bool] = True, on_value_changed: Callable[[int], None] | None = None,