From 859745ea8615817481ce8e5f25971f2f3c8f4a41 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Mon, 1 Dec 2025 14:31:37 -0800 Subject: [PATCH] ui: tree dialog improvements (#1537) * ui: highlight on pressed, and less indent * inherit MultiOptionDialog main buttons * align top level folders to the edge properly * lint * handle folder presses too --------- Co-authored-by: Jason Wen --- system/ui/sunnypilot/widgets/tree_dialog.py | 57 ++++++++------------- 1 file changed, 22 insertions(+), 35 deletions(-) diff --git a/system/ui/sunnypilot/widgets/tree_dialog.py b/system/ui/sunnypilot/widgets/tree_dialog.py index 6458ebf001..5685a6c27e 100644 --- a/system/ui/sunnypilot/widgets/tree_dialog.py +++ b/system/ui/sunnypilot/widgets/tree_dialog.py @@ -11,8 +11,8 @@ from openpilot.common.params import Params from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import DialogResult -from openpilot.system.ui.widgets.button import Button, ButtonStyle -from openpilot.system.ui.widgets.label import gui_label, Label +from openpilot.system.ui.widgets.button import Button, ButtonStyle, BUTTON_PRESSED_BACKGROUND_COLORS +from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog from openpilot.system.ui.sunnypilot.lib.styles import style @@ -34,7 +34,7 @@ class TreeFolder: class TreeItemWidget(Button): - def __init__(self, text, ref, is_folder=False, indent_level=0, click_callback=None, favorite_callback=None, is_favorite=False): + def __init__(self, text, ref, is_folder=False, indent_level=0, click_callback=None, favorite_callback=None, is_favorite=False, is_expanded=False): super().__init__(text, click_callback, button_style=ButtonStyle.NORMAL, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, text_padding=20 + indent_level * 30, elide_right=True) self.text = text @@ -46,14 +46,21 @@ class TreeItemWidget(Button): self._favorite_callback = favorite_callback self.text_padding = 20 + indent_level * 30 self.border_radius = 10 + self.is_expanded = is_expanded def _render(self, rect): - indent = 60 * self.indent_level if self.indent_level > 0 else 10 + indent = 60 * self.indent_level self._rect = rl.Rectangle(rect.x + indent, rect.y, rect.width - indent, rect.height) - color = style.BUTTON_PRIMARY_COLOR if self.selected and not (self.ref == "search_bar" or self.is_folder) else style.BUTTON_DISABLED_BG_COLOR + if self.is_pressed: + color = BUTTON_PRESSED_BACKGROUND_COLORS[self._button_style] + elif self.selected and self.ref != "search_bar": + color = style.BUTTON_PRIMARY_COLOR + else: + color = style.BUTTON_DISABLED_BG_COLOR roundness = self.border_radius / (min(self._rect.width, self._rect.height) / 2) rl.draw_rectangle_rounded(self._rect, roundness, 10, color) - text_rect = rl.Rectangle(self._rect.x + self.text_padding + 20, self._rect.y, self._rect.width - self.text_padding - 20 - 90, self._rect.height) + text_offset = self.text_padding + 20 - 15 if self.is_expanded and not self.is_folder and self.indent_level > 0 else self.text_padding + 20 + text_rect = rl.Rectangle(self._rect.x + text_offset, self._rect.y, self._rect.width - self.text_padding - 20 - 90, self._rect.height) self._label.render(text_rect) if not self.is_folder and self._favorite_callback: @@ -86,8 +93,6 @@ class TreeOptionDialog(MultiOptionDialog): self.display_func = display_func or (lambda node: node.data.get('display_name', node.ref)) self.search_funcs = search_funcs or [lambda node: node.data.get('display_name', ''), lambda node: node.data.get('short_name', '')] self._build_visible_items() - self.cancel_rect = None - self.select_rect = None def _on_search_confirm(self, result, text): if result == DialogResult.CONFIRM: @@ -134,26 +139,14 @@ class TreeOptionDialog(MultiOptionDialog): for node in nodes: favorite_cb = (lambda node_ref=node: self._toggle_favorite(node_ref)) if self.fav_param and node.ref != "Default" else None self.visible_items.append(TreeItemWidget(self.display_func(node), node.ref, False, 1 if folder.folder else 0, - lambda node_ref=node: self._select_node(node_ref), favorite_cb, node.ref in self.favorites)) + lambda node_ref=node: self._select_node(node_ref), + favorite_cb, node.ref in self.favorites, is_expanded=expanded)) self.option_buttons = self.visible_items self.options = [item.text for item in self.visible_items] self.scroller._items = self.visible_items if reset_scroll: self.scroller.scroll_panel.set_offset(0) - def _draw_button(self, button_rect, button_text, is_primary=False, is_enabled=True): - if is_primary and is_enabled: - button_color = style.BUTTON_PRIMARY_COLOR - elif not is_enabled: - button_color = style.BUTTON_NEUTRAL_GRAY - else: - button_color = style.BUTTON_DISABLED_BG_COLOR - roundness = 10 / (min(button_rect.width, button_rect.height) / 2) - rl.draw_rectangle_rounded(button_rect, roundness, 10, button_color) - label = Label(button_text, 60, FontWeight.NORMAL, rl.GuiTextAlignment.TEXT_ALIGN_CENTER, - text_color=rl.WHITE if is_enabled else rl.GRAY) - label.render(button_rect) - def _render(self, rect): dialog_content_rect = rl.Rectangle(rect.x + 50, rect.y + 50, rect.width - 100, rect.height - 100) rl.draw_rectangle_rounded(dialog_content_rect, 0.02, 20, rl.BLACK) @@ -169,18 +162,12 @@ class TreeOptionDialog(MultiOptionDialog): button_width = (dialog_content_rect.width - 150) / 2 button_y_position = dialog_content_rect.y + dialog_content_rect.height - 160 - self.cancel_rect = rl.Rectangle(dialog_content_rect.x + 50, button_y_position, button_width, 160) - self.select_rect = rl.Rectangle(dialog_content_rect.x + 100 + button_width, button_y_position, button_width, 160) - self._draw_button(self.cancel_rect, tr("Cancel")) - self._draw_button(self.select_rect, tr("Select"), True, self.selection != self.current) + cancel_rect = rl.Rectangle(dialog_content_rect.x + 50, button_y_position, button_width, 160) + self.cancel_button.render(cancel_rect) + + select_rect = rl.Rectangle(dialog_content_rect.x + 100 + button_width, button_y_position, button_width, 160) + self.select_button.set_enabled(self.selection != self.current) + self.select_button.render(select_rect) + return self._result - - def _handle_mouse_release(self, mouse_pos): - if self.cancel_rect and rl.check_collision_point_rec(mouse_pos, self.cancel_rect): - self._set_result(DialogResult.CANCEL) - return True - if self.select_rect and rl.check_collision_point_rec(mouse_pos, self.select_rect) and self.selection != self.current: - self._set_result(DialogResult.CONFIRM) - return True - return super()._handle_mouse_release(mouse_pos)