mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-01 19:42:07 +08:00
raylib: remove gui_button (#36229)
* vibing can be good * and listview * rm that * html render * text.py * ssh keys * updater w/ Auto * wow gpt5 actually is better * well this is better * huh wifi still doesn't work * lfg * lint * manager waits for exit * wait a minute this changes nothing * this will work * whoops * clean up html * actually useless * clean up option * typing * bump
This commit is contained in:
@@ -141,7 +141,7 @@ class DeviceLayout(Widget):
|
||||
def _on_regulatory(self):
|
||||
if not self._fcc_dialog:
|
||||
self._fcc_dialog = HtmlRenderer(os.path.join(BASEDIR, "selfdrive/assets/offroad/fcc.html"))
|
||||
gui_app.set_modal_overlay(self._fcc_dialog, callback=lambda result: setattr(self, '_fcc_dialog', None))
|
||||
gui_app.set_modal_overlay(self._fcc_dialog)
|
||||
|
||||
def _on_review_training_guide(self):
|
||||
if not self._training_guide:
|
||||
|
||||
@@ -2,13 +2,14 @@ import pyray as rl
|
||||
import requests
|
||||
import threading
|
||||
import copy
|
||||
from collections.abc import Callable
|
||||
from enum import Enum
|
||||
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.widgets import DialogResult
|
||||
from openpilot.system.ui.widgets.button import gui_button, ButtonStyle
|
||||
from openpilot.system.ui.widgets.button import Button, ButtonStyle
|
||||
from openpilot.system.ui.widgets.confirm_dialog import alert_dialog
|
||||
from openpilot.system.ui.widgets.keyboard import Keyboard
|
||||
from openpilot.system.ui.widgets.list_view import (
|
||||
@@ -38,9 +39,15 @@ class SshKeyAction(ItemAction):
|
||||
self._params = Params()
|
||||
self._error_message: str = ""
|
||||
self._text_font = gui_app.font(FontWeight.MEDIUM)
|
||||
self._button = Button("", click_callback=self._handle_button_click, button_style=ButtonStyle.LIST_ACTION,
|
||||
border_radius=BUTTON_BORDER_RADIUS, font_size=BUTTON_FONT_SIZE)
|
||||
|
||||
self._refresh_state()
|
||||
|
||||
def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None:
|
||||
super().set_touch_valid_callback(touch_callback)
|
||||
self._button.set_touch_valid_callback(touch_callback)
|
||||
|
||||
def _refresh_state(self):
|
||||
self._username = self._params.get("GithubUsername")
|
||||
self._state = SshKeyActionState.REMOVE if self._params.get("GithubSshKeys") else SshKeyActionState.ADD
|
||||
@@ -66,18 +73,11 @@ class SshKeyAction(ItemAction):
|
||||
)
|
||||
|
||||
# Draw button
|
||||
if gui_button(
|
||||
rl.Rectangle(
|
||||
rect.x + rect.width - BUTTON_WIDTH, rect.y + (rect.height - BUTTON_HEIGHT) / 2, BUTTON_WIDTH, BUTTON_HEIGHT
|
||||
),
|
||||
self._state.value,
|
||||
is_enabled=self._state != SshKeyActionState.LOADING,
|
||||
border_radius=BUTTON_BORDER_RADIUS,
|
||||
font_size=BUTTON_FONT_SIZE,
|
||||
button_style=ButtonStyle.LIST_ACTION,
|
||||
):
|
||||
self._handle_button_click()
|
||||
return True
|
||||
button_rect = rl.Rectangle(rect.x + rect.width - BUTTON_WIDTH, rect.y + (rect.height - BUTTON_HEIGHT) / 2, BUTTON_WIDTH, BUTTON_HEIGHT)
|
||||
self._button.set_rect(button_rect)
|
||||
self._button.set_text(self._state.value)
|
||||
self._button.set_enabled(self._state != SshKeyActionState.LOADING)
|
||||
self._button.render(button_rect)
|
||||
return False
|
||||
|
||||
def _handle_button_click(self):
|
||||
|
||||
+11
-8
@@ -7,7 +7,7 @@ from openpilot.system.ui.lib.application import gui_app
|
||||
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.button import gui_button, ButtonStyle
|
||||
from openpilot.system.ui.widgets.button import Button, ButtonStyle
|
||||
|
||||
MARGIN = 50
|
||||
SPACING = 40
|
||||
@@ -56,6 +56,15 @@ class TextWindow(Widget):
|
||||
self._scroll_panel = GuiScrollPanel()
|
||||
self._scroll_panel._offset_filter_y.x = -max(self._content_rect.height - self._textarea_rect.height, 0)
|
||||
|
||||
button_text = "Exit" if PC else "Reboot"
|
||||
self._button = Button(button_text, click_callback=self._on_button_clicked, button_style=ButtonStyle.TRANSPARENT_WHITE_BORDER)
|
||||
|
||||
@staticmethod
|
||||
def _on_button_clicked():
|
||||
gui_app.request_close()
|
||||
if not PC:
|
||||
HARDWARE.reboot()
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
scroll = self._scroll_panel.update(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))
|
||||
@@ -67,13 +76,7 @@ class TextWindow(Widget):
|
||||
rl.end_scissor_mode()
|
||||
|
||||
button_bounds = rl.Rectangle(rect.width - MARGIN - BUTTON_SIZE.x - SPACING, rect.height - MARGIN - BUTTON_SIZE.y, BUTTON_SIZE.x, BUTTON_SIZE.y)
|
||||
ret = gui_button(button_bounds, "Exit" if PC else "Reboot", button_style=ButtonStyle.TRANSPARENT)
|
||||
if ret:
|
||||
if PC:
|
||||
gui_app.request_close()
|
||||
else:
|
||||
HARDWARE.reboot()
|
||||
return ret
|
||||
self._button.render(button_bounds)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+15
-15
@@ -9,7 +9,7 @@ from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from openpilot.system.ui.lib.wifi_manager import WifiManager
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.button import gui_button, ButtonStyle
|
||||
from openpilot.system.ui.widgets.button import Button, ButtonStyle
|
||||
from openpilot.system.ui.widgets.label import gui_text_box, gui_label
|
||||
from openpilot.system.ui.widgets.network import WifiManagerUI
|
||||
|
||||
@@ -45,8 +45,17 @@ class Updater(Widget):
|
||||
self.update_thread = None
|
||||
self.wifi_manager_ui = WifiManagerUI(WifiManager())
|
||||
|
||||
# Buttons
|
||||
self._wifi_button = Button("Connect to Wi-Fi", click_callback=lambda: self.set_current_screen(Screen.WIFI))
|
||||
self._install_button = Button("Install", click_callback=self.install_update, button_style=ButtonStyle.PRIMARY)
|
||||
self._back_button = Button("Back", click_callback=lambda: self.set_current_screen(Screen.PROMPT))
|
||||
self._reboot_button = Button("Reboot", click_callback=lambda: HARDWARE.reboot())
|
||||
|
||||
def set_current_screen(self, screen: Screen):
|
||||
self.current_screen = screen
|
||||
|
||||
def install_update(self):
|
||||
self.current_screen = Screen.PROGRESS
|
||||
self.set_current_screen(Screen.PROGRESS)
|
||||
self.progress_value = 0
|
||||
self.progress_text = "Downloading..."
|
||||
self.show_reboot_button = False
|
||||
@@ -96,15 +105,11 @@ class Updater(Widget):
|
||||
|
||||
# WiFi button
|
||||
wifi_button_rect = rl.Rectangle(MARGIN, button_y, button_width, BUTTON_HEIGHT)
|
||||
if gui_button(wifi_button_rect, "Connect to Wi-Fi"):
|
||||
self.current_screen = Screen.WIFI
|
||||
return # Return to avoid processing other buttons after screen change
|
||||
self._wifi_button.render(wifi_button_rect)
|
||||
|
||||
# Install button
|
||||
install_button_rect = rl.Rectangle(MARGIN * 2 + button_width, button_y, button_width, BUTTON_HEIGHT)
|
||||
if gui_button(install_button_rect, "Install", button_style=ButtonStyle.PRIMARY):
|
||||
self.install_update()
|
||||
return # Return to avoid further processing after action
|
||||
self._install_button.render(install_button_rect)
|
||||
|
||||
def render_wifi_screen(self, rect: rl.Rectangle):
|
||||
# Draw the Wi-Fi manager UI
|
||||
@@ -112,9 +117,7 @@ class Updater(Widget):
|
||||
self.wifi_manager_ui.render(wifi_rect)
|
||||
|
||||
back_button_rect = rl.Rectangle(MARGIN, rect.height - MARGIN - BUTTON_HEIGHT, BUTTON_WIDTH, BUTTON_HEIGHT)
|
||||
if gui_button(back_button_rect, "Back"):
|
||||
self.current_screen = Screen.PROMPT
|
||||
return # Return to avoid processing other interactions after screen change
|
||||
self._back_button.render(back_button_rect)
|
||||
|
||||
def render_progress_screen(self, rect: rl.Rectangle):
|
||||
title_rect = rl.Rectangle(MARGIN + 100, 330, rect.width - MARGIN * 2 - 200, 100)
|
||||
@@ -133,10 +136,7 @@ class Updater(Widget):
|
||||
# Show reboot button if needed
|
||||
if self.show_reboot_button:
|
||||
reboot_rect = rl.Rectangle(MARGIN + 100, rect.height - MARGIN - BUTTON_HEIGHT, BUTTON_WIDTH, BUTTON_HEIGHT)
|
||||
if gui_button(reboot_rect, "Reboot"):
|
||||
# Return True to signal main loop to exit before rebooting
|
||||
HARDWARE.reboot()
|
||||
return
|
||||
self._reboot_button.render(reboot_rect)
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
if self.current_screen == Screen.PROMPT:
|
||||
|
||||
+17
-103
@@ -3,8 +3,7 @@ from enum import IntEnum
|
||||
|
||||
import pyray as rl
|
||||
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.lib.application import FontWeight, MousePos
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.label import TextAlignment, Label
|
||||
|
||||
@@ -14,7 +13,8 @@ class ButtonStyle(IntEnum):
|
||||
PRIMARY = 1 # For main actions
|
||||
DANGER = 2 # For critical actions, like reboot or delete
|
||||
TRANSPARENT = 3 # For buttons with transparent background and border
|
||||
TRANSPARENT_WHITE_TEXT = 3 # For buttons with transparent background and border and white text
|
||||
TRANSPARENT_WHITE_TEXT = 9 # For buttons with transparent background and border and white text
|
||||
TRANSPARENT_WHITE_BORDER = 10 # For buttons with transparent background and white border and text
|
||||
ACTION = 4
|
||||
LIST_ACTION = 5 # For list items with action buttons
|
||||
NO_EFFECT = 6
|
||||
@@ -32,6 +32,7 @@ BUTTON_TEXT_COLOR = {
|
||||
ButtonStyle.DANGER: rl.Color(228, 228, 228, 255),
|
||||
ButtonStyle.TRANSPARENT: rl.BLACK,
|
||||
ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.WHITE,
|
||||
ButtonStyle.TRANSPARENT_WHITE_BORDER: rl.Color(228, 228, 228, 255),
|
||||
ButtonStyle.ACTION: rl.BLACK,
|
||||
ButtonStyle.LIST_ACTION: rl.Color(228, 228, 228, 255),
|
||||
ButtonStyle.NO_EFFECT: rl.Color(228, 228, 228, 255),
|
||||
@@ -49,6 +50,7 @@ BUTTON_BACKGROUND_COLORS = {
|
||||
ButtonStyle.DANGER: rl.Color(255, 36, 36, 255),
|
||||
ButtonStyle.TRANSPARENT: rl.BLACK,
|
||||
ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.BLANK,
|
||||
ButtonStyle.TRANSPARENT_WHITE_BORDER: rl.BLACK,
|
||||
ButtonStyle.ACTION: rl.Color(189, 189, 189, 255),
|
||||
ButtonStyle.LIST_ACTION: rl.Color(57, 57, 57, 255),
|
||||
ButtonStyle.NO_EFFECT: rl.Color(51, 51, 51, 255),
|
||||
@@ -62,6 +64,7 @@ BUTTON_PRESSED_BACKGROUND_COLORS = {
|
||||
ButtonStyle.DANGER: rl.Color(255, 36, 36, 255),
|
||||
ButtonStyle.TRANSPARENT: rl.BLACK,
|
||||
ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.BLANK,
|
||||
ButtonStyle.TRANSPARENT_WHITE_BORDER: rl.BLANK,
|
||||
ButtonStyle.ACTION: rl.Color(130, 130, 130, 255),
|
||||
ButtonStyle.LIST_ACTION: rl.Color(74, 74, 74, 74),
|
||||
ButtonStyle.NO_EFFECT: rl.Color(51, 51, 51, 255),
|
||||
@@ -73,104 +76,6 @@ BUTTON_DISABLED_BACKGROUND_COLORS = {
|
||||
ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.BLANK,
|
||||
}
|
||||
|
||||
_pressed_buttons: set[str] = set() # Track mouse press state globally
|
||||
|
||||
|
||||
# TODO: This should be a Widget class
|
||||
|
||||
def gui_button(
|
||||
rect: rl.Rectangle,
|
||||
text: str,
|
||||
font_size: int = DEFAULT_BUTTON_FONT_SIZE,
|
||||
font_weight: FontWeight = FontWeight.MEDIUM,
|
||||
button_style: ButtonStyle = ButtonStyle.NORMAL,
|
||||
is_enabled: bool = True,
|
||||
border_radius: int = 10, # Corner rounding in pixels
|
||||
text_alignment: TextAlignment = TextAlignment.CENTER,
|
||||
text_padding: int = 20, # Padding for left/right alignment
|
||||
icon=None,
|
||||
) -> int:
|
||||
button_id = f"{rect.x}_{rect.y}_{rect.width}_{rect.height}"
|
||||
result = 0
|
||||
|
||||
if button_style in (ButtonStyle.PRIMARY, ButtonStyle.DANGER) and not is_enabled:
|
||||
button_style = ButtonStyle.NORMAL
|
||||
|
||||
if button_style == ButtonStyle.ACTION and font_size == DEFAULT_BUTTON_FONT_SIZE:
|
||||
font_size = ACTION_BUTTON_FONT_SIZE
|
||||
|
||||
# Set background color based on button type
|
||||
bg_color = BUTTON_BACKGROUND_COLORS[button_style]
|
||||
mouse_over = is_enabled and rl.check_collision_point_rec(rl.get_mouse_position(), rect)
|
||||
is_pressed = button_id in _pressed_buttons
|
||||
|
||||
if mouse_over:
|
||||
if rl.is_mouse_button_pressed(rl.MouseButton.MOUSE_BUTTON_LEFT):
|
||||
# Only this button enters pressed state
|
||||
_pressed_buttons.add(button_id)
|
||||
is_pressed = True
|
||||
|
||||
# Use pressed color when mouse is down over this button
|
||||
if is_pressed and rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT):
|
||||
bg_color = BUTTON_PRESSED_BACKGROUND_COLORS[button_style]
|
||||
|
||||
# Handle button click
|
||||
if rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) and is_pressed:
|
||||
result = 1
|
||||
_pressed_buttons.remove(button_id)
|
||||
|
||||
# Clean up pressed state if mouse is released anywhere
|
||||
if rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT) and button_id in _pressed_buttons:
|
||||
_pressed_buttons.remove(button_id)
|
||||
|
||||
# Draw the button with rounded corners
|
||||
roundness = border_radius / (min(rect.width, rect.height) / 2)
|
||||
if button_style != ButtonStyle.TRANSPARENT:
|
||||
rl.draw_rectangle_rounded(rect, roundness, 20, bg_color)
|
||||
else:
|
||||
rl.draw_rectangle_rounded(rect, roundness, 20, rl.BLACK)
|
||||
rl.draw_rectangle_rounded_lines_ex(rect, roundness, 20, 2, rl.WHITE)
|
||||
|
||||
# Handle icon and text positioning
|
||||
font = gui_app.font(font_weight)
|
||||
text_size = measure_text_cached(font, text, font_size)
|
||||
text_pos = rl.Vector2(0, rect.y + (rect.height - text_size.y) // 2) # Vertical centering
|
||||
|
||||
# Draw icon if provided
|
||||
if icon:
|
||||
icon_y = rect.y + (rect.height - icon.height) / 2
|
||||
if text:
|
||||
if text_alignment == TextAlignment.LEFT:
|
||||
icon_x = rect.x + text_padding
|
||||
text_pos.x = icon_x + icon.width + ICON_PADDING
|
||||
elif text_alignment == TextAlignment.CENTER:
|
||||
total_width = icon.width + ICON_PADDING + text_size.x
|
||||
icon_x = rect.x + (rect.width - total_width) / 2
|
||||
text_pos.x = icon_x + icon.width + ICON_PADDING
|
||||
else: # RIGHT
|
||||
text_pos.x = rect.x + rect.width - text_size.x - text_padding
|
||||
icon_x = text_pos.x - ICON_PADDING - icon.width
|
||||
else:
|
||||
# Center icon when no text
|
||||
icon_x = rect.x + (rect.width - icon.width) / 2
|
||||
|
||||
rl.draw_texture_v(icon, rl.Vector2(icon_x, icon_y), rl.WHITE if is_enabled else rl.Color(255, 255, 255, 100))
|
||||
else:
|
||||
# No icon, position text normally
|
||||
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 if any
|
||||
if text:
|
||||
color = BUTTON_TEXT_COLOR[button_style] if is_enabled else BUTTON_DISABLED_TEXT_COLORS.get(button_style, rl.Color(228, 228, 228, 51))
|
||||
rl.draw_text_ex(font, text, text_pos, font_size, 0, color)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class Button(Widget):
|
||||
def __init__(self,
|
||||
@@ -182,7 +87,7 @@ class Button(Widget):
|
||||
border_radius: int = 10,
|
||||
text_alignment: TextAlignment = TextAlignment.CENTER,
|
||||
text_padding: int = 20,
|
||||
icon = None,
|
||||
icon=None,
|
||||
multi_touch: bool = False,
|
||||
):
|
||||
|
||||
@@ -200,6 +105,11 @@ class Button(Widget):
|
||||
def set_text(self, text):
|
||||
self._label.set_text(text)
|
||||
|
||||
def set_button_style(self, button_style: ButtonStyle):
|
||||
self._button_style = button_style
|
||||
self._background_color = BUTTON_BACKGROUND_COLORS[self._button_style]
|
||||
self._label.set_text_color(BUTTON_TEXT_COLOR[self._button_style])
|
||||
|
||||
def _update_state(self):
|
||||
if self.enabled:
|
||||
self._label.set_text_color(BUTTON_TEXT_COLOR[self._button_style])
|
||||
@@ -213,7 +123,11 @@ class Button(Widget):
|
||||
|
||||
def _render(self, _):
|
||||
roundness = self._border_radius / (min(self._rect.width, self._rect.height) / 2)
|
||||
rl.draw_rectangle_rounded(self._rect, roundness, 10, self._background_color)
|
||||
if self._button_style == ButtonStyle.TRANSPARENT_WHITE_BORDER:
|
||||
rl.draw_rectangle_rounded(self._rect, roundness, 10, rl.BLACK)
|
||||
rl.draw_rectangle_rounded_lines_ex(self._rect, roundness, 10, 2, rl.WHITE)
|
||||
else:
|
||||
rl.draw_rectangle_rounded(self._rect, roundness, 10, self._background_color)
|
||||
self._label.render(self._rect)
|
||||
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ from typing import Any
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
|
||||
from openpilot.system.ui.lib.wrap_text import wrap_text
|
||||
from openpilot.system.ui.widgets import Widget, DialogResult
|
||||
from openpilot.system.ui.widgets.button import gui_button, ButtonStyle
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.button import Button, ButtonStyle
|
||||
|
||||
|
||||
class ElementType(Enum):
|
||||
@@ -40,6 +40,7 @@ class HtmlRenderer(Widget):
|
||||
self._normal_font = gui_app.font(FontWeight.NORMAL)
|
||||
self._bold_font = gui_app.font(FontWeight.BOLD)
|
||||
self._scroll_panel = GuiScrollPanel()
|
||||
self._ok_button = Button("OK", click_callback=lambda: gui_app.set_modal_overlay(None), button_style=ButtonStyle.PRIMARY)
|
||||
|
||||
self.styles: dict[ElementType, dict[str, Any]] = {
|
||||
ElementType.H1: {"size": 68, "weight": FontWeight.BOLD, "color": rl.BLACK, "margin_top": 20, "margin_bottom": 16},
|
||||
@@ -126,10 +127,9 @@ class HtmlRenderer(Widget):
|
||||
button_x = content_rect.x + content_rect.width - button_width
|
||||
button_y = content_rect.y + content_rect.height - button_height
|
||||
button_rect = rl.Rectangle(button_x, button_y, button_width, button_height)
|
||||
if gui_button(button_rect, "OK", button_style=ButtonStyle.PRIMARY) == 1:
|
||||
return DialogResult.CONFIRM
|
||||
self._ok_button.render(button_rect)
|
||||
|
||||
return DialogResult.NO_ACTION
|
||||
return -1
|
||||
|
||||
def _render_content(self, rect: rl.Rectangle, scroll_offset: float = 0) -> float:
|
||||
current_y = rect.y + scroll_offset
|
||||
|
||||
@@ -6,7 +6,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.lib.wrap_text import wrap_text
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.button import Button, gui_button, ButtonStyle
|
||||
from openpilot.system.ui.widgets.button import Button, ButtonStyle
|
||||
from openpilot.system.ui.widgets.toggle import Toggle, WIDTH as TOGGLE_WIDTH, HEIGHT as TOGGLE_HEIGHT
|
||||
|
||||
ITEM_BASE_WIDTH = 600
|
||||
@@ -149,9 +149,16 @@ class DualButtonAction(ItemAction):
|
||||
right_callback: Callable = None, enabled: bool | Callable[[], bool] = True):
|
||||
super().__init__(width=0, enabled=enabled) # Width 0 means use full width
|
||||
self.left_text, self.right_text = left_text, right_text
|
||||
self.left_callback, self.right_callback = left_callback, right_callback
|
||||
|
||||
def _render(self, rect: rl.Rectangle) -> bool:
|
||||
self.left_button = Button(left_text, click_callback=left_callback, button_style=ButtonStyle.LIST_ACTION)
|
||||
self.right_button = Button(right_text, click_callback=right_callback, button_style=ButtonStyle.DANGER)
|
||||
|
||||
def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None:
|
||||
super().set_touch_valid_callback(touch_callback)
|
||||
self.left_button.set_touch_valid_callback(touch_callback)
|
||||
self.right_button.set_touch_valid_callback(touch_callback)
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
button_spacing = 30
|
||||
button_height = 120
|
||||
button_width = (rect.width - button_spacing) / 2
|
||||
@@ -160,16 +167,9 @@ class DualButtonAction(ItemAction):
|
||||
left_rect = rl.Rectangle(rect.x, button_y, button_width, button_height)
|
||||
right_rect = rl.Rectangle(rect.x + button_width + button_spacing, button_y, button_width, button_height)
|
||||
|
||||
left_clicked = gui_button(left_rect, self.left_text, button_style=ButtonStyle.LIST_ACTION) == 1
|
||||
right_clicked = gui_button(right_rect, self.right_text, button_style=ButtonStyle.DANGER) == 1
|
||||
|
||||
if left_clicked and self.left_callback:
|
||||
self.left_callback()
|
||||
return True
|
||||
if right_clicked and self.right_callback:
|
||||
self.right_callback()
|
||||
return True
|
||||
return False
|
||||
# Render buttons
|
||||
self.left_button.render(left_rect)
|
||||
self.right_button.render(right_rect)
|
||||
|
||||
|
||||
class MultipleButtonAction(ItemAction):
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import pyray as rl
|
||||
from openpilot.system.ui.lib.application import FontWeight
|
||||
from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel
|
||||
from openpilot.system.ui.lib.application import FontWeight, gui_app
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.button import gui_button, ButtonStyle, TextAlignment
|
||||
from openpilot.system.ui.widgets.button import Button, ButtonStyle, TextAlignment
|
||||
from openpilot.system.ui.widgets.label import gui_label
|
||||
from openpilot.system.ui.widgets.scroller import Scroller
|
||||
|
||||
# Constants
|
||||
MARGIN = 50
|
||||
@@ -22,7 +22,17 @@ class MultiOptionDialog(Widget):
|
||||
self.options = options
|
||||
self.current = current
|
||||
self.selection = current
|
||||
self.scroll = GuiScrollPanel()
|
||||
|
||||
# Create scroller with option buttons
|
||||
self.option_buttons = [Button(option, click_callback=lambda opt=option: self._on_option_clicked(opt),
|
||||
text_alignment=TextAlignment.LEFT, button_style=ButtonStyle.NORMAL) for option in options]
|
||||
self.scroller = Scroller(self.option_buttons, spacing=LIST_ITEM_SPACING)
|
||||
|
||||
self.cancel_button = Button("Cancel", click_callback=lambda: gui_app.set_modal_overlay(None))
|
||||
self.select_button = Button("Select", click_callback=lambda: gui_app.set_modal_overlay(None), button_style=ButtonStyle.PRIMARY)
|
||||
|
||||
def _on_option_clicked(self, option):
|
||||
self.selection = option
|
||||
|
||||
def _render(self, rect):
|
||||
dialog_rect = rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - 2 * MARGIN, rect.height - 2 * MARGIN)
|
||||
@@ -36,36 +46,26 @@ class MultiOptionDialog(Widget):
|
||||
# Options area
|
||||
options_y = content_rect.y + TITLE_FONT_SIZE + ITEM_SPACING
|
||||
options_h = content_rect.height - TITLE_FONT_SIZE - BUTTON_HEIGHT - 2 * ITEM_SPACING
|
||||
view_rect = rl.Rectangle(content_rect.x, options_y, content_rect.width, options_h)
|
||||
content_h = len(self.options) * (ITEM_HEIGHT + LIST_ITEM_SPACING)
|
||||
list_content_rect = rl.Rectangle(content_rect.x, options_y, content_rect.width, content_h)
|
||||
options_rect = rl.Rectangle(content_rect.x, options_y, content_rect.width, options_h)
|
||||
|
||||
# Scroll and render options
|
||||
offset = self.scroll.update(view_rect, list_content_rect)
|
||||
valid_click = self.scroll.is_touch_valid() and rl.is_mouse_button_released(rl.MouseButton.MOUSE_BUTTON_LEFT)
|
||||
|
||||
rl.begin_scissor_mode(int(view_rect.x), int(options_y), int(view_rect.width), int(options_h))
|
||||
# Update button styles and set width based on selection
|
||||
for i, option in enumerate(self.options):
|
||||
item_y = options_y + i * (ITEM_HEIGHT + LIST_ITEM_SPACING) + offset
|
||||
item_rect = rl.Rectangle(view_rect.x, item_y, view_rect.width, ITEM_HEIGHT)
|
||||
selected = option == self.selection
|
||||
button = self.option_buttons[i]
|
||||
button.set_button_style(ButtonStyle.PRIMARY if selected else ButtonStyle.NORMAL)
|
||||
button.set_rect(rl.Rectangle(0, 0, options_rect.width, ITEM_HEIGHT))
|
||||
|
||||
if rl.check_collision_recs(item_rect, view_rect):
|
||||
selected = option == self.selection
|
||||
style = ButtonStyle.PRIMARY if selected else ButtonStyle.NORMAL
|
||||
|
||||
if gui_button(item_rect, option, button_style=style, text_alignment=TextAlignment.LEFT) and valid_click:
|
||||
self.selection = option
|
||||
rl.end_scissor_mode()
|
||||
self.scroller.render(options_rect)
|
||||
|
||||
# Buttons
|
||||
button_y = content_rect.y + content_rect.height - BUTTON_HEIGHT
|
||||
button_w = (content_rect.width - BUTTON_SPACING) / 2
|
||||
|
||||
if gui_button(rl.Rectangle(content_rect.x, button_y, button_w, BUTTON_HEIGHT), "Cancel"):
|
||||
return 0
|
||||
cancel_rect = rl.Rectangle(content_rect.x, button_y, button_w, BUTTON_HEIGHT)
|
||||
self.cancel_button.render(cancel_rect)
|
||||
|
||||
if gui_button(rl.Rectangle(content_rect.x + button_w + BUTTON_SPACING, button_y, button_w, BUTTON_HEIGHT),
|
||||
"Select", is_enabled=self.selection != self.current, button_style=ButtonStyle.PRIMARY):
|
||||
return 1
|
||||
select_rect = rl.Rectangle(content_rect.x + button_w + BUTTON_SPACING, button_y, button_w, BUTTON_HEIGHT)
|
||||
self.select_button.set_enabled(self.selection != self.current)
|
||||
self.select_button.render(select_rect)
|
||||
|
||||
return -1
|
||||
|
||||
Reference in New Issue
Block a user