mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-02-18 20:03:53 +08:00
SP Toggles
This commit is contained in:
@@ -9,6 +9,9 @@ from openpilot.system.ui.lib.application import gui_app
|
||||
from openpilot.system.ui.lib.multilang import tr, tr_noop
|
||||
from openpilot.system.ui.widgets import DialogResult
|
||||
|
||||
if Params().get_bool("sunnypilot_ui"):
|
||||
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp as toggle_item
|
||||
|
||||
# Description constants
|
||||
DESCRIPTIONS = {
|
||||
'enable_adb': tr_noop(
|
||||
|
||||
@@ -9,6 +9,9 @@ from openpilot.system.ui.lib.multilang import tr, tr_noop
|
||||
from openpilot.system.ui.widgets import DialogResult
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
|
||||
if Params().get_bool("sunnypilot_ui"):
|
||||
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp as toggle_item
|
||||
|
||||
PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants
|
||||
|
||||
# Description constants
|
||||
|
||||
97
system/ui/sunnypilot/widgets/list_view.py
Normal file
97
system/ui/sunnypilot/widgets/list_view.py
Normal file
@@ -0,0 +1,97 @@
|
||||
import pyray as rl
|
||||
|
||||
from collections.abc import Callable
|
||||
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
|
||||
|
||||
|
||||
import openpilot.system.ui.sunnypilot.lib.styles as styles
|
||||
style = styles.Default
|
||||
|
||||
|
||||
class ToggleActionSP(ToggleAction):
|
||||
def __init__(self, initial_state: bool = False, width: int = style.TOGGLE_WIDTH, enabled: bool | Callable[[], bool] = True, param: str | None = None):
|
||||
ToggleAction.__init__(self, initial_state, width, enabled)
|
||||
self.toggle = ToggleSP(initial_state=initial_state, param=param)
|
||||
|
||||
class ListItemSP(ListItem):
|
||||
def __init__(self, title: str = "", icon: str | None = None, description: str | Callable[[], str] | None = None,
|
||||
description_visible: bool = False, callback: Callable | None = None,
|
||||
action_item: ItemAction | None = None):
|
||||
ListItem.__init__(self, title, icon, description, description_visible, callback, action_item)
|
||||
|
||||
def get_right_item_rect(self, item_rect: rl.Rectangle) -> rl.Rectangle:
|
||||
if not self.action_item:
|
||||
return rl.Rectangle(0, 0, 0, 0)
|
||||
|
||||
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,
|
||||
item_rect.width - (style.ITEM_PADDING * 2), style.ITEM_BASE_HEIGHT)
|
||||
|
||||
action_width = self.action_item.rect.width
|
||||
if isinstance(self.action_item, ToggleAction):
|
||||
action_x = item_rect.x
|
||||
else:
|
||||
action_x = item_rect.x + item_rect.width - action_width
|
||||
action_y = item_rect.y
|
||||
return rl.Rectangle(action_x, action_y, action_width, style.ITEM_BASE_HEIGHT)
|
||||
|
||||
def _render(self, _):
|
||||
content_x = self._rect.x + style.ITEM_PADDING
|
||||
text_x = content_x
|
||||
left_action_item = isinstance(self.action_item, ToggleAction)
|
||||
|
||||
if left_action_item:
|
||||
left_rect = rl.Rectangle(
|
||||
content_x,
|
||||
self._rect.y + (style.ITEM_BASE_HEIGHT - style.TOGGLE_HEIGHT) // 2,
|
||||
style.TOGGLE_WIDTH,
|
||||
style.TOGGLE_HEIGHT
|
||||
)
|
||||
text_x = left_rect.x + left_rect.width + style.ITEM_PADDING
|
||||
|
||||
# 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
|
||||
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
|
||||
if self.action_item.render(left_rect) and self.action_item.enabled:
|
||||
if self.callback:
|
||||
self.callback()
|
||||
|
||||
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
|
||||
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:
|
||||
self.callback()
|
||||
|
||||
# Draw description if visible
|
||||
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
|
||||
)
|
||||
self._html_renderer.render(description_rect)
|
||||
|
||||
def toggle_item_sp(title: str, description: str | Callable[[], str] | None = None, initial_state: bool = False,
|
||||
callback: Callable | None = None, icon: str = "", enabled: bool | Callable[[], bool] = True, param: str | None = None) -> ListItem:
|
||||
action = ToggleActionSP(initial_state=initial_state, enabled=enabled, param=param)
|
||||
return ListItemSP(title=title, description=description, action_item=action, icon=icon, callback=callback)
|
||||
100
system/ui/sunnypilot/widgets/toggle.py
Normal file
100
system/ui/sunnypilot/widgets/toggle.py
Normal file
@@ -0,0 +1,100 @@
|
||||
import pyray as rl
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.system.ui.lib.application import MousePos
|
||||
from openpilot.system.ui.widgets.toggle import Toggle
|
||||
import openpilot.system.ui.sunnypilot.lib.styles as styles
|
||||
|
||||
style = styles.Default
|
||||
|
||||
class ToggleSP(Toggle):
|
||||
def __init__(self, initial_state=False, param: str | None = None):
|
||||
self.param_key = param
|
||||
self.params = Params()
|
||||
if self.param_key:
|
||||
initial_state = self.params.get_bool(self.param_key)
|
||||
Toggle.__init__(self, initial_state)
|
||||
|
||||
def _handle_mouse_release(self, mouse_pos: MousePos):
|
||||
super()._handle_mouse_release(mouse_pos)
|
||||
if self._enabled and self.param_key:
|
||||
self.params.put_bool(self.param_key, self._state)
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
self.update()
|
||||
if self._enabled:
|
||||
bg_color = self._blend_color(style.TOGGLE_OFF_COLOR, style.TOGGLE_ON_COLOR, self._progress)
|
||||
knob_color = style.TOGGLE_KNOB_COLOR
|
||||
else:
|
||||
bg_color = self._blend_color(style.TOGGLE_DISABLED_OFF_COLOR, style.TOGGLE_DISABLED_ON_COLOR, self._progress)
|
||||
knob_color = style.TOGGLE_DISABLED_KNOB_COLOR
|
||||
|
||||
# Draw background
|
||||
bg_rect = rl.Rectangle(self._rect.x, self._rect.y, style.TOGGLE_WIDTH, style.TOGGLE_BG_HEIGHT)
|
||||
|
||||
# Draw outline first
|
||||
outline_color = style.TOGGLE_ON_COLOR
|
||||
if not self._enabled:
|
||||
# Use a more subtle color for disabled state
|
||||
outline_color = rl.Color(outline_color.r // 2, outline_color.g // 2, outline_color.b // 2, 255)
|
||||
|
||||
# Draw outline by drawing a slightly larger rounded rectangle behind the background
|
||||
outline_rect = rl.Rectangle(bg_rect.x - 2, bg_rect.y - 2, bg_rect.width + 4, bg_rect.height + 4)
|
||||
rl.draw_rectangle_rounded(outline_rect, 1.0, 10, outline_color)
|
||||
|
||||
# Draw actual background
|
||||
rl.draw_rectangle_rounded(bg_rect, 1.0, 10, bg_color)
|
||||
|
||||
# Draw knob to sit inside the background
|
||||
knob_padding = 5
|
||||
knob_radius = style.TOGGLE_BG_HEIGHT / 2 - knob_padding
|
||||
|
||||
left_edge = bg_rect.x + knob_padding
|
||||
right_edge = bg_rect.x + bg_rect.width - knob_padding
|
||||
|
||||
knob_travel_distance = right_edge - left_edge - 2 * knob_radius
|
||||
min_knob_x = left_edge + knob_radius
|
||||
knob_x = min_knob_x + knob_travel_distance * self._progress
|
||||
knob_y = self._rect.y + style.TOGGLE_BG_HEIGHT / 2
|
||||
|
||||
rl.draw_circle(int(knob_x), int(knob_y), knob_radius, knob_color)
|
||||
|
||||
symbol_size = knob_radius / 2
|
||||
|
||||
if self._state and (self._enabled or self._progress > 0.5):
|
||||
# Draw checkmark when toggle is ON
|
||||
start_x = knob_x - symbol_size * 0.8
|
||||
start_y = knob_y
|
||||
mid_x = knob_x - symbol_size * 0.1
|
||||
mid_y = knob_y + symbol_size * 0.6
|
||||
end_x = knob_x + symbol_size * 0.8
|
||||
end_y = knob_y - symbol_size * 0.5
|
||||
|
||||
rl.draw_line_ex(
|
||||
rl.Vector2(int(start_x), int(start_y)),
|
||||
rl.Vector2(int(mid_x), int(mid_y)),
|
||||
3,
|
||||
style.TOGGLE_ON_COLOR
|
||||
)
|
||||
rl.draw_line_ex(
|
||||
rl.Vector2(int(mid_x), int(mid_y)),
|
||||
rl.Vector2(int(end_x), int(end_y)),
|
||||
3,
|
||||
style.TOGGLE_ON_COLOR
|
||||
)
|
||||
else:
|
||||
# Draw X when toggle is OFF
|
||||
x_size_factor = 0.65
|
||||
x_offset = symbol_size * x_size_factor
|
||||
|
||||
rl.draw_line_ex(
|
||||
rl.Vector2(int(knob_x - x_offset), int(knob_y - x_offset)),
|
||||
rl.Vector2(int(knob_x + x_offset), int(knob_y + x_offset)),
|
||||
3,
|
||||
style.TOGGLE_OFF_COLOR
|
||||
)
|
||||
rl.draw_line_ex(
|
||||
rl.Vector2(int(knob_x + x_offset), int(knob_y - x_offset)),
|
||||
rl.Vector2(int(knob_x - x_offset), int(knob_y + x_offset)),
|
||||
3,
|
||||
style.TOGGLE_OFF_COLOR
|
||||
)
|
||||
Reference in New Issue
Block a user