You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
170 lines
6.6 KiB
170 lines
6.6 KiB
from collections.abc import Callable
|
|
from enum import IntEnum
|
|
|
|
import pyray as rl
|
|
|
|
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
|
|
|
|
|
|
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(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),
|
|
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,
|
|
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: TextAlignment = TextAlignment.CENTER,
|
|
text_padding: int = 20,
|
|
icon=None,
|
|
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,
|
|
BUTTON_TEXT_COLOR[self._button_style], icon=icon)
|
|
|
|
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: TextAlignment = TextAlignment.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))
|
|
|