mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-06-25 22:52:07 +08:00
91b7752268
* pressed state for larger sliders
* wifibutton
* fix
* clean up
* some work
* don't nee this now
* stash
* more
* new pressed bigcircle
* black
* interp
* just check position
* clean up and fix slider reset
* fix custom
* no speed
* stash
* even chatter couldn't figure this one out
* makes sense to combine together, less split mentality
* clean that up
* fix lag
* match ui.py prio to eliminate lag on wifiui show event. separately, why is this slow?
* night mode
* delay scroll over
* fix auto scrolling
* stash
* waiting looks disabled
* clean up and don't reset sliders until user goes back
* rm
* fix
* add termsheader back
* fix callbacks
* ctrl alt l
* fix text spacing
* clean up
* stash
* fix style
* i want to go back
* guard on exit
* kinda useless stuff
* Revert "kinda useless stuff"
This reverts commit a4acbac31523408f358c5f68262cb630aa13ad8e.
* Revert "guard on exit"
This reverts commit 63ccfbf64edfbe1a144a441681f5ec78d8021ff7.
* wide
* setup pressed!
* grow animation
* 10s after initial
* slow fast
* start onboarding (terms)
* rm duplicate page
* add qr code
* final grey
* fix visual lag on first start
* clean up dead code
* dont exit from cancel
* revert grey
* clean up, REVIEW ME
* Revert "clean up, REVIEW ME"
This reverts commit c66fa60947c5f922520e7cf58c630b4bbe2d0177.
* reboot slider
* kb fix
* Revert "kb fix"
This reverts commit 883039448e6c37ae1d25d4f75ada6e96b6736358.
* ./ goes to letters
* Revert "./ goes to letters"
This reverts commit 0d97442427edb1a000638863a3f2181204ddc160.
* clean up
* some more clean up
* more
* clean up
* rename block
* reset pending scroll so it can't use stale data in rare sequence
* remove unused assets
* clean up imports
* fix updater
* clean up
* fix double reboot
* demo time - reset to setup on reboot
* let manager restart
* Revert "demo time - reset to setup on reboot"
This reverts commit 9468657e8438a1ce8fcb5266403b7bb3539f131f.
* url... and no grow animation on start button
* one next button
* grow instead of shake wifi button
* 36 pt font size in setup
* touch up onboarding a lil
* Revert "rm cpp bz2 (#37332)"
This reverts commit f4a36f7f74.
* more onboarding and clean up
* clean up
* wow what an amazing future clean up
* back to software select
* fix
* copy
* fix dm confirmation dialog not disabling widget underneath, all fixed with real nav stack in here
* uploading
* lint
* add review terms to device w/ close button
* todo
* remove old Terms vertical scrolling classes
* use new Scroller!
* installer
* tweak to match figma exactly
* revert
* fixup updater
* demo day
* demo day v2
* ... for percent while finishing setup
* demo day v3
* demo day v4
* remove ...
* demo day v6 -- "why does it do that!!"
* demo day v7 -- no flash
* hmm
* demo day v7
* prebuilt
* revert demo day
* scroll after pop animation
* back -> retry
* stash fixes
* damn, need back_callback
* scroll over immediately if already in network setup
* tweaks
* going down is confusing
* more
* Revert "more"
This reverts commit 29ce75b1f81eb40e7527a71d27842d9a66802206.
* Revert "going down is confusing"
This reverts commit 0cd2ae30d4135db1ccba6478429b45e886714e9c.
* dupl
* nl
* sort functions
* more clean up from merge
* move
* more
* dismiss to download (hack)
* Revert "dismiss to download (hack)"
This reverts commit 53c45ed1f63db1f0cebbce0dfab1777c8658f505.
* onboarding work
* set brightness and timeout in root onboarding only
* clean up
* type
* keep 5m for settings preview
* switch back to letters on . or /
* reset first step scroller
* custom software warning goes down network comes up and back cb fix
* clean up
* smaller qr
* ReviewTermsPage just for device as NavWidget
* clean up
* installer: stay on 100%
* reset has internet while in wifiui
* try this
* try this
* see what error we get exactly
see what error we get exactly
* not final solution but see how good
* rm
* copy changes
* reset on disconnect
* for separate pr
* Revert "reset on disconnect"
This reverts commit 552372fa4d497ba7d9de7f2edb730ee63798ffa4.
* revert this, too buggy
* fix for updater
* sort
* fix test
* minor cleanup
* more leaks than this rn
* onboarding clean up
* clean up application
* click delay to small button
* clean up
* reset more state
* fix training guide not cleaning up driverview
* Revert "fix training guide not cleaning up driverview"
This reverts commit cac7c5f436056cc9e747f80905d390790fb83c22.
* simpler fix :(
* nice catch, if you go back to terms it will reset 300s timeout and brightness
* duplicate show
* unused
305 lines
12 KiB
Python
305 lines
12 KiB
Python
from collections.abc import Callable
|
|
from enum import IntEnum
|
|
|
|
import pyray as rl
|
|
|
|
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
|
|
from openpilot.system.ui.widgets import Widget
|
|
from openpilot.system.ui.widgets.label import Label, UnifiedLabel
|
|
from openpilot.common.filter_simple import FirstOrderFilter
|
|
|
|
|
|
class ButtonStyle(IntEnum):
|
|
NORMAL = 0 # Most common, neutral buttons
|
|
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 = 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
|
|
KEYBOARD = 7
|
|
FORGET_WIFI = 8
|
|
|
|
|
|
ICON_PADDING = 15
|
|
DEFAULT_BUTTON_FONT_SIZE = 60
|
|
ACTION_BUTTON_FONT_SIZE = 48
|
|
|
|
BUTTON_TEXT_COLOR = {
|
|
ButtonStyle.NORMAL: rl.Color(228, 228, 228, 255),
|
|
ButtonStyle.PRIMARY: rl.Color(228, 228, 228, 255),
|
|
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),
|
|
ButtonStyle.KEYBOARD: rl.Color(221, 221, 221, 255),
|
|
ButtonStyle.FORGET_WIFI: rl.Color(51, 51, 51, 255),
|
|
}
|
|
|
|
BUTTON_DISABLED_TEXT_COLORS = {
|
|
ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.WHITE,
|
|
}
|
|
|
|
BUTTON_BACKGROUND_COLORS = {
|
|
ButtonStyle.NORMAL: rl.Color(51, 51, 51, 255),
|
|
ButtonStyle.PRIMARY: rl.Color(70, 91, 234, 255),
|
|
ButtonStyle.DANGER: rl.Color(226, 44, 44, 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),
|
|
ButtonStyle.KEYBOARD: rl.Color(68, 68, 68, 255),
|
|
ButtonStyle.FORGET_WIFI: rl.Color(189, 189, 189, 255),
|
|
}
|
|
|
|
BUTTON_PRESSED_BACKGROUND_COLORS = {
|
|
ButtonStyle.NORMAL: rl.Color(74, 74, 74, 255),
|
|
ButtonStyle.PRIMARY: rl.Color(48, 73, 244, 255),
|
|
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),
|
|
ButtonStyle.KEYBOARD: rl.Color(51, 51, 51, 255),
|
|
ButtonStyle.FORGET_WIFI: rl.Color(130, 130, 130, 255),
|
|
}
|
|
|
|
BUTTON_DISABLED_BACKGROUND_COLORS = {
|
|
ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.BLANK,
|
|
}
|
|
|
|
|
|
class Button(Widget):
|
|
def __init__(self,
|
|
text: str | Callable[[], str],
|
|
click_callback: Callable[[], None] | None = None,
|
|
font_size: int = DEFAULT_BUTTON_FONT_SIZE,
|
|
font_weight: FontWeight = FontWeight.MEDIUM,
|
|
button_style: ButtonStyle = ButtonStyle.NORMAL,
|
|
border_radius: int = 10,
|
|
text_alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
|
|
text_padding: int = 20,
|
|
icon=None,
|
|
elide_right: bool = False,
|
|
multi_touch: bool = False,
|
|
):
|
|
|
|
super().__init__()
|
|
self._button_style = button_style
|
|
self._border_radius = border_radius
|
|
self._background_color = BUTTON_BACKGROUND_COLORS[self._button_style]
|
|
|
|
self._label = Label(text, font_size, font_weight, text_alignment, text_padding=text_padding,
|
|
text_color=BUTTON_TEXT_COLOR[self._button_style], icon=icon, elide_right=elide_right)
|
|
|
|
self._click_callback = click_callback
|
|
self._multi_touch = multi_touch
|
|
|
|
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])
|
|
if self.is_pressed:
|
|
self._background_color = BUTTON_PRESSED_BACKGROUND_COLORS[self._button_style]
|
|
else:
|
|
self._background_color = BUTTON_BACKGROUND_COLORS[self._button_style]
|
|
elif self._button_style != ButtonStyle.NO_EFFECT:
|
|
self._background_color = BUTTON_DISABLED_BACKGROUND_COLORS.get(self._button_style, rl.Color(51, 51, 51, 255))
|
|
self._label.set_text_color(BUTTON_DISABLED_TEXT_COLORS.get(self._button_style, rl.Color(228, 228, 228, 51)))
|
|
|
|
def _render(self, _):
|
|
roundness = self._border_radius / (min(self._rect.width, self._rect.height) / 2)
|
|
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)
|
|
|
|
|
|
class ButtonRadio(Button):
|
|
def __init__(self,
|
|
text: str,
|
|
icon,
|
|
click_callback: Callable[[], None] | None = None,
|
|
font_size: int = DEFAULT_BUTTON_FONT_SIZE,
|
|
text_alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_LEFT,
|
|
border_radius: int = 10,
|
|
text_padding: int = 20,
|
|
):
|
|
|
|
super().__init__(text, click_callback=click_callback, font_size=font_size,
|
|
border_radius=border_radius, text_padding=text_padding,
|
|
text_alignment=text_alignment)
|
|
self._text_padding = text_padding
|
|
self._icon = icon
|
|
self.selected = False
|
|
|
|
def _handle_mouse_release(self, mouse_pos: MousePos):
|
|
super()._handle_mouse_release(mouse_pos)
|
|
self.selected = not self.selected
|
|
|
|
def _update_state(self):
|
|
if self.selected:
|
|
self._background_color = BUTTON_BACKGROUND_COLORS[ButtonStyle.PRIMARY]
|
|
else:
|
|
self._background_color = BUTTON_BACKGROUND_COLORS[ButtonStyle.NORMAL]
|
|
|
|
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)
|
|
self._label.render(self._rect)
|
|
|
|
if self._icon and self.selected:
|
|
icon_y = self._rect.y + (self._rect.height - self._icon.height) / 2
|
|
icon_x = self._rect.x + self._rect.width - self._icon.width - self._text_padding - ICON_PADDING
|
|
rl.draw_texture_v(self._icon, rl.Vector2(icon_x, icon_y), rl.WHITE if self.enabled else rl.Color(255, 255, 255, 100))
|
|
|
|
|
|
class IconButton(Widget):
|
|
def __init__(self, texture: rl.Texture):
|
|
super().__init__()
|
|
self._texture = texture
|
|
self._opacity_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps)
|
|
self.set_rect(rl.Rectangle(0, 0, self._texture.width, self._texture.height))
|
|
|
|
def set_opacity(self, opacity: float, smooth: bool = False):
|
|
if smooth:
|
|
self._opacity_filter.update(opacity)
|
|
else:
|
|
self._opacity_filter.x = opacity
|
|
|
|
def _render(self, rect: rl.Rectangle):
|
|
color = rl.Color(180, 180, 180, int(150 * self._opacity_filter.x)) if self.is_pressed else rl.WHITE
|
|
if not self.enabled:
|
|
color = rl.Color(255, 255, 255, int(255 * 0.9 * 0.35 * self._opacity_filter.x))
|
|
draw_x = rect.x + (rect.width - self._texture.width) / 2
|
|
draw_y = rect.y + (rect.height - self._texture.height) / 2
|
|
rl.draw_texture(self._texture, int(draw_x), int(draw_y), color)
|
|
|
|
|
|
class SmallCircleIconButton(Widget):
|
|
def __init__(self, icon_txt: rl.Texture):
|
|
super().__init__()
|
|
self.set_rect(rl.Rectangle(0, 0, 100, 100))
|
|
self._opacity_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps)
|
|
self._icon_bg_txt = gui_app.texture("icons_mici/setup/small_button.png", 100, 100)
|
|
self._icon_bg_pressed_txt = gui_app.texture("icons_mici/setup/small_button_pressed.png", 100, 100)
|
|
self._icon_bg_disabled_txt = gui_app.texture("icons_mici/setup/small_button_disabled.png", 100, 100)
|
|
self._icon_txt = icon_txt
|
|
|
|
def set_opacity(self, opacity: float, smooth: bool = False):
|
|
if smooth:
|
|
self._opacity_filter.update(opacity)
|
|
else:
|
|
self._opacity_filter.x = opacity
|
|
|
|
def _render(self, _):
|
|
white = rl.Color(255, 255, 255, int(255 * self._opacity_filter.x))
|
|
if not self.enabled:
|
|
bg_txt = self._icon_bg_disabled_txt
|
|
icon_white = rl.Color(255, 255, 255, int(white.a * 0.35))
|
|
else:
|
|
bg_txt = self._icon_bg_pressed_txt if self.is_pressed else self._icon_bg_txt
|
|
icon_white = white
|
|
|
|
rl.draw_texture(bg_txt, int(self.rect.x), int(self.rect.y), white)
|
|
icon_x = self.rect.x + (self.rect.width - self._icon_txt.width) / 2
|
|
icon_y = self.rect.y + (self.rect.height - self._icon_txt.height) / 2
|
|
rl.draw_texture(self._icon_txt, int(icon_x), int(icon_y), icon_white)
|
|
|
|
|
|
class SmallButton(Widget):
|
|
def __init__(self, text: str):
|
|
super().__init__()
|
|
self._click_delay = 0.075
|
|
self._opacity_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps)
|
|
|
|
self._load_assets()
|
|
|
|
self._label = UnifiedLabel(text, 36, font_weight=FontWeight.SEMI_BOLD,
|
|
text_color=rl.Color(255, 255, 255, int(255 * 0.9)),
|
|
alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER,
|
|
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE)
|
|
|
|
self._bg_disabled_txt = None
|
|
|
|
def _load_assets(self):
|
|
self.set_rect(rl.Rectangle(0, 0, 194, 100))
|
|
self._bg_txt = gui_app.texture("icons_mici/setup/reset/small_button.png", 194, 100)
|
|
self._bg_pressed_txt = gui_app.texture("icons_mici/setup/reset/small_button_pressed.png", 194, 100)
|
|
|
|
def set_text(self, text: str):
|
|
self._label.set_text(text)
|
|
|
|
def set_opacity(self, opacity: float, smooth: bool = False):
|
|
if smooth:
|
|
self._opacity_filter.update(opacity)
|
|
else:
|
|
self._opacity_filter.x = opacity
|
|
|
|
def _render(self, _):
|
|
if not self.enabled and self._bg_disabled_txt is not None:
|
|
rl.draw_texture(self._bg_disabled_txt, int(self.rect.x), int(self.rect.y), rl.Color(255, 255, 255, int(255 * self._opacity_filter.x)))
|
|
elif self.is_pressed:
|
|
rl.draw_texture(self._bg_pressed_txt, int(self.rect.x), int(self.rect.y), rl.Color(255, 255, 255, int(255 * self._opacity_filter.x)))
|
|
else:
|
|
rl.draw_texture(self._bg_txt, int(self.rect.x), int(self.rect.y), rl.Color(255, 255, 255, int(255 * self._opacity_filter.x)))
|
|
|
|
opacity = 0.9 if self.enabled else 0.35
|
|
self._label.set_color(rl.Color(255, 255, 255, int(255 * opacity * self._opacity_filter.x)))
|
|
self._label.render(self._rect)
|
|
|
|
|
|
class SmallRedPillButton(SmallButton):
|
|
def _load_assets(self):
|
|
self.set_rect(rl.Rectangle(0, 0, 194, 100))
|
|
self._bg_txt = gui_app.texture("icons_mici/setup/small_red_pill.png", 194, 100)
|
|
self._bg_pressed_txt = gui_app.texture("icons_mici/setup/small_red_pill_pressed.png", 194, 100)
|
|
|
|
|
|
class SmallerRoundedButton(SmallButton):
|
|
def _load_assets(self):
|
|
self.set_rect(rl.Rectangle(0, 0, 150, 100))
|
|
self._bg_txt = gui_app.texture("icons_mici/setup/smaller_button.png", 150, 100)
|
|
self._bg_disabled_txt = gui_app.texture("icons_mici/setup/smaller_button_disabled.png", 150, 100)
|
|
self._bg_pressed_txt = gui_app.texture("icons_mici/setup/smaller_button_pressed.png", 150, 100)
|
|
|
|
|
|
class WideRoundedButton(SmallButton):
|
|
def _load_assets(self):
|
|
self.set_rect(rl.Rectangle(0, 0, 316, 100))
|
|
self._bg_txt = gui_app.texture("icons_mici/setup/medium_button_bg.png", 316, 100)
|
|
self._bg_pressed_txt = gui_app.texture("icons_mici/setup/medium_button_pressed_bg.png", 316, 100)
|
|
|
|
|
|
class WidishRoundedButton(SmallButton):
|
|
def _load_assets(self):
|
|
self.set_rect(rl.Rectangle(0, 0, 250, 100))
|
|
self._bg_txt = gui_app.texture("icons_mici/setup/widish_button.png", 250, 100)
|
|
self._bg_pressed_txt = gui_app.texture("icons_mici/setup/widish_button_pressed.png", 250, 100)
|
|
self._bg_disabled_txt = gui_app.texture("icons_mici/setup/widish_button_disabled.png", 250, 100)
|
|
|
|
|
|
class FullRoundedButton(SmallButton):
|
|
def _load_assets(self):
|
|
self.set_rect(rl.Rectangle(0, 0, 520, 100))
|
|
self._bg_txt = gui_app.texture("icons_mici/setup/reset/wide_button.png", 520, 100)
|
|
self._bg_pressed_txt = gui_app.texture("icons_mici/setup/reset/wide_button_pressed.png", 520, 100)
|