mirror of
https://github.com/infiniteCable2/openpilot.git
synced 2026-10-01 03:53:41 +08:00
ui: sunnypilot MultiButtonControl (#1478)
* commaai/openpilot:d05cb31e2e916fba41ba8167030945f427fd811b * bump opendbc * bump opendbc * bump opendbc * bump opendbc * bump opendbc * sunnypilot: remove Qt * cabana: revert to stock Qt * commaai/openpilot:5198b1b079c37742c1050f02ce0aa6dd42b038b9 * commaai/openpilot:954b567b9ba0f3d1ae57d6aa7797fa86dd92ec6e * commaai/openpilot:7534b2a160faa683412c04c1254440e338931c5e * sum more * bump opendbc * not yet * should've been symlink'ed * raylib says wut * quiet mode back * more fixes * no more * too extra red diff on the side * need to bring this back * too extra * let's update docs here * Revert "let's update docs here" This reverts commit 51fe03cd5121e6fdf14657b2c33852c34922b851. * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * multi-button * Lint * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * Panels. With Icons. And Scroller. * patience, grasshopper * more patience, grasshopper * sp raylib preview * fix callback * fix ui preview * add ui previews * better padding * this * support for next line multi-button * uhh * disabled colors * listitem -> listitemsp * listitem -> listitemsp * add show_description method * remove padding from line separator. like, WHY? 😩😩 * scroller -> scroller_tici * scroller -> scroller_tici * ui: `GuiApplicationExt` * add to readme * use gui_app.sunnypilot_ui() * use gui_app.sunnypilot_ui() * use in toggles panel * ugh. no * better & animated * lint * cleanup * lint. LINT * slight * default no inline ty <3 * style change * move stuff around --------- Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
This commit is contained in:
@@ -11,6 +11,7 @@ from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
|
||||
if gui_app.sunnypilot_ui():
|
||||
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp as toggle_item
|
||||
from openpilot.system.ui.sunnypilot.widgets.list_view import multiple_button_item_sp as multiple_button_item
|
||||
|
||||
PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants
|
||||
|
||||
@@ -99,7 +100,7 @@ class TogglesLayout(Widget):
|
||||
lambda: tr("Driving Personality"),
|
||||
lambda: tr(DESCRIPTIONS["LongitudinalPersonality"]),
|
||||
buttons=[lambda: tr("Aggressive"), lambda: tr("Standard"), lambda: tr("Relaxed")],
|
||||
button_width=255,
|
||||
button_width=300,
|
||||
callback=self._set_longitudinal_personality,
|
||||
selected_index=self._params.get("LongitudinalPersonality", return_default=True),
|
||||
icon="speed_limit.png"
|
||||
|
||||
@@ -24,6 +24,10 @@ class Base:
|
||||
TOGGLE_WIDTH = int(TOGGLE_HEIGHT * 1.75)
|
||||
TOGGLE_BG_HEIGHT = TOGGLE_HEIGHT - 20
|
||||
|
||||
# Button Control
|
||||
BUTTON_WIDTH = 300
|
||||
BUTTON_HEIGHT = 120
|
||||
|
||||
|
||||
@dataclass
|
||||
class DefaultStyleSP(Base):
|
||||
@@ -47,5 +51,10 @@ class DefaultStyleSP(Base):
|
||||
TOGGLE_DISABLED_OFF_COLOR = DISABLED_OFF_BG_COLOR
|
||||
TOGGLE_DISABLED_KNOB_COLOR = rl.Color(88, 88, 88, 255) # Lighter Grey
|
||||
|
||||
# Multi Button Control
|
||||
MBC_TRANSPARENT = rl.Color(255, 255, 255, 0)
|
||||
MBC_BG_CHECKED_ENABLED = rl.Color(0x69, 0x68, 0x68, 0xFF)
|
||||
MBC_DISABLED = rl.Color(0xFF, 0xFF, 0xFF, 0x33)
|
||||
|
||||
|
||||
style = DefaultStyleSP
|
||||
|
||||
@@ -7,9 +7,11 @@ See the LICENSE.md file in the root directory for more details.
|
||||
from collections.abc import Callable
|
||||
|
||||
import pyray as rl
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.system.ui.lib.application import MousePos
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.sunnypilot.widgets.toggle import ToggleSP
|
||||
from openpilot.system.ui.widgets.list_view import ListItem, ToggleAction, ItemAction
|
||||
from openpilot.system.ui.widgets.list_view import ListItem, ToggleAction, ItemAction, MultipleButtonAction, _resolve_value
|
||||
from openpilot.system.ui.sunnypilot.lib.styles import style
|
||||
|
||||
|
||||
@@ -20,11 +22,74 @@ class ToggleActionSP(ToggleAction):
|
||||
self.toggle = ToggleSP(initial_state=initial_state, callback=callback, param=param)
|
||||
|
||||
|
||||
class MultipleButtonActionSP(MultipleButtonAction):
|
||||
def __init__(self, buttons: list[str | Callable[[], str]], button_width: int, selected_index: int = 0, callback: Callable = None,
|
||||
param: str | None = None):
|
||||
MultipleButtonAction.__init__(self, buttons, button_width, selected_index, callback)
|
||||
self.param_key = param
|
||||
self.params = Params()
|
||||
if self.param_key:
|
||||
self.selected_button = int(self.params.get(self.param_key, return_default=True))
|
||||
self._anim_x: float | None = None
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
|
||||
button_y = rect.y + (rect.height - style.BUTTON_HEIGHT) / 2
|
||||
|
||||
total_width = len(self.buttons) * self.button_width
|
||||
track_rect = rl.Rectangle(rect.x, button_y, total_width, style.BUTTON_HEIGHT)
|
||||
|
||||
bg_color = style.MBC_TRANSPARENT
|
||||
text_color = style.ITEM_TEXT_COLOR if self.enabled else style.MBC_DISABLED
|
||||
highlight_color = style.MBC_BG_CHECKED_ENABLED if self.enabled else style.MBC_DISABLED
|
||||
|
||||
# background
|
||||
rl.draw_rectangle_rounded(track_rect, 0.2, 20, bg_color)
|
||||
|
||||
# border
|
||||
border_color = style.MBC_BG_CHECKED_ENABLED if self.enabled else style.MBC_DISABLED
|
||||
rl.draw_rectangle_rounded_lines_ex(track_rect, 0.2, 20, 2, border_color)
|
||||
|
||||
# highlight with animation
|
||||
target_x = rect.x + self.selected_button * self.button_width
|
||||
if not self._anim_x:
|
||||
self._anim_x = target_x
|
||||
self._anim_x += (target_x - self._anim_x) * 0.2
|
||||
|
||||
highlight_rect = rl.Rectangle(self._anim_x, button_y, self.button_width, style.BUTTON_HEIGHT)
|
||||
rl.draw_rectangle_rounded(highlight_rect, 0.2, 20, highlight_color)
|
||||
|
||||
# text
|
||||
for i, _text in enumerate(self.buttons):
|
||||
button_x = rect.x + i * self.button_width
|
||||
|
||||
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, text_color)
|
||||
|
||||
def _handle_mouse_release(self, mouse_pos: MousePos):
|
||||
MultipleButtonAction._handle_mouse_release(self, mouse_pos)
|
||||
if self.param_key:
|
||||
self.params.put(self.param_key, self.selected_button)
|
||||
|
||||
|
||||
class ListItemSP(ListItem):
|
||||
def __init__(self, title: str | Callable[[], str] = "", icon: str | None = None, description: str | Callable[[], str] | None = None,
|
||||
description_visible: bool = False, callback: Callable | None = None,
|
||||
action_item: ItemAction | None = None):
|
||||
action_item: ItemAction | None = None, inline: bool = True):
|
||||
ListItem.__init__(self, title, icon, description, description_visible, callback, action_item)
|
||||
self.inline = inline
|
||||
if not self.inline:
|
||||
self._rect.height += style.ITEM_BASE_HEIGHT/1.75
|
||||
|
||||
def get_item_height(self, font: rl.Font, max_width: int) -> float:
|
||||
height = super().get_item_height(font, max_width)
|
||||
if not self.inline:
|
||||
height = height + style.ITEM_BASE_HEIGHT/1.75
|
||||
return height
|
||||
|
||||
def show_description(self, show: bool):
|
||||
self._set_description_visible(show)
|
||||
@@ -33,6 +98,10 @@ class ListItemSP(ListItem):
|
||||
if not self.action_item:
|
||||
return rl.Rectangle(0, 0, 0, 0)
|
||||
|
||||
if not self.inline:
|
||||
action_y = item_rect.y + self._text_size.y + style.ITEM_PADDING * 3
|
||||
return rl.Rectangle(item_rect.x + style.ITEM_PADDING, action_y, item_rect.width - (style.ITEM_PADDING * 2), style.BUTTON_HEIGHT)
|
||||
|
||||
right_width = self.action_item.rect.width
|
||||
if right_width == 0: # Full width action (like DualButtonAction)
|
||||
return rl.Rectangle(item_rect.x + style.ITEM_PADDING, item_rect.y,
|
||||
@@ -47,6 +116,13 @@ class ListItemSP(ListItem):
|
||||
return rl.Rectangle(action_x, action_y, action_width, style.ITEM_BASE_HEIGHT)
|
||||
|
||||
def _render(self, _):
|
||||
if not self.is_visible:
|
||||
return
|
||||
|
||||
# Don't draw items that are not in parent's viewport
|
||||
if (self._rect.y + self.rect.height) <= self._parent_rect.y or self._rect.y >= (self._parent_rect.y + self._parent_rect.height):
|
||||
return
|
||||
|
||||
content_x = self._rect.x + style.ITEM_PADDING
|
||||
text_x = content_x
|
||||
left_action_item = isinstance(self.action_item, ToggleAction)
|
||||
@@ -62,8 +138,8 @@ class ListItemSP(ListItem):
|
||||
|
||||
# Draw title
|
||||
if self.title:
|
||||
text_size = measure_text_cached(self._font, self.title, style.ITEM_TEXT_FONT_SIZE)
|
||||
item_y = self._rect.y + (style.ITEM_BASE_HEIGHT - text_size.y) // 2
|
||||
self._text_size = measure_text_cached(self._font, self.title, style.ITEM_TEXT_FONT_SIZE)
|
||||
item_y = self._rect.y + (style.ITEM_BASE_HEIGHT - self._text_size.y) // 2
|
||||
rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), style.ITEM_TEXT_FONT_SIZE, 0, style.ITEM_TEXT_COLOR)
|
||||
|
||||
# Render toggle and handle callback
|
||||
@@ -74,14 +150,13 @@ class ListItemSP(ListItem):
|
||||
else:
|
||||
if self.title:
|
||||
# Draw main text
|
||||
text_size = measure_text_cached(self._font, self.title, style.ITEM_TEXT_FONT_SIZE)
|
||||
item_y = self._rect.y + (style.ITEM_BASE_HEIGHT - text_size.y) // 2
|
||||
self._text_size = measure_text_cached(self._font, self.title, style.ITEM_TEXT_FONT_SIZE)
|
||||
item_y = self._rect.y + (style.ITEM_BASE_HEIGHT - self._text_size.y) // 2 if self.inline else self._rect.y + style.ITEM_PADDING * 1.5
|
||||
rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), style.ITEM_TEXT_FONT_SIZE, 0, style.ITEM_TEXT_COLOR)
|
||||
|
||||
# Draw right item if present
|
||||
if self.action_item:
|
||||
right_rect = self.get_right_item_rect(self._rect)
|
||||
right_rect.y = self._rect.y
|
||||
if self.action_item.render(right_rect) and self.action_item.enabled:
|
||||
# Right item was clicked/activated
|
||||
if self.callback:
|
||||
@@ -91,12 +166,12 @@ class ListItemSP(ListItem):
|
||||
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
|
||||
)
|
||||
|
||||
desc_y = self._rect.y + style.ITEM_DESC_V_OFFSET
|
||||
if not self.inline and self.action_item:
|
||||
desc_y = self.action_item.rect.y + style.ITEM_DESC_V_OFFSET - style.ITEM_PADDING * 1.75
|
||||
|
||||
description_rect = rl.Rectangle(self._rect.x + style.ITEM_PADDING, desc_y, content_width, description_height)
|
||||
self._html_renderer.render(description_rect)
|
||||
|
||||
|
||||
@@ -104,3 +179,10 @@ def toggle_item_sp(title: str | Callable[[], str], description: str | Callable[[
|
||||
callback: Callable | None = None, icon: str = "", enabled: bool | Callable[[], bool] = True, param: str | None = None) -> ListItemSP:
|
||||
action = ToggleActionSP(initial_state=initial_state, enabled=enabled, callback=callback, param=param)
|
||||
return ListItemSP(title=title, description=description, action_item=action, icon=icon, callback=callback)
|
||||
|
||||
|
||||
def multiple_button_item_sp(title: str | Callable[[], str], description: str | Callable[[], str], buttons: list[str | Callable[[], str]],
|
||||
selected_index: int = 0, button_width: int = style.BUTTON_WIDTH, callback: Callable = None,
|
||||
icon: str = "", param: str | None = None, inline: bool = False) -> ListItemSP:
|
||||
action = MultipleButtonActionSP(buttons, button_width, selected_index, callback=callback, param=param)
|
||||
return ListItemSP(title=title, description=description, icon=icon, action_item=action, inline=inline)
|
||||
|
||||
@@ -16,7 +16,9 @@ from openpilot.system.ui.widgets.scroller_tici import Scroller
|
||||
from openpilot.system.ui.widgets.list_view import ButtonAction, ListItem, MultipleButtonAction, ToggleAction, button_item, text_item
|
||||
|
||||
if gui_app.sunnypilot_ui():
|
||||
from openpilot.system.ui.sunnypilot.widgets.list_view import ListItemSP as ListItem
|
||||
from openpilot.system.ui.sunnypilot.widgets.list_view import ToggleActionSP as ToggleAction
|
||||
from openpilot.system.ui.sunnypilot.widgets.list_view import MultipleButtonActionSP as MultipleButtonAction
|
||||
|
||||
# These are only used for AdvancedNetworkSettings, standalone apps just need WifiManagerUI
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user