mirror of
https://github.com/MoreTore/openpilot.git
synced 2026-09-30 11:23:55 +08:00
UI
This commit is contained in:
+172
-140
@@ -1,11 +1,11 @@
|
||||
import math
|
||||
import pyray as rl
|
||||
from typing import Union
|
||||
from enum import Enum
|
||||
from collections.abc import Callable
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.label import MiciLabel
|
||||
from openpilot.system.ui.widgets.label import UnifiedLabel
|
||||
from openpilot.system.ui.widgets.scroller import DO_ZOOM
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
|
||||
from openpilot.common.filter_simple import BounceFilter
|
||||
|
||||
@@ -16,8 +16,7 @@ except ImportError:
|
||||
|
||||
SCROLLING_SPEED_PX_S = 50
|
||||
COMPLICATION_SIZE = 36
|
||||
LABEL_COLOR = rl.WHITE
|
||||
LABEL_HORIZONTAL_PADDING = 40
|
||||
LABEL_COLOR = rl.Color(255, 255, 255, int(255 * 0.9))
|
||||
COMPLICATION_GREY = rl.Color(0xAA, 0xAA, 0xAA, 255)
|
||||
PRESSED_SCALE = 1.15 if DO_ZOOM else 1.07
|
||||
|
||||
@@ -29,50 +28,51 @@ class ScrollState(Enum):
|
||||
|
||||
|
||||
class BigCircleButton(Widget):
|
||||
def __init__(self, icon: str, red: bool = False):
|
||||
def __init__(self, icon: rl.Texture, red: bool = False, icon_offset: tuple[int, int] = (0, 0)):
|
||||
super().__init__()
|
||||
self._red = red
|
||||
self._icon_offset = icon_offset
|
||||
|
||||
# State
|
||||
self.set_rect(rl.Rectangle(0, 0, 180, 180))
|
||||
self._press_state_enabled = True
|
||||
self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps)
|
||||
self._click_delay = 0.075
|
||||
|
||||
# Icons
|
||||
self._txt_icon = gui_app.texture(icon, 64, 53)
|
||||
self._txt_icon = icon
|
||||
self._txt_btn_disabled_bg = gui_app.texture("icons_mici/buttons/button_circle_disabled.png", 180, 180)
|
||||
|
||||
self._txt_btn_bg = gui_app.texture("icons_mici/buttons/button_circle.png", 180, 180)
|
||||
self._txt_btn_pressed_bg = gui_app.texture("icons_mici/buttons/button_circle_hover.png", 180, 180)
|
||||
self._txt_btn_pressed_bg = gui_app.texture("icons_mici/buttons/button_circle_pressed.png", 180, 180)
|
||||
|
||||
self._txt_btn_red_bg = gui_app.texture("icons_mici/buttons/button_circle_red.png", 180, 180)
|
||||
self._txt_btn_red_pressed_bg = gui_app.texture("icons_mici/buttons/button_circle_red_hover.png", 180, 180)
|
||||
self._txt_btn_red_pressed_bg = gui_app.texture("icons_mici/buttons/button_circle_red_pressed.png", 180, 180)
|
||||
|
||||
def set_enable_pressed_state(self, pressed: bool):
|
||||
self._press_state_enabled = pressed
|
||||
def _draw_content(self, btn_y: float):
|
||||
# draw icon
|
||||
icon_color = rl.Color(255, 255, 255, int(255 * 0.9)) if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35))
|
||||
rl.draw_texture_ex(self._txt_icon, (self._rect.x + (self._rect.width - self._txt_icon.width) / 2 + self._icon_offset[0],
|
||||
btn_y + (self._rect.height - self._txt_icon.height) / 2 + self._icon_offset[1]), 0, 1.0, icon_color)
|
||||
|
||||
def _render(self, _):
|
||||
# draw background
|
||||
txt_bg = self._txt_btn_bg if not self._red else self._txt_btn_red_bg
|
||||
if not self.enabled:
|
||||
txt_bg = self._txt_btn_disabled_bg
|
||||
elif self.is_pressed and self._press_state_enabled:
|
||||
elif self.is_pressed:
|
||||
txt_bg = self._txt_btn_pressed_bg if not self._red else self._txt_btn_red_pressed_bg
|
||||
|
||||
scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed and self._press_state_enabled else 1.0)
|
||||
scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed else 1.0)
|
||||
btn_x = self._rect.x + (self._rect.width * (1 - scale)) / 2
|
||||
btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2
|
||||
rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE)
|
||||
|
||||
# draw icon
|
||||
icon_color = rl.WHITE if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35))
|
||||
rl.draw_texture(self._txt_icon, int(self._rect.x + (self._rect.width - self._txt_icon.width) / 2),
|
||||
int(self._rect.y + (self._rect.height - self._txt_icon.height) / 2), icon_color)
|
||||
self._draw_content(btn_y)
|
||||
|
||||
|
||||
class BigCircleToggle(BigCircleButton):
|
||||
def __init__(self, icon: str, toggle_callback: Callable = None):
|
||||
super().__init__(icon, False)
|
||||
def __init__(self, icon: rl.Texture, toggle_callback: Callable | None = None, icon_offset: tuple[int, int] = (0, 0)):
|
||||
super().__init__(icon, False, icon_offset=icon_offset)
|
||||
self._toggle_callback = toggle_callback
|
||||
|
||||
# State
|
||||
@@ -80,7 +80,7 @@ class BigCircleToggle(BigCircleButton):
|
||||
|
||||
# Icons
|
||||
self._txt_toggle_enabled = gui_app.texture("icons_mici/buttons/toggle_dot_enabled.png", 66, 66)
|
||||
self._txt_toggle_disabled = gui_app.texture("icons_mici/buttons/toggle_dot_disabled.png", 70, 70) # TODO: why discrepancy?
|
||||
self._txt_toggle_disabled = gui_app.texture("icons_mici/buttons/toggle_dot_disabled.png", 66, 66)
|
||||
|
||||
def set_checked(self, checked: bool):
|
||||
self._checked = checked
|
||||
@@ -92,62 +92,47 @@ class BigCircleToggle(BigCircleButton):
|
||||
if self._toggle_callback:
|
||||
self._toggle_callback(self._checked)
|
||||
|
||||
def _render(self, _):
|
||||
super()._render(_)
|
||||
def _draw_content(self, btn_y: float):
|
||||
super()._draw_content(btn_y)
|
||||
|
||||
# draw status icon
|
||||
rl.draw_texture(self._txt_toggle_enabled if self._checked else self._txt_toggle_disabled,
|
||||
int(self._rect.x + (self._rect.width - self._txt_toggle_enabled.width) / 2),
|
||||
int(self._rect.y + 5), rl.WHITE)
|
||||
rl.draw_texture_ex(self._txt_toggle_enabled if self._checked else self._txt_toggle_disabled,
|
||||
(self._rect.x + (self._rect.width - self._txt_toggle_enabled.width) / 2, btn_y + 5),
|
||||
0, 1.0, rl.WHITE)
|
||||
|
||||
|
||||
class BigButton(Widget):
|
||||
LABEL_HORIZONTAL_PADDING = 40
|
||||
LABEL_VERTICAL_PADDING = 23 # visually matches 30 in figma
|
||||
|
||||
"""A lightweight stand-in for the Qt BigButton, drawn & updated each frame."""
|
||||
|
||||
def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = ""):
|
||||
def __init__(self, text: str, value: str = "", icon: Union[rl.Texture, None] = None, scroll: bool = False):
|
||||
super().__init__()
|
||||
self.set_rect(rl.Rectangle(0, 0, 402, 180))
|
||||
self.text = text
|
||||
self.value = value
|
||||
self.set_icon(icon)
|
||||
self._label_font_size_override: int | None = None
|
||||
self._txt_icon = icon
|
||||
self._scroll = scroll
|
||||
|
||||
self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps)
|
||||
self._click_delay = 0.075
|
||||
self._shake_start: float | None = None
|
||||
self._grow_animation_until: float | None = None
|
||||
|
||||
self._rotate_icon_t: float | None = None
|
||||
|
||||
self._label_font = gui_app.font(FontWeight.DISPLAY)
|
||||
self._value_font = gui_app.font(FontWeight.ROMAN)
|
||||
|
||||
self._label = MiciLabel(text, font_size=self._get_label_font_size(), width=int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2),
|
||||
font_weight=FontWeight.DISPLAY, color=LABEL_COLOR,
|
||||
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, wrap_text=True)
|
||||
self._sub_label = MiciLabel(value, font_size=COMPLICATION_SIZE, width=int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2),
|
||||
font_weight=FontWeight.ROMAN, color=COMPLICATION_GREY,
|
||||
alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, wrap_text=True)
|
||||
self._label = UnifiedLabel(text, font_size=self._get_label_font_size(), font_weight=FontWeight.BOLD,
|
||||
text_color=LABEL_COLOR, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, scroll=scroll,
|
||||
line_height=0.9)
|
||||
self._sub_label = UnifiedLabel(value, font_size=COMPLICATION_SIZE, font_weight=FontWeight.ROMAN,
|
||||
text_color=COMPLICATION_GREY, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM)
|
||||
self._update_label_layout()
|
||||
|
||||
self._load_images()
|
||||
|
||||
# internal state
|
||||
self._scroll_offset = 0 # in pixels
|
||||
self._needs_scroll = measure_text_cached(self._label_font, text, self._get_label_font_size()).x + 25 > self._rect.width
|
||||
self._scroll_timer = 0
|
||||
self._scroll_state = ScrollState.PRE_SCROLL
|
||||
|
||||
def set_icon(self, icon: Union[str, rl.Texture]):
|
||||
self._txt_icon = gui_app.texture(icon, 64, 64) if isinstance(icon, str) and len(icon) else icon
|
||||
|
||||
def _refresh_label_metrics(self):
|
||||
font_size = self._label_font_size_override if self._label_font_size_override is not None else self._get_label_font_size()
|
||||
self._label.set_font_size(font_size)
|
||||
self._needs_scroll = measure_text_cached(self._label_font, self.text, font_size).x + 25 > self._rect.width
|
||||
self._scroll_offset = 0
|
||||
self._scroll_timer = 0
|
||||
self._scroll_state = ScrollState.PRE_SCROLL
|
||||
|
||||
def _set_label_font_size_override(self, font_size: int | None):
|
||||
self._label_font_size_override = font_size
|
||||
self._refresh_label_metrics()
|
||||
def set_icon(self, icon: Union[rl.Texture, None]):
|
||||
self._txt_icon = icon
|
||||
|
||||
def set_rotate_icon(self, rotate: bool):
|
||||
if rotate and self._rotate_icon_t is not None:
|
||||
@@ -158,32 +143,37 @@ class BigButton(Widget):
|
||||
self._txt_default_bg = gui_app.texture("icons_mici/buttons/button_rectangle.png", 402, 180)
|
||||
self._txt_pressed_bg = gui_app.texture("icons_mici/buttons/button_rectangle_pressed.png", 402, 180)
|
||||
self._txt_disabled_bg = gui_app.texture("icons_mici/buttons/button_rectangle_disabled.png", 402, 180)
|
||||
self._txt_hover_bg = gui_app.texture("icons_mici/buttons/button_rectangle_hover.png", 402, 180)
|
||||
|
||||
def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None:
|
||||
super().set_touch_valid_callback(lambda: touch_callback() and self._grow_animation_until is None)
|
||||
|
||||
def _width_hint(self) -> int:
|
||||
# Single line if scrolling, so hide behind icon if exists
|
||||
icon_size = self._txt_icon.width if self._txt_icon and self._scroll and self.value else 0
|
||||
return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2 - icon_size)
|
||||
|
||||
def _get_label_font_size(self):
|
||||
if len(self.text) < 12:
|
||||
font_size = 64
|
||||
elif len(self.text) < 17:
|
||||
font_size = 48
|
||||
elif len(self.text) < 20:
|
||||
font_size = 42
|
||||
if len(self.text) <= 18:
|
||||
return 48
|
||||
else:
|
||||
font_size = 36
|
||||
return 42
|
||||
|
||||
def _update_label_layout(self):
|
||||
self._label.set_font_size(self._get_label_font_size())
|
||||
if self.value:
|
||||
font_size -= 20
|
||||
|
||||
return font_size
|
||||
self._label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP)
|
||||
else:
|
||||
self._label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM)
|
||||
|
||||
def set_text(self, text: str):
|
||||
self.text = text
|
||||
self._label.set_text(text)
|
||||
self._refresh_label_metrics()
|
||||
self._update_label_layout()
|
||||
|
||||
def set_value(self, value: str):
|
||||
self.value = value
|
||||
self._sub_label.set_text(value)
|
||||
self._refresh_label_metrics()
|
||||
self._update_label_layout()
|
||||
|
||||
def get_value(self) -> str:
|
||||
return self.value
|
||||
@@ -191,64 +181,60 @@ class BigButton(Widget):
|
||||
def get_text(self):
|
||||
return self.text
|
||||
|
||||
def _update_state(self):
|
||||
# hold on text for a bit, scroll, hold again, reset
|
||||
if self._needs_scroll:
|
||||
"""`dt` should be seconds since last frame (rl.get_frame_time())."""
|
||||
# TODO: this comment is generated by GPT, prob wrong and misused
|
||||
dt = rl.get_frame_time()
|
||||
def trigger_shake(self):
|
||||
self._shake_start = rl.get_time()
|
||||
|
||||
self._scroll_timer += dt
|
||||
if self._scroll_state == ScrollState.PRE_SCROLL:
|
||||
if self._scroll_timer < 0.5:
|
||||
return
|
||||
self._scroll_state = ScrollState.SCROLLING
|
||||
self._scroll_timer = 0
|
||||
def trigger_grow_animation(self, duration: float = 0.65):
|
||||
self._grow_animation_until = rl.get_time() + duration
|
||||
|
||||
elif self._scroll_state == ScrollState.SCROLLING:
|
||||
self._scroll_offset -= SCROLLING_SPEED_PX_S * dt
|
||||
# reset when text has completely left the button + 50 px gap
|
||||
# TODO: use global constant for 30+30 px gap
|
||||
# TODO: add std Widget padding option integrated into the self._rect
|
||||
full_len = measure_text_cached(self._label_font, self.text, self._get_label_font_size()).x + 30 + 30
|
||||
if self._scroll_offset < (self._rect.width - full_len):
|
||||
self._scroll_state = ScrollState.POST_SCROLL
|
||||
self._scroll_timer = 0
|
||||
@property
|
||||
def _shake_offset(self) -> float:
|
||||
SHAKE_DURATION = 0.5
|
||||
SHAKE_AMPLITUDE = 24.0
|
||||
SHAKE_FREQUENCY = 32.0
|
||||
if self._shake_start is None:
|
||||
return 0.0
|
||||
t = rl.get_time() - self._shake_start
|
||||
if t > SHAKE_DURATION:
|
||||
return 0.0
|
||||
decay = 1.0 - t / SHAKE_DURATION
|
||||
return decay * SHAKE_AMPLITUDE * math.sin(t * SHAKE_FREQUENCY)
|
||||
|
||||
elif self._scroll_state == ScrollState.POST_SCROLL:
|
||||
# wait for a bit before starting to scroll again
|
||||
if self._scroll_timer < 0.75:
|
||||
return
|
||||
self._scroll_state = ScrollState.PRE_SCROLL
|
||||
self._scroll_timer = 0
|
||||
self._scroll_offset = 0
|
||||
def set_position(self, x: float, y: float) -> None:
|
||||
super().set_position(x + self._shake_offset, y)
|
||||
|
||||
def _handle_background(self) -> tuple[rl.Texture, float, float, float]:
|
||||
if self._grow_animation_until is not None:
|
||||
if rl.get_time() >= self._grow_animation_until:
|
||||
self._grow_animation_until = None
|
||||
|
||||
def _render(self, _):
|
||||
# draw _txt_default_bg
|
||||
txt_bg = self._txt_default_bg
|
||||
if not self.enabled:
|
||||
txt_bg = self._txt_disabled_bg
|
||||
elif self.is_pressed:
|
||||
txt_bg = self._txt_hover_bg
|
||||
txt_bg = self._txt_pressed_bg
|
||||
|
||||
scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed else 1.0)
|
||||
scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed or self._grow_animation_until is not None else 1.0)
|
||||
btn_x = self._rect.x + (self._rect.width * (1 - scale)) / 2
|
||||
btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2
|
||||
rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE)
|
||||
return txt_bg, btn_x, btn_y, scale
|
||||
|
||||
def _draw_content(self, btn_y: float):
|
||||
# LABEL ------------------------------------------------------------------
|
||||
lx = self._rect.x + LABEL_HORIZONTAL_PADDING
|
||||
ly = btn_y + self._rect.height - 33 # - 40# - self._get_label_font_size() / 2
|
||||
|
||||
if self.value:
|
||||
self._sub_label.set_position(lx, ly)
|
||||
ly -= self._sub_label.font_size + 9
|
||||
self._sub_label.render()
|
||||
label_x = self._rect.x + self.LABEL_HORIZONTAL_PADDING
|
||||
|
||||
label_color = LABEL_COLOR if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35))
|
||||
self._label.set_color(label_color)
|
||||
self._label.set_position(lx, ly)
|
||||
self._label.render()
|
||||
label_rect = rl.Rectangle(label_x, btn_y + self.LABEL_VERTICAL_PADDING, self._width_hint(),
|
||||
self._rect.height - self.LABEL_VERTICAL_PADDING * 2)
|
||||
self._label.render(label_rect)
|
||||
|
||||
if self.value:
|
||||
label_y = btn_y + self.LABEL_VERTICAL_PADDING + self._label.get_content_height(self._width_hint())
|
||||
sub_label_height = btn_y + self._rect.height - self.LABEL_VERTICAL_PADDING - label_y
|
||||
sub_label_rect = rl.Rectangle(label_x, label_y, self._width_hint(), sub_label_height)
|
||||
self._sub_label.render(sub_label_rect)
|
||||
|
||||
# ICON -------------------------------------------------------------------
|
||||
if self._txt_icon:
|
||||
@@ -256,23 +242,35 @@ class BigButton(Widget):
|
||||
if self._rotate_icon_t is not None:
|
||||
rotation = (rl.get_time() - self._rotate_icon_t) * 180
|
||||
|
||||
# drop top right with 30px padding
|
||||
# draw top right with 30px padding
|
||||
x = self._rect.x + self._rect.width - 30 - self._txt_icon.width / 2
|
||||
y = self._rect.y + 30 + self._txt_icon.height / 2
|
||||
y = btn_y + 30 + self._txt_icon.height / 2
|
||||
source_rec = rl.Rectangle(0, 0, self._txt_icon.width, self._txt_icon.height)
|
||||
dest_rec = rl.Rectangle(int(x), int(y), self._txt_icon.width, self._txt_icon.height)
|
||||
dest_rec = rl.Rectangle(x, y, self._txt_icon.width, self._txt_icon.height)
|
||||
origin = rl.Vector2(self._txt_icon.width / 2, self._txt_icon.height / 2)
|
||||
rl.draw_texture_pro(self._txt_icon, source_rec, dest_rec, origin, rotation, rl.WHITE)
|
||||
rl.draw_texture_pro(self._txt_icon, source_rec, dest_rec, origin, rotation, rl.Color(255, 255, 255, int(255 * 0.9)))
|
||||
|
||||
def _render(self, _):
|
||||
txt_bg, btn_x, btn_y, scale = self._handle_background()
|
||||
|
||||
if self._scroll:
|
||||
# draw black background since images are transparent
|
||||
scaled_rect = rl.Rectangle(btn_x, btn_y, self._rect.width * scale, self._rect.height * scale)
|
||||
rl.draw_rectangle_rounded(scaled_rect, 0.4, 7, rl.Color(0, 0, 0, int(255 * 0.5)))
|
||||
|
||||
self._draw_content(btn_y)
|
||||
rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE)
|
||||
else:
|
||||
rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE)
|
||||
self._draw_content(btn_y)
|
||||
|
||||
|
||||
class BigToggle(BigButton):
|
||||
def __init__(self, text: str, value: str = "", initial_state: bool = False, toggle_callback: Callable = None):
|
||||
def __init__(self, text: str, value: str = "", initial_state: bool = False, toggle_callback: Callable | None = None):
|
||||
super().__init__(text, value, "")
|
||||
self._checked = initial_state
|
||||
self._toggle_callback = toggle_callback
|
||||
|
||||
self._set_label_font_size_override(48)
|
||||
|
||||
def _load_images(self):
|
||||
super()._load_images()
|
||||
self._txt_enabled_toggle = gui_app.texture("icons_mici/buttons/toggle_pill_enabled.png", 84, 66)
|
||||
@@ -290,35 +288,30 @@ class BigToggle(BigButton):
|
||||
def _draw_pill(self, x: float, y: float, checked: bool):
|
||||
# draw toggle icon top right
|
||||
if checked:
|
||||
rl.draw_texture(self._txt_enabled_toggle, int(x), int(y), rl.WHITE)
|
||||
rl.draw_texture_ex(self._txt_enabled_toggle, (x, y), 0, 1.0, rl.WHITE)
|
||||
else:
|
||||
rl.draw_texture(self._txt_disabled_toggle, int(x), int(y), rl.WHITE)
|
||||
rl.draw_texture_ex(self._txt_disabled_toggle, (x, y), 0, 1.0, rl.WHITE)
|
||||
|
||||
def _render(self, _):
|
||||
super()._render(_)
|
||||
def _draw_content(self, btn_y: float):
|
||||
super()._draw_content(btn_y)
|
||||
|
||||
x = self._rect.x + self._rect.width - self._txt_enabled_toggle.width
|
||||
y = self._rect.y
|
||||
y = btn_y
|
||||
self._draw_pill(x, y, self._checked)
|
||||
|
||||
|
||||
class BigMultiToggle(BigToggle):
|
||||
def __init__(self, text: str, options: list[str], toggle_callback: Callable = None,
|
||||
select_callback: Callable = None):
|
||||
def __init__(self, text: str, options: list[str], toggle_callback: Callable | None = None,
|
||||
select_callback: Callable | None = None):
|
||||
super().__init__(text, "", toggle_callback=toggle_callback)
|
||||
assert len(options) > 0
|
||||
self._options = options
|
||||
self._select_callback = select_callback
|
||||
|
||||
self._label.set_width(int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2 - self._txt_enabled_toggle.width))
|
||||
# Keep the title size stable when the selected option changes.
|
||||
self._set_label_font_size_override(self._get_label_font_size())
|
||||
|
||||
self.set_value(self._options[0])
|
||||
|
||||
def _get_label_font_size(self):
|
||||
font_size = super()._get_label_font_size()
|
||||
return font_size - 6
|
||||
def _width_hint(self) -> int:
|
||||
return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2 - self._txt_enabled_toggle.width)
|
||||
|
||||
def _handle_mouse_release(self, mouse_pos: MousePos):
|
||||
super()._handle_mouse_release(mouse_pos)
|
||||
@@ -328,22 +321,60 @@ class BigMultiToggle(BigToggle):
|
||||
if self._select_callback:
|
||||
self._select_callback(self.value)
|
||||
|
||||
def _render(self, _):
|
||||
BigButton._render(self, _)
|
||||
def _draw_content(self, btn_y: float):
|
||||
# don't draw pill from BigToggle
|
||||
BigButton._draw_content(self, btn_y)
|
||||
|
||||
checked_idx = self._options.index(self.value)
|
||||
|
||||
x = self._rect.x + self._rect.width - self._txt_enabled_toggle.width
|
||||
y = self._rect.y
|
||||
y = btn_y
|
||||
|
||||
for i in range(len(self._options)):
|
||||
self._draw_pill(x, y, checked_idx == i)
|
||||
y += 35
|
||||
|
||||
|
||||
class GreyBigButton(BigButton):
|
||||
"""Users should manage newlines with this class themselves"""
|
||||
|
||||
LABEL_HORIZONTAL_PADDING = 30
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.set_touch_valid_callback(lambda: False)
|
||||
|
||||
self._rect.width = 476
|
||||
|
||||
self._label.set_font_size(36)
|
||||
self._label.set_font_weight(FontWeight.BOLD)
|
||||
self._label.set_line_height(1.0)
|
||||
|
||||
self._sub_label.set_font_size(36)
|
||||
self._sub_label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.9)))
|
||||
self._sub_label.set_font_weight(FontWeight.DISPLAY_REGULAR)
|
||||
self._sub_label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE if not self._label.text else
|
||||
rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM)
|
||||
self._sub_label.set_line_height(0.95)
|
||||
|
||||
@property
|
||||
def LABEL_VERTICAL_PADDING(self):
|
||||
return BigButton.LABEL_VERTICAL_PADDING if self._label.text else 18
|
||||
|
||||
def _width_hint(self) -> int:
|
||||
return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2)
|
||||
|
||||
def _get_label_font_size(self):
|
||||
return 36
|
||||
|
||||
def _render(self, _):
|
||||
rl.draw_rectangle_rounded(self._rect, 0.4, 10, rl.Color(255, 255, 255, int(255 * 0.15)))
|
||||
self._draw_content(self._rect.y)
|
||||
|
||||
|
||||
class BigMultiParamToggle(BigMultiToggle):
|
||||
def __init__(self, text: str, param: str, options: list[str], toggle_callback: Callable = None,
|
||||
select_callback: Callable = None):
|
||||
def __init__(self, text: str, param: str, options: list[str], toggle_callback: Callable | None = None,
|
||||
select_callback: Callable | None = None):
|
||||
super().__init__(text, options, toggle_callback, select_callback)
|
||||
self._param = param
|
||||
|
||||
@@ -360,7 +391,7 @@ class BigMultiParamToggle(BigMultiToggle):
|
||||
|
||||
|
||||
class BigParamControl(BigToggle):
|
||||
def __init__(self, text: str, param: str, toggle_callback: Callable = None):
|
||||
def __init__(self, text: str, param: str, toggle_callback: Callable | None = None):
|
||||
super().__init__(text, "", toggle_callback=toggle_callback)
|
||||
self.param = param
|
||||
self.params = Params()
|
||||
@@ -376,8 +407,9 @@ class BigParamControl(BigToggle):
|
||||
|
||||
# TODO: param control base class
|
||||
class BigCircleParamControl(BigCircleToggle):
|
||||
def __init__(self, icon: str, param: str, toggle_callback: Callable = None):
|
||||
super().__init__(icon, toggle_callback)
|
||||
def __init__(self, icon: rl.Texture, param: str, toggle_callback: Callable | None = None,
|
||||
icon_offset: tuple[int, int] = (0, 0)):
|
||||
super().__init__(icon, toggle_callback, icon_offset=icon_offset)
|
||||
self._param = param
|
||||
self.params = Params()
|
||||
self.set_checked(self.params.get_bool(self._param, False))
|
||||
|
||||
Reference in New Issue
Block a user