BigUI: Purple Rain

This commit is contained in:
firestarsdog
2026-07-14 19:43:43 -04:00
parent 5a2117892c
commit 55ad1fd0fd
3 changed files with 142 additions and 94 deletions
@@ -1,8 +1,8 @@
from __future__ import annotations
import math
import threading
import time
from dataclasses import replace
import pyray as rl
@@ -32,8 +32,9 @@ from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import (
draw_rounded_fill,
draw_rounded_stroke,
draw_settings_list_row,
draw_soft_card,
mix_colors,
draw_text_fit_common,
snap_rect,
with_alpha,
)
from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state
from openpilot.selfdrive.ui.lib.fingerprint_catalog import (
@@ -443,9 +444,6 @@ class VehicleSettingsManagerView(PanelManagerView):
class ButtonActionComboDialog(Widget):
_DIALOG_W = 660
_DIALOG_H = 440
def __init__(self, controller: "StarPilotVehicleSettingsLayout", title: str,
keys: tuple[str, ...], labels: tuple[str, ...]):
super().__init__()
@@ -453,60 +451,54 @@ class ButtonActionComboDialog(Widget):
self._title = title
self._keys = keys
self._labels = labels
self._pressed_idx: int | None = None
self._pressed_close = False
self._snapshot = {key: controller._params.get_int(key) for key in keys}
self._pressed_zone: str | None = None
self._font_title = gui_app.font(FontWeight.BOLD)
self._font_btn = gui_app.font(FontWeight.SEMI_BOLD)
def _layout(self):
sw = gui_app.width
sh = gui_app.height
dx = (sw - self._DIALOG_W) / 2
dy = (sh - self._DIALOG_H) / 2
d_rect = rl.Rectangle(int(dx), int(dy), int(self._DIALOG_W), int(self._DIALOG_H))
rows_area_y = dy + 72
rows_area_h = len(self._keys) * 88 + 12
rows_area = rl.Rectangle(int(dx + 40), int(rows_area_y), int(self._DIALOG_W - 80), int(rows_area_h))
row_rects = []
for i in range(len(self._keys)):
row_rects.append(rl.Rectangle(
int(dx + 40), int(rows_area_y + i * 88),
int(self._DIALOG_W - 80), 88,
))
close_w = 320
close_h = 64
close_rect = rl.Rectangle(
int(dx + (self._DIALOG_W - close_w) / 2),
int(dy + self._DIALOG_H - 36 - close_h),
close_w, close_h,
)
return d_rect, rows_area, row_rects, close_rect
self._ok_offset = 0.0
self._cancel_offset = 0.0
self._ok_target = 0.0
self._cancel_target = 0.0
self._row_rects: list[rl.Rectangle] = []
self._cancel_rect = rl.Rectangle(0, 0, 0, 0)
self._ok_rect = rl.Rectangle(0, 0, 0, 0)
def _handle_mouse_press(self, mouse_pos):
_, _, row_rects, close_rect = self._layout()
if rl.check_collision_point_rec(mouse_pos, close_rect):
self._pressed_close = True
return
for i, rect in enumerate(row_rects):
for i, rect in enumerate(self._row_rects):
if rl.check_collision_point_rec(mouse_pos, rect):
self._pressed_idx = i
self._pressed_zone = f"row:{i}"
return
if rl.check_collision_point_rec(mouse_pos, self._cancel_rect):
self._pressed_zone = "cancel"
self._cancel_target = 1.0
return
if rl.check_collision_point_rec(mouse_pos, self._ok_rect):
self._pressed_zone = "ok"
self._ok_target = 1.0
def _handle_mouse_release(self, mouse_pos):
if self._pressed_idx is not None:
idx = self._pressed_idx
self._pressed_idx = None
_, _, row_rects, _ = self._layout()
if idx < len(row_rects) and rl.check_collision_point_rec(mouse_pos, row_rects[idx]):
zone = self._pressed_zone
self._pressed_zone = None
if not zone:
return
if zone.startswith("row:"):
idx = int(zone.split(":")[1])
if idx < len(self._row_rects) and rl.check_collision_point_rec(mouse_pos, self._row_rects[idx]):
self._controller._show_action_picker(self._keys[idx])
return
if self._pressed_close:
self._pressed_close = False
_, _, _, close_rect = self._layout()
if rl.check_collision_point_rec(mouse_pos, close_rect):
if zone == "cancel":
self._cancel_target = 0.0
if rl.check_collision_point_rec(mouse_pos, self._cancel_rect):
for k, v in self._snapshot.items():
self._controller._params.put_int(k, v)
gui_app.pop_widget()
return
if zone == "ok":
self._ok_target = 0.0
if rl.check_collision_point_rec(mouse_pos, self._ok_rect):
gui_app.pop_widget()
def _render(self, rect):
@@ -514,63 +506,119 @@ class ButtonActionComboDialog(Widget):
sh = gui_app.height
rl.draw_rectangle(0, 0, sw, sh, rl.Color(0, 0, 0, 160))
d_rect, rows_area, row_rects, close_rect = self._layout()
dialog_w = min(2320, int(rect.width - 40))
dialog_h = max(min(1015, int(rect.height - 40)), 968)
dx = rect.x + (rect.width - dialog_w) / 2
dy = rect.y + (rect.height - dialog_h) / 2
d_rect = snap_rect(rl.Rectangle(int(dx), int(dy), dialog_w, dialog_h))
btn_y = int(dy + dialog_h - 160 - 87)
btn_pair_w = 600 + 40 + 600
btn_start_x = int(dx + (dialog_w - btn_pair_w) / 2)
self._cancel_rect = rl.Rectangle(btn_start_x, btn_y, 600, 160)
self._ok_rect = rl.Rectangle(btn_start_x + 600 + 40, btn_y, 600, 160)
row_margin = 80
self._row_rects = []
for i in range(len(self._keys)):
self._row_rects.append(rl.Rectangle(
int(dx + row_margin), int(dy + 130 + i * (170 + 40)),
dialog_w - 2 * row_margin, 170,
))
mouse_pos = gui_app.last_mouse_event.pos
draw_rounded_fill(d_rect, rl.Color(10, 12, 16, 255), radius_px=24)
draw_rounded_stroke(d_rect, rl.Color(255, 255, 255, 16), radius_px=24)
rl.draw_rectangle_rec(rl.Rectangle(d_rect.x, d_rect.y, d_rect.width, 4), PANEL_STYLE.accent)
draw_rounded_fill(d_rect, rl.Color(10, 12, 16, 255), radius_px=35)
draw_rounded_stroke(d_rect, rl.Color(255, 255, 255, 16), radius_px=35)
rl.draw_rectangle_rec(rl.Rectangle(d_rect.x, d_rect.y, d_rect.width, 3), PANEL_STYLE.accent)
title_size = 32
title_size = 64
ts = measure_text_cached(self._font_title, self._title, title_size)
rl.draw_text_ex(self._font_title, self._title,
rl.Vector2(int(d_rect.x + (d_rect.width - ts.x) / 2),
int(d_rect.y + (64 - title_size) / 2)),
rl.Vector2(int(dx + (dialog_w - ts.x) / 2), int(dy + 87)),
title_size, 0, rl.WHITE)
draw_list_group_shell(rows_area, style=PANEL_STYLE)
font_label = gui_app.font(FontWeight.MEDIUM)
font_value = gui_app.font(FontWeight.MEDIUM)
value_fs = 48
title_fs = 48
for i in range(len(self._keys)):
row_rect = row_rects[i]
row_rect = self._row_rects[i]
hovered = rl.check_collision_point_rec(mouse_pos, row_rect)
pressed = self._pressed_idx == i
pressed = self._pressed_zone == f"row:{i}"
draw_settings_list_row(
row_rect,
title=self._labels[i],
value=self._controller._get_action_name(self._keys[i]),
enabled=True,
hovered=hovered and (self._pressed_idx is None),
pressed=pressed,
is_last=(i == len(self._keys) - 1),
show_chevron=True,
title_size=46,
value_size=42,
style=PANEL_STYLE,
)
bg = rl.Color(255, 255, 255, 4)
if pressed:
bg = rl.Color(255, 255, 255, 12)
elif hovered:
bg = rl.Color(255, 255, 255, 8)
draw_rounded_fill(row_rect, bg, radius_px=8)
draw_rounded_stroke(row_rect, rl.Color(255, 255, 255, 6), radius_px=8)
close_hovered = rl.check_collision_point_rec(mouse_pos, close_rect)
close_pressed = self._pressed_close
if i < len(self._keys) - 1:
sep_y = int(row_rect.y + row_rect.height - 1)
rl.draw_line(int(row_rect.x + 24), sep_y, int(row_rect.x + row_rect.width - 24), sep_y, rl.Color(255, 255, 255, 16))
if close_pressed:
close_fill = mix_colors(PANEL_STYLE.accent, rl.Color(0, 0, 0, 255), 0.2)
close_border = PANEL_STYLE.accent
elif close_hovered:
close_fill = PANEL_STYLE.accent
close_border = PANEL_STYLE.accent
title_x = int(row_rect.x + 24)
title_y = int(row_rect.y + (row_rect.height - title_fs) / 2)
rl.draw_text_ex(self._font_title, self._labels[i], rl.Vector2(title_x, title_y), title_fs, 0, rl.WHITE)
action_name = self._controller._get_action_name(self._keys[i])
value_left = int(title_x + 350)
value_right = int(row_rect.x + row_rect.width - 24)
available_w = value_right - value_left
text_w = measure_text_cached(font_label, action_name, value_fs).x
if text_w <= available_w:
val_x = value_right - text_w
val_y = int(row_rect.y + (row_rect.height - value_fs) / 2)
rl.draw_text_ex(font_value, action_name, rl.Vector2(val_x, val_y), value_fs, 0, rl.WHITE)
else:
draw_text_fit_common(
font_value, action_name,
rl.Vector2(value_left, int(row_rect.y + (row_rect.height - value_fs) / 2)),
available_w, value_fs, color=rl.WHITE,
)
dt = rl.get_frame_time()
self._ok_offset += (self._ok_target - self._ok_offset) * (1 - math.exp(-dt / 0.060))
self._cancel_offset += (self._cancel_target - self._cancel_offset) * (1 - math.exp(-dt / 0.060))
c_y = self._cancel_rect.y + min(1.0, 10 * self._cancel_offset * 0.1)
c_face = snap_rect(rl.Rectangle(self._cancel_rect.x, c_y, 600, 160))
c_hovered = rl.check_collision_point_rec(mouse_pos, self._cancel_rect)
c_pressed = self._pressed_zone == "cancel"
if c_pressed:
c_fill = rl.Color(50, 54, 64, 255)
c_border = rl.Color(255, 255, 255, 40)
elif c_hovered:
c_fill = rl.Color(40, 44, 54, 255)
c_border = rl.Color(255, 255, 255, 30)
else:
close_fill = rl.Color(255, 255, 255, 14)
close_border = rl.Color(255, 255, 255, 28)
c_fill = rl.Color(34, 38, 48, 255)
c_border = rl.Color(255, 255, 255, 20)
draw_soft_card(close_rect, close_fill, close_border)
draw_rounded_fill(c_face, c_fill, radius_px=41)
draw_rounded_stroke(c_face, c_border, radius_px=41)
c_text = tr("CANCEL")
cts = measure_text_cached(self._font_btn, c_text, 49)
rl.draw_text_ex(self._font_btn, c_text,
rl.Vector2(int(c_face.x + (c_face.width - cts.x) / 2),
int(c_face.y + (c_face.height - cts.y) / 2)),
49, 0, rl.WHITE)
close_text = tr("Close")
close_size = 22
cts = measure_text_cached(self._font_btn, close_text, close_size)
rl.draw_text_ex(self._font_btn, close_text,
rl.Vector2(int(close_rect.x + (close_rect.width - cts.x) / 2),
int(close_rect.y + (close_rect.height - cts.y) / 2)),
close_size, 0, rl.WHITE)
o_y = self._ok_rect.y + min(1.0, 10 * self._ok_offset * 0.1)
o_face = snap_rect(rl.Rectangle(self._ok_rect.x, o_y, 600, 160))
draw_rounded_fill(o_face, PANEL_STYLE.accent, radius_px=41)
draw_rounded_stroke(o_face, with_alpha(PANEL_STYLE.accent, 150), radius_px=41)
o_text = tr("OK")
ots = measure_text_cached(self._font_btn, o_text, 49)
rl.draw_text_ex(self._font_btn, o_text,
rl.Vector2(int(o_face.x + (o_face.width - ots.x) / 2),
int(o_face.y + (o_face.height - ots.y) / 2)),
49, 0, rl.WHITE)
class StarPilotVehicleSettingsLayout(_SettingsPage):
+2 -2
View File
@@ -47,7 +47,7 @@ BUTTON_DISABLED_TEXT_COLORS = {
BUTTON_BACKGROUND_COLORS = {
ButtonStyle.NORMAL: rl.Color(51, 51, 51, 255),
ButtonStyle.PRIMARY: rl.Color(70, 91, 234, 255),
ButtonStyle.PRIMARY: rl.Color(139, 92, 246, 255),
ButtonStyle.DANGER: rl.Color(226, 44, 44, 255),
ButtonStyle.TRANSPARENT: rl.BLACK,
ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.BLANK,
@@ -61,7 +61,7 @@ BUTTON_BACKGROUND_COLORS = {
BUTTON_PRESSED_BACKGROUND_COLORS = {
ButtonStyle.NORMAL: rl.Color(74, 74, 74, 255),
ButtonStyle.PRIMARY: rl.Color(48, 73, 244, 255),
ButtonStyle.PRIMARY: rl.Color(120, 78, 220, 255),
ButtonStyle.DANGER: rl.Color(255, 36, 36, 255),
ButtonStyle.TRANSPARENT: rl.BLACK,
ButtonStyle.TRANSPARENT_WHITE_TEXT: rl.BLANK,
+1 -1
View File
@@ -46,7 +46,7 @@ class MultiOptionDialog(Widget):
def _render(self, rect):
dialog_rect = rl.Rectangle(rect.x + MARGIN, rect.y + MARGIN, rect.width - 2 * MARGIN, rect.height - 2 * MARGIN)
rl.draw_rectangle_rounded(dialog_rect, 0.02, 20, rl.Color(30, 30, 30, 255))
rl.draw_rectangle_rounded(dialog_rect, 0.02, 20, rl.Color(10, 12, 16, 255))
content_rect = rl.Rectangle(dialog_rect.x + MARGIN, dialog_rect.y + MARGIN,
dialog_rect.width - 2 * MARGIN, dialog_rect.height - 2 * MARGIN)