mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-06-08 16:04:50 +08:00
Compare commits
13 Commits
fca-temp-f
...
pyui/sp-to
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38bb1407c9 | ||
|
|
613e8d27b5 | ||
|
|
3af7841a5d | ||
|
|
5c2c6a9c75 | ||
|
|
acfbfea41d | ||
|
|
1dee05cb1f | ||
|
|
3ff8ccfc23 | ||
|
|
c1f251a34f | ||
|
|
29e76a1e38 | ||
|
|
3384771117 | ||
|
|
a6dfe14487 | ||
|
|
b9c3ef57d1 | ||
|
|
9b5228ec08 |
@@ -156,6 +156,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"ShowAdvancedControls", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"sunnypilot_ui", {PERSISTENT, BOOL, "1"}},
|
||||
|
||||
// MADS params
|
||||
{"Mads", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||
|
||||
@@ -4,6 +4,9 @@ from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.list_view import toggle_item
|
||||
from openpilot.system.ui.widgets.scroller import Scroller
|
||||
|
||||
if Params().get_bool("sunnypilot_ui"):
|
||||
from openpilot.system.ui.sunnypilot.lib.list_view import toggle_item_sp as toggle_item
|
||||
|
||||
# Description constants
|
||||
DESCRIPTIONS = {
|
||||
'enable_adb': (
|
||||
|
||||
@@ -3,6 +3,9 @@ from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.list_view import multiple_button_item, toggle_item
|
||||
from openpilot.system.ui.widgets.scroller import Scroller
|
||||
|
||||
if Params().get_bool("sunnypilot_ui"):
|
||||
from openpilot.system.ui.sunnypilot.lib.list_view import toggle_item_sp as toggle_item
|
||||
|
||||
# Description constants
|
||||
DESCRIPTIONS = {
|
||||
"OpenpilotEnabledToggle": (
|
||||
|
||||
121
system/ui/sunnypilot/lib/list_view.py
Normal file
121
system/ui/sunnypilot/lib/list_view.py
Normal file
@@ -0,0 +1,121 @@
|
||||
import pyray as rl
|
||||
|
||||
from openpilot.system.ui.widgets.list_view import ToggleAction, ListItem, ItemAction
|
||||
from openpilot.system.ui.lib.wrap_text import wrap_text
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from collections.abc import Callable
|
||||
|
||||
from openpilot.system.ui.sunnypilot.lib.toggle import ToggleSP
|
||||
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):
|
||||
ToggleAction.__init__(self, initial_state, width, enabled)
|
||||
self.toggle = ToggleSP(initial_state=initial_state)
|
||||
|
||||
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_item_height(self, font: rl.Font, max_width: int) -> float:
|
||||
if not self.is_visible:
|
||||
return 0
|
||||
|
||||
total_width = self._rect.width - (2 * style.ITEM_PADDING) # Full width minus padding
|
||||
max_width = int(total_width - (2 * style.ITEM_PADDING))
|
||||
|
||||
current_description = self.get_description()
|
||||
if self.description_visible and current_description:
|
||||
if (
|
||||
not self._wrapped_description
|
||||
or current_description != self._prev_description
|
||||
or max_width != self._prev_max_width
|
||||
):
|
||||
self._prev_max_width = max_width
|
||||
self._prev_description = current_description
|
||||
|
||||
wrapped_lines = wrap_text(self._font, current_description, style.ITEM_DESC_FONT_SIZE, max_width)
|
||||
self._wrapped_description = "\n".join(wrapped_lines)
|
||||
self._description_height = len(wrapped_lines) * style.ITEM_DESC_FONT_SIZE + 10
|
||||
return style.ITEM_BASE_HEIGHT + self._description_height - (style.ITEM_BASE_HEIGHT - style.ITEM_DESC_V_OFFSET) + style.ITEM_PADDING
|
||||
return style.ITEM_BASE_HEIGHT
|
||||
|
||||
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
|
||||
current_description = self.get_description()
|
||||
if self.description_visible and current_description and self._wrapped_description:
|
||||
rl.draw_text_ex(
|
||||
self._font,
|
||||
self._wrapped_description,
|
||||
rl.Vector2(content_x, self._rect.y + style.ITEM_DESC_V_OFFSET),
|
||||
style.ITEM_DESC_FONT_SIZE,
|
||||
0,
|
||||
style.ITEM_DESC_TEXT_COLOR,
|
||||
)
|
||||
|
||||
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) -> ListItem:
|
||||
action = ToggleActionSP(initial_state=initial_state, enabled=enabled)
|
||||
return ListItemSP(title=title, description=description, action_item=action, icon=icon, callback=callback)
|
||||
34
system/ui/sunnypilot/lib/styles.py
Normal file
34
system/ui/sunnypilot/lib/styles.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import pyray as rl
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class Default:
|
||||
# Base Colors
|
||||
ON_BG_COLOR = rl.Color(28, 101, 186, 255) # Blue
|
||||
OFF_BG_COLOR = rl.Color(39, 39, 39, 255) # Grey
|
||||
DISABLED_ON_BG_COLOR = rl.Color(37, 70, 107, 255) # Dull Blue
|
||||
DISABLED_OFF_BG_COLOR = rl.Color(39, 39, 39, 255) # Grey
|
||||
ITEM_TEXT_COLOR = rl.WHITE
|
||||
ITEM_DESC_TEXT_COLOR = rl.Color(128, 128, 128, 255)
|
||||
|
||||
# Widget/Control Dimensions
|
||||
ITEM_BASE_HEIGHT = 170
|
||||
ITEM_PADDING = 30
|
||||
ITEM_TEXT_FONT_SIZE = 50
|
||||
ITEM_DESC_FONT_SIZE = 40
|
||||
ITEM_DESC_V_OFFSET = 150
|
||||
|
||||
TOGGLE_HEIGHT = 100
|
||||
TOGGLE_WIDTH = int(TOGGLE_HEIGHT * 1.75)
|
||||
TOGGLE_BG_HEIGHT = TOGGLE_HEIGHT - 20
|
||||
|
||||
BUTTON_WIDTH = 250
|
||||
BUTTON_HEIGHT = 100
|
||||
|
||||
# Toggle Colors
|
||||
TOGGLE_ON_COLOR = ON_BG_COLOR
|
||||
TOGGLE_OFF_COLOR = OFF_BG_COLOR
|
||||
TOGGLE_KNOB_COLOR = rl.WHITE
|
||||
TOGGLE_DISABLED_ON_COLOR = DISABLED_ON_BG_COLOR
|
||||
TOGGLE_DISABLED_OFF_COLOR = DISABLED_OFF_BG_COLOR
|
||||
TOGGLE_DISABLED_KNOB_COLOR = rl.Color(88, 88, 88, 255) # Lighter Grey
|
||||
89
system/ui/sunnypilot/lib/toggle.py
Normal file
89
system/ui/sunnypilot/lib/toggle.py
Normal file
@@ -0,0 +1,89 @@
|
||||
import pyray as rl
|
||||
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):
|
||||
Toggle.__init__(self, initial_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