system/ui: add MultiOptionDialog for selection from scrollable lists (#35176)

* add MultiOptionDialog for selection from scrollable lists

* mv

---------

Co-authored-by: Cameron Clough <cameronjclough@gmail.com>
pull/35177/head
Dean Lee 2 months ago committed by GitHub
parent f1760e63d3
commit bbeb37d726
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 21
      system/ui/lib/button.py
  2. 81
      system/ui/widgets/option_dialog.py

@ -10,6 +10,12 @@ class ButtonStyle(IntEnum):
TRANSPARENT = 3 # For buttons with transparent background and border TRANSPARENT = 3 # For buttons with transparent background and border
class TextAlignment(IntEnum):
LEFT = 0
CENTER = 1
RIGHT = 2
DEFAULT_BUTTON_FONT_SIZE = 60 DEFAULT_BUTTON_FONT_SIZE = 60
BUTTON_ENABLED_TEXT_COLOR = rl.Color(228, 228, 228, 255) BUTTON_ENABLED_TEXT_COLOR = rl.Color(228, 228, 228, 255)
BUTTON_DISABLED_TEXT_COLOR = rl.Color(228, 228, 228, 51) BUTTON_DISABLED_TEXT_COLOR = rl.Color(228, 228, 228, 51)
@ -38,6 +44,8 @@ def gui_button(
button_style: ButtonStyle = ButtonStyle.NORMAL, button_style: ButtonStyle = ButtonStyle.NORMAL,
is_enabled: bool = True, is_enabled: bool = True,
border_radius: int = 10, # Corner rounding in pixels border_radius: int = 10, # Corner rounding in pixels
text_alignment: TextAlignment = TextAlignment.CENTER,
text_padding: int = 20, # Padding for left/right alignment
) -> int: ) -> int:
result = 0 result = 0
@ -58,11 +66,16 @@ def gui_button(
rl.draw_rectangle_rounded_lines_ex(rect, roundness, 20, 2, rl.WHITE) rl.draw_rectangle_rounded_lines_ex(rect, roundness, 20, 2, rl.WHITE)
font = gui_app.font(font_weight) font = gui_app.font(font_weight)
# Center text in the button
text_size = rl.measure_text_ex(font, text, font_size, 0) text_size = rl.measure_text_ex(font, text, font_size, 0)
text_pos = rl.Vector2( text_pos = rl.Vector2(0, rect.y + (rect.height - text_size.y) // 2) # Vertical centering
rect.x + (rect.width - text_size.x) // 2, rect.y + (rect.height - text_size.y) // 2
) # Horizontal alignment
if text_alignment == TextAlignment.LEFT:
text_pos.x = rect.x + text_padding
elif text_alignment == TextAlignment.CENTER:
text_pos.x = rect.x + (rect.width - text_size.x) // 2
elif text_alignment == TextAlignment.RIGHT:
text_pos.x = rect.x + rect.width - text_size.x - text_padding
# Draw the button text # Draw the button text
text_color = BUTTON_ENABLED_TEXT_COLOR if is_enabled else BUTTON_DISABLED_TEXT_COLOR text_color = BUTTON_ENABLED_TEXT_COLOR if is_enabled else BUTTON_DISABLED_TEXT_COLOR

@ -0,0 +1,81 @@
import pyray as rl
from openpilot.system.ui.lib.button import gui_button, ButtonStyle, TextAlignment
from openpilot.system.ui.lib.label import gui_label
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
class MultiOptionDialog:
def __init__(self, title, options, current=""):
self._title = title
self._options = options
self._current = current if current in options else ""
self._selection = self._current
self._option_height = 80
self._padding = 20
self.scroll_panel = GuiScrollPanel()
@property
def selection(self):
return self._selection
def render(self, rect):
title_rect = rl.Rectangle(rect.x + self._padding, rect.y + self._padding, rect.width - 2 * self._padding, 70)
gui_label(title_rect, self._title, 70)
options_y_start = rect.y + 120
options_height = len(self._options) * (self._option_height + 10)
options_rect = rl.Rectangle(rect.x + self._padding, options_y_start, rect.width - 2 * self._padding, options_height)
view_rect = rl.Rectangle(
rect.x + self._padding, options_y_start, rect.width - 2 * self._padding, rect.height - 200 - 2 * self._padding
)
offset = self.scroll_panel.handle_scroll(view_rect, options_rect)
is_click_valid = self.scroll_panel.is_click_valid()
rl.begin_scissor_mode(int(view_rect.x), int(view_rect.y), int(view_rect.width), int(view_rect.height))
for i, option in enumerate(self._options):
y_pos = view_rect.y + i * (self._option_height + 10) + offset.y
item_rect = rl.Rectangle(view_rect.x, y_pos, view_rect.width, self._option_height)
if not rl.check_collision_recs(item_rect, view_rect):
continue
is_selected = option == self._selection
button_style = ButtonStyle.PRIMARY if is_selected else ButtonStyle.NORMAL
if gui_button(item_rect, option, button_style=button_style, text_alignment=TextAlignment.LEFT) and is_click_valid:
self._selection = option
rl.end_scissor_mode()
button_y = rect.y + rect.height - 80 - self._padding
button_width = (rect.width - 3 * self._padding) / 2
cancel_rect = rl.Rectangle(rect.x + self._padding, button_y, button_width, 80)
if gui_button(cancel_rect, "Cancel"):
return 0 # Canceled
select_rect = rl.Rectangle(rect.x + 2 * self._padding + button_width, button_y, button_width, 80)
has_new_selection = self._selection != "" and self._selection != self._current
if gui_button(select_rect, "Select", is_enabled=has_new_selection, button_style=ButtonStyle.PRIMARY):
return 1 # Selected
return -1 # Still active
if __name__ == "__main__":
from openpilot.system.ui.lib.application import gui_app
gui_app.init_window("Multi Option Dialog Example")
options = [f"Option {i}" for i in range(1, 11)]
dialog = MultiOptionDialog("Choose an option", options, options[0])
for _ in gui_app.render():
result = dialog.render(rl.Rectangle(100, 100, 1024, 800))
if result >= 0:
print(f"Selected: {dialog.selection}" if result > 0 else "Canceled")
break
Loading…
Cancel
Save