raylib: common mouse press hook (#35489)

* something like this

* need these

* rest

* another pr

* what is this merge conflict

f

* fix mouse down

* rm that!

* fix that

* rearrange

* fix bug where mouse held down on widget, dragged off, then let go

* temp

* fix that

* missing init
pull/35488/head
Shane Smiskol 4 days ago committed by GitHub
parent db5e413049
commit 2c59b5f8c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      selfdrive/ui/layouts/home.py
  2. 3
      selfdrive/ui/layouts/main.py
  3. 3
      selfdrive/ui/layouts/network.py
  4. 3
      selfdrive/ui/layouts/settings/developer.py
  5. 3
      selfdrive/ui/layouts/settings/device.py
  6. 8
      selfdrive/ui/layouts/settings/settings.py
  7. 3
      selfdrive/ui/layouts/settings/software.py
  8. 3
      selfdrive/ui/layouts/settings/toggles.py
  9. 13
      selfdrive/ui/layouts/sidebar.py
  10. 3
      selfdrive/ui/onroad/alert_renderer.py
  11. 4
      selfdrive/ui/onroad/augmented_road_view.py
  12. 3
      selfdrive/ui/onroad/cameraview.py
  13. 4
      selfdrive/ui/onroad/driver_camera_view.py
  14. 3
      selfdrive/ui/onroad/driver_state.py
  15. 5
      selfdrive/ui/onroad/exp_button.py
  16. 5
      selfdrive/ui/onroad/hud_renderer.py
  17. 3
      selfdrive/ui/onroad/model_renderer.py
  18. 3
      selfdrive/ui/widgets/offroad_alerts.py
  19. 25
      system/ui/lib/application.py
  20. 3
      system/ui/lib/inputbox.py
  21. 12
      system/ui/lib/list_view.py
  22. 3
      system/ui/lib/toggle.py
  23. 3
      system/ui/reset.py
  24. 3
      system/ui/setup.py
  25. 3
      system/ui/spinner.py
  26. 3
      system/ui/text.py
  27. 3
      system/ui/updater.py
  28. 3
      system/ui/widgets/keyboard.py
  29. 3
      system/ui/widgets/network.py
  30. 5
      system/ui/widgets/option_dialog.py

@ -26,6 +26,7 @@ class HomeLayoutState(IntEnum):
class HomeLayout(Widget):
def __init__(self):
super().__init__()
self.params = Params()
self.update_alert = UpdateAlert()
@ -58,7 +59,7 @@ class HomeLayout(Widget):
def _set_state(self, state: HomeLayoutState):
self.current_state = state
def render(self, rect: rl.Rectangle):
def _render(self, rect: rl.Rectangle):
self._update_layout_rects(rect)
current_time = time.time()

@ -16,6 +16,7 @@ class MainState(IntEnum):
class MainLayout(Widget):
def __init__(self):
super().__init__()
self._sidebar = Sidebar()
self._sidebar_visible = True
self._current_mode = MainState.HOME
@ -32,7 +33,7 @@ class MainLayout(Widget):
# Set callbacks
self._setup_callbacks()
def render(self, rect):
def _render(self, rect):
self._current_callback = None
self._update_layout_rects(rect)

@ -6,10 +6,11 @@ from openpilot.system.ui.widgets.network import WifiManagerUI
class NetworkLayout(Widget):
def __init__(self):
super().__init__()
self.wifi_manager = WifiManagerWrapper()
self.wifi_ui = WifiManagerUI(self.wifi_manager)
def render(self, rect: rl.Rectangle):
def _render(self, rect: rl.Rectangle):
self.wifi_ui.render(rect)
def shutdown(self):

@ -14,6 +14,7 @@ DESCRIPTIONS = {
class DeveloperLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
items = [
toggle_item(
@ -44,7 +45,7 @@ class DeveloperLayout(Widget):
self._list_widget = ListView(items)
def render(self, rect):
def _render(self, rect):
self._list_widget.render(rect)
def _on_enable_adb(self): pass

@ -17,6 +17,7 @@ DESCRIPTIONS = {
class DeviceLayout(Widget):
def __init__(self):
super().__init__()
params = Params()
dongle_id = params.get("DongleId", encoding="utf-8") or "N/A"
serial = params.get("HardwareSerial") or "N/A"
@ -37,7 +38,7 @@ class DeviceLayout(Widget):
self._list_widget = ListView(items)
def render(self, rect):
def _render(self, rect):
self._list_widget.render(rect)
def _on_pair_device(self): pass

@ -50,6 +50,7 @@ class PanelInfo:
class SettingsLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
self._current_panel = PanelType.DEVICE
self._max_scroll = 0.0
@ -73,7 +74,7 @@ class SettingsLayout(Widget):
def set_callbacks(self, on_close: Callable):
self._close_callback = on_close
def render(self, rect: rl.Rectangle):
def _render(self, rect: rl.Rectangle):
# Calculate layout
sidebar_rect = rl.Rectangle(rect.x, rect.y, SIDEBAR_WIDTH, rect.height)
panel_rect = rl.Rectangle(rect.x + SIDEBAR_WIDTH, rect.y, rect.width - SIDEBAR_WIDTH, rect.height)
@ -82,9 +83,6 @@ class SettingsLayout(Widget):
self._draw_sidebar(sidebar_rect)
self._draw_current_panel(panel_rect)
if rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT):
self.handle_mouse_release(rl.get_mouse_position())
def _draw_sidebar(self, rect: rl.Rectangle):
rl.draw_rectangle_rec(rect, SIDEBAR_COLOR)
@ -154,7 +152,7 @@ class SettingsLayout(Widget):
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE,
)
def handle_mouse_release(self, mouse_pos: rl.Vector2) -> bool:
def _handle_mouse_release(self, mouse_pos: rl.Vector2) -> bool:
# Check close button
if rl.check_collision_point_rec(mouse_pos, self._close_btn_rect):
if self._close_callback:

@ -4,6 +4,7 @@ from openpilot.system.ui.lib.list_view import ListView, button_item, text_item
class SoftwareLayout(Widget):
def __init__(self):
super().__init__()
items = [
text_item("Current Version", ""),
button_item("Download", "CHECK", callback=self._on_download_update),
@ -14,7 +15,7 @@ class SoftwareLayout(Widget):
self._list_widget = ListView(items)
def render(self, rect):
def _render(self, rect):
self._list_widget.render(rect)
def _on_download_update(self): pass

@ -21,6 +21,7 @@ DESCRIPTIONS = {
class TogglesLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
items = [
toggle_item(
@ -65,5 +66,5 @@ class TogglesLayout(Widget):
self._list_widget = ListView(items)
def render(self, rect):
def _render(self, rect):
self._list_widget.render(rect)

@ -62,6 +62,7 @@ class MetricData:
class Sidebar(Widget):
def __init__(self):
super().__init__()
self._net_type = NETWORK_TYPES.get(NetworkType.none)
self._net_strength = 0
@ -83,7 +84,7 @@ class Sidebar(Widget):
self._on_settings_click = on_settings
self._on_flag_click = on_flag
def render(self, rect: rl.Rectangle):
def _render(self, rect: rl.Rectangle):
self.update_state()
# Background
@ -93,8 +94,6 @@ class Sidebar(Widget):
self._draw_network_indicator(rect)
self._draw_metrics(rect)
self._handle_mouse_release()
def update_state(self):
sm = ui_state.sm
if not sm.updated['deviceState']:
@ -137,11 +136,7 @@ class Sidebar(Widget):
else:
self._panda_status.update("VEHICLE", "ONLINE", Colors.GOOD)
def _handle_mouse_release(self):
if not rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT):
return
mouse_pos = rl.get_mouse_position()
def _handle_mouse_release(self, mouse_pos: rl.Vector2):
if rl.check_collision_point_rec(mouse_pos, SETTINGS_BTN):
if self._on_settings_click:
self._on_settings_click()
@ -151,7 +146,7 @@ class Sidebar(Widget):
def _draw_buttons(self, rect: rl.Rectangle):
mouse_pos = rl.get_mouse_position()
mouse_down = rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT)
mouse_down = self._is_pressed and rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT)
# Settings button
settings_down = mouse_down and rl.check_collision_point_rec(mouse_pos, SETTINGS_BTN)

@ -61,6 +61,7 @@ ALERT_CRITICAL_REBOOT = Alert(
class AlertRenderer(Widget):
def __init__(self):
super().__init__()
self.font_regular: rl.Font = gui_app.font(FontWeight.NORMAL)
self.font_bold: rl.Font = gui_app.font(FontWeight.BOLD)
@ -91,7 +92,7 @@ class AlertRenderer(Widget):
# Return current alert
return Alert(text1=ss.alertText1, text2=ss.alertText2, size=ss.alertSize, status=ss.alertStatus)
def render(self, rect: rl.Rectangle) -> None:
def _render(self, rect: rl.Rectangle) -> None:
alert = self.get_alert(ui_state.sm)
if not alert:
return

@ -52,7 +52,7 @@ class AugmentedRoadView(CameraView):
# Callbacks
self.on_click: Callable | None = None
def render(self, rect):
def _render(self, rect):
# Only render when system is started to avoid invalid data access
if not ui_state.started:
return
@ -83,7 +83,7 @@ class AugmentedRoadView(CameraView):
)
# Render the base camera view
super().render(rect)
super()._render(rect)
# Draw all UI overlays
self.model_renderer.render(self._content_rect)

@ -57,6 +57,7 @@ else:
class CameraView(Widget):
def __init__(self, name: str, stream_type: VisionStreamType):
super().__init__()
self._name = name
# Primary stream
self.client = VisionIpcClient(name, stream_type, conflate=True)
@ -149,7 +150,7 @@ class CameraView(Widget):
[0.0, 0.0, 1.0]
])
def render(self, rect: rl.Rectangle):
def _render(self, rect: rl.Rectangle):
if self._switching:
self._handle_switch()

@ -13,8 +13,8 @@ class DriverCameraView(CameraView):
super().__init__("camerad", stream_type)
self.driver_state_renderer = DriverStateRenderer()
def render(self, rect):
super().render(rect)
def _render(self, rect):
super()._render(rect)
if not self.frame:
gui_label(

@ -43,6 +43,7 @@ class ArcData:
class DriverStateRenderer(Widget):
def __init__(self):
super().__init__()
# Initial state with NumPy arrays
self.face_kpts_draw = DEFAULT_FACE_KPTS_3D.copy()
self.is_active = False
@ -74,7 +75,7 @@ class DriverStateRenderer(Widget):
self.engaged_color = rl.Color(26, 242, 66, 255)
self.disengaged_color = rl.Color(139, 139, 139, 255)
def render(self, rect):
def _render(self, rect):
if not self._is_visible(ui_state.sm):
return

@ -8,6 +8,7 @@ from openpilot.common.params import Params
class ExpButton(Widget):
def __init__(self, button_size: int, icon_size: int):
super().__init__()
self._params = Params()
self._experimental_mode: bool = False
self._engageable: bool = False
@ -41,13 +42,13 @@ class ExpButton(Widget):
return True
return False
def render(self, rect: rl.Rectangle) -> None:
def _render(self, rect: rl.Rectangle) -> None:
self._rect.x, self._rect.y = rect.x, rect.y
center_x = int(self._rect.x + self._rect.width // 2)
center_y = int(self._rect.y + self._rect.height // 2)
mouse_over = rl.check_collision_point_rec(rl.get_mouse_position(), self._rect)
mouse_down = rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT)
mouse_down = rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and self._is_pressed
self._white_color.a = 180 if (mouse_down and mouse_over) or not self._engageable else 255
texture = self._txt_exp if self._held_or_actual_mode() else self._txt_wheel

@ -57,6 +57,7 @@ COLORS = Colors()
class HudRenderer(Widget):
def __init__(self):
super().__init__()
"""Initialize the HUD renderer."""
self.is_cruise_set: bool = False
self.is_cruise_available: bool = False
@ -99,7 +100,7 @@ class HudRenderer(Widget):
self._exp_button.update_state(sm)
def render(self, rect: rl.Rectangle) -> None:
def _render(self, rect: rl.Rectangle) -> None:
"""Render HUD elements to the screen."""
self._update_state(ui_state.sm)
@ -120,7 +121,7 @@ class HudRenderer(Widget):
button_x = rect.x + rect.width - UI_CONFIG.border_size - UI_CONFIG.button_size
button_y = rect.y + UI_CONFIG.border_size
self._exp_button.render(rl.Rectangle(button_x, button_y, 0, 0))
self._exp_button.render(rl.Rectangle(button_x, button_y, UI_CONFIG.button_size, UI_CONFIG.button_size))
def handle_mouse_event(self) -> bool:
return bool(self._exp_button.handle_mouse_event())

@ -45,6 +45,7 @@ class LeadVehicle:
class ModelRenderer(Widget):
def __init__(self):
super().__init__()
self._longitudinal_control = False
self._experimental_mode = False
self._blend_factor = 1.0
@ -86,7 +87,7 @@ class ModelRenderer(Widget):
self._car_space_transform = transform.astype(np.float32)
self._transform_dirty = True
def render(self, rect: rl.Rectangle):
def _render(self, rect: rl.Rectangle):
sm = ui_state.sm
# Check if data is up-to-date

@ -43,6 +43,7 @@ class AlertData:
class AbstractAlert(Widget, ABC):
def __init__(self, has_reboot_btn: bool = False):
super().__init__()
self.params = Params()
self.has_reboot_btn = has_reboot_btn
self.dismiss_callback: Callable | None = None
@ -89,7 +90,7 @@ class AbstractAlert(Widget, ABC):
return False
def render(self, rect: rl.Rectangle):
def _render(self, rect: rl.Rectangle):
rl.draw_rectangle_rounded(rect, AlertConstants.BORDER_RADIUS / rect.width, 10, AlertColors.BACKGROUND)
footer_height = AlertConstants.BUTTON_SIZE[1] + AlertConstants.SPACING

@ -28,10 +28,33 @@ FONT_DIR = ASSETS_DIR.joinpath("fonts")
class Widget(abc.ABC):
def __init__(self):
self._is_pressed = False
def render(self, rect: rl.Rectangle) -> bool | int | None:
ret = self._render(rect)
# Keep track of whether mouse down started within the widget's rectangle
mouse_pos = rl.get_mouse_position()
if rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT):
if rl.check_collision_point_rec(mouse_pos, rect):
self._is_pressed = True
if rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT):
if self._is_pressed and rl.check_collision_point_rec(mouse_pos, rect):
self._handle_mouse_release(mouse_pos)
self._is_pressed = False
return ret
@abc.abstractmethod
def render(self, rect: rl.Rectangle) -> bool | None:
def _render(self, rect: rl.Rectangle) -> bool | int | None:
"""Render the widget within the given rectangle."""
def _handle_mouse_release(self, mouse_pos: rl.Vector2) -> bool:
"""Handle mouse release events, if applicable."""
return False
class FontWeight(IntEnum):
THIN = 0

@ -9,6 +9,7 @@ PASSWORD_MASK_DELAY = 1.5 # Seconds to show character before masking
class InputBox(Widget):
def __init__(self, max_text_size=255, password_mode=False):
super().__init__()
self._max_text_size = max_text_size
self._input_text = ""
self._cursor_position = 0
@ -100,7 +101,7 @@ class InputBox(Widget):
return True
return False
def render(self, rect, color=rl.BLACK, border_color=rl.DARKGRAY, text_color=rl.WHITE, font_size=80):
def _render(self, rect, color=rl.BLACK, border_color=rl.DARKGRAY, text_color=rl.WHITE, font_size=80):
# Store dimensions for text offset calculations
self._visible_width = rect.width
self._font_size = font_size

@ -32,6 +32,7 @@ BUTTON_FONT_WEIGHT = FontWeight.MEDIUM
# Abstract base class for right-side items
class RightItem(Widget, ABC):
def __init__(self, width: int = 100):
super().__init__()
self.width = width
self.enabled = True
@ -47,7 +48,7 @@ class ToggleRightItem(RightItem):
self.state = initial_state
self.enabled = True
def render(self, rect: rl.Rectangle) -> bool:
def _render(self, rect: rl.Rectangle) -> bool:
if self.toggle.render(rl.Rectangle(rect.x, rect.y + (rect.height - TOGGLE_HEIGHT) / 2, self.width, TOGGLE_HEIGHT)):
self.state = not self.state
return True
@ -73,7 +74,7 @@ class ButtonRightItem(RightItem):
self.text = text
self.enabled = True
def render(self, rect: rl.Rectangle) -> bool:
def _render(self, rect: rl.Rectangle) -> bool:
return (
gui_button(
rl.Rectangle(rect.x, rect.y + (rect.height - BUTTON_HEIGHT) / 2, BUTTON_WIDTH, BUTTON_HEIGHT),
@ -103,7 +104,7 @@ class TextRightItem(RightItem):
text_width = measure_text_cached(font, text, font_size).x
super().__init__(int(text_width + 20))
def render(self, rect: rl.Rectangle) -> bool:
def _render(self, rect: rl.Rectangle) -> bool:
font = gui_app.font(FontWeight.NORMAL)
text_size = measure_text_cached(font, self.text, self.font_size)
@ -165,8 +166,9 @@ class ListItem:
return rl.Rectangle(right_x, right_y, right_width, ITEM_BASE_HEIGHT)
class ListView:
class ListView(Widget):
def __init__(self, items: list[ListItem]):
super().__init__()
self._items: list[ListItem] = items
self._last_dim: tuple[float, float] = (0, 0)
self.scroll_panel = GuiScrollPanel()
@ -183,7 +185,7 @@ class ListView:
def invalid_height_cache(self):
self._last_dim = (0, 0)
def render(self, rect: rl.Rectangle):
def _render(self, rect: rl.Rectangle):
if self._last_dim != (rect.width, rect.height):
self._update_item_rects(rect)
self._last_dim = (rect.width, rect.height)

@ -14,6 +14,7 @@ ANIMATION_SPEED = 8.0
class Toggle(Widget):
def __init__(self, initial_state=False):
super().__init__()
self._state = initial_state
self._enabled = True
self._rect = rl.Rectangle(0, 0, WIDTH, HEIGHT)
@ -50,7 +51,7 @@ class Toggle(Widget):
self._progress += delta if self._progress < self._target else -delta
self._progress = max(0.0, min(1.0, self._progress))
def render(self, rect: rl.Rectangle):
def _render(self, rect: rl.Rectangle):
self._rect.x, self._rect.y = rect.x, rect.y
self.update()

@ -29,6 +29,7 @@ class ResetState(IntEnum):
class Reset(Widget):
def __init__(self, mode):
super().__init__()
self.mode = mode
self.reset_state = ResetState.NONE
@ -54,7 +55,7 @@ class Reset(Widget):
self.reset_state = ResetState.RESETTING
threading.Timer(0.1, self._do_erase).start()
def render(self, rect: rl.Rectangle):
def _render(self, rect: rl.Rectangle):
label_rect = rl.Rectangle(rect.x + 140, rect.y, rect.width - 280, 100)
gui_label(label_rect, "System Reset", 100, font_weight=FontWeight.BOLD)

@ -41,6 +41,7 @@ class SetupState(IntEnum):
class Setup(Widget):
def __init__(self):
super().__init__()
self.state = SetupState.GETTING_STARTED
self.network_check_thread = None
self.network_connected = threading.Event()
@ -67,7 +68,7 @@ class Setup(Widget):
except (FileNotFoundError, ValueError):
self.state = SetupState.LOW_VOLTAGE
def render(self, rect: rl.Rectangle):
def _render(self, rect: rl.Rectangle):
if self.state == SetupState.LOW_VOLTAGE:
self.render_low_voltage(rect)
elif self.state == SetupState.GETTING_STARTED:

@ -24,6 +24,7 @@ def clamp(value, min_value, max_value):
class Spinner(Widget):
def __init__(self):
super().__init__()
self._comma_texture = gui_app.texture("images/spinner_comma.png", TEXTURE_SIZE, TEXTURE_SIZE)
self._spinner_texture = gui_app.texture("images/spinner_track.png", TEXTURE_SIZE, TEXTURE_SIZE, alpha_premultiply=True)
self._rotation = 0.0
@ -38,7 +39,7 @@ class Spinner(Widget):
self._progress = None
self._wrapped_lines = wrap_text(text, FONT_SIZE, gui_app.width - MARGIN_H)
def render(self, rect: rl.Rectangle):
def _render(self, rect: rl.Rectangle):
if self._wrapped_lines:
# Calculate total height required for spinner and text
spacing = 50

@ -48,13 +48,14 @@ def wrap_text(text, font_size, max_width):
class TextWindow(Widget):
def __init__(self, text: str):
super().__init__()
self._textarea_rect = rl.Rectangle(MARGIN, MARGIN, gui_app.width - MARGIN * 2, gui_app.height - MARGIN * 2)
self._wrapped_lines = wrap_text(text, FONT_SIZE, self._textarea_rect.width - 20)
self._content_rect = rl.Rectangle(0, 0, self._textarea_rect.width - 20, len(self._wrapped_lines) * LINE_HEIGHT)
self._scroll_panel = GuiScrollPanel(show_vertical_scroll_bar=True)
self._scroll_panel._offset.y = -max(self._content_rect.height - self._textarea_rect.height, 0)
def render(self, rect: rl.Rectangle):
def _render(self, rect: rl.Rectangle):
scroll = self._scroll_panel.handle_scroll(self._textarea_rect, self._content_rect)
rl.begin_scissor_mode(int(self._textarea_rect.x), int(self._textarea_rect.y), int(self._textarea_rect.width), int(self._textarea_rect.height))
for i, line in enumerate(self._wrapped_lines):

@ -32,6 +32,7 @@ class Screen(IntEnum):
class Updater(Widget):
def __init__(self, updater_path, manifest_path):
super().__init__()
self.updater = updater_path
self.manifest = manifest_path
self.current_screen = Screen.PROMPT
@ -137,7 +138,7 @@ class Updater(Widget):
HARDWARE.reboot()
return
def render(self, rect: rl.Rectangle):
def _render(self, rect: rl.Rectangle):
if self.current_screen == Screen.PROMPT:
self.render_prompt_screen(rect)
elif self.current_screen == Screen.WIFI:

@ -54,6 +54,7 @@ KEYBOARD_LAYOUTS = {
class Keyboard(Widget):
def __init__(self, max_text_size: int = 255, min_text_size: int = 0, password_mode: bool = False, show_password_toggle: bool = False):
super().__init__()
self._layout_name: Literal["lowercase", "uppercase", "numbers", "specials"] = "lowercase"
self._caps_lock = False
self._last_shift_press_time = 0
@ -95,7 +96,7 @@ class Keyboard(Widget):
self._title = title
self._sub_title = sub_title
def render(self, rect: rl.Rectangle):
def _render(self, rect: rl.Rectangle):
rect = rl.Rectangle(rect.x + CONTENT_MARGIN, rect.y + CONTENT_MARGIN, rect.width - 2 * CONTENT_MARGIN, rect.height - 2 * CONTENT_MARGIN)
gui_label(rl.Rectangle(rect.x, rect.y, rect.width, 95), self._title, 90, font_weight=FontWeight.BOLD)
gui_label(rl.Rectangle(rect.x, rect.y + 95, rect.width, 60), self._sub_title, 55, font_weight=FontWeight.NORMAL)

@ -59,6 +59,7 @@ UIState = StateIdle | StateConnecting | StateNeedsAuth | StateShowForgetConfirm
class WifiManagerUI(Widget):
def __init__(self, wifi_manager: WifiManagerWrapper):
super().__init__()
self.state: UIState = StateIdle()
self.btn_width: int = 200
self.scroll_panel = GuiScrollPanel()
@ -80,7 +81,7 @@ class WifiManagerUI(Widget):
self.wifi_manager.start()
self.wifi_manager.connect()
def render(self, rect: rl.Rectangle):
def _render(self, rect: rl.Rectangle):
with self._lock:
if not self._networks:
gui_label(rect, "Scanning Wi-Fi networks...", 72, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER)

@ -8,6 +8,7 @@ from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
class MultiOptionDialog(Widget):
def __init__(self, title, options, current=""):
super().__init__()
self._title = title
self._options = options
self._current = current if current in options else ""
@ -20,7 +21,7 @@ class MultiOptionDialog(Widget):
def selection(self):
return self._selection
def render(self, rect):
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)
@ -77,6 +78,6 @@ if __name__ == "__main__":
for _ in gui_app.render():
result = dialog.render(rl.Rectangle(100, 100, 1024, 800))
if result >= 0:
if isinstance(result, int) and result >= 0:
print(f"Selected: {dialog.selection}" if result > 0 else "Canceled")
break

Loading…
Cancel
Save