mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-06-28 10:02:06 +08:00
BigUI WIP: Sounds/Sliders
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
import math
|
||||
import time
|
||||
import pyray as rl
|
||||
from collections.abc import Callable
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
|
||||
@@ -522,125 +523,148 @@ class ValueTile(AetherTile):
|
||||
|
||||
|
||||
class SliderTile(AetherTile):
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
get_value: Callable[[], float],
|
||||
set_value: Callable[[float], None],
|
||||
min_val: float,
|
||||
max_val: float,
|
||||
step: float,
|
||||
icon_path: str | None = None,
|
||||
bg_color: rl.Color | str | None = None,
|
||||
is_enabled: Callable[[], bool] | None = None,
|
||||
desc: str = "",
|
||||
unit: str = "",
|
||||
labels: dict[float, str] | None = None,
|
||||
):
|
||||
super().__init__(surface_color=bg_color)
|
||||
self.title = title
|
||||
self.desc = desc
|
||||
self.get_value = get_value
|
||||
self.set_value = set_value
|
||||
self.min_val = min_val
|
||||
self.max_val = max_val
|
||||
self.step = step
|
||||
self.unit = unit
|
||||
self.labels = labels or {}
|
||||
self.set_enabled(is_enabled or (lambda: True))
|
||||
self._icon = starpilot_texture(icon_path, 80, 80) if icon_path else None
|
||||
self._font = gui_app.font(FontWeight.BOLD)
|
||||
self._font_desc = gui_app.font(FontWeight.NORMAL)
|
||||
self._active_color = self.surface_color
|
||||
self._disabled_color = rl.Color(120, 120, 120, 255)
|
||||
LONG_PRESS_THRESHOLD = 0.5
|
||||
DRAG_THRESHOLD = 10
|
||||
|
||||
self._is_dragging = False
|
||||
self._last_mouse_x = 0.0
|
||||
self._velocity = 0.0
|
||||
self._smooth_value = get_value()
|
||||
def __init__(
|
||||
self,
|
||||
title: str,
|
||||
get_value: Callable[[], float],
|
||||
set_value: Callable[[float], None],
|
||||
min_val: float,
|
||||
max_val: float,
|
||||
step: float,
|
||||
icon_path: str | None = None,
|
||||
bg_color: rl.Color | str | None = None,
|
||||
is_enabled: Callable[[], bool] | None = None,
|
||||
desc: str = "",
|
||||
unit: str = "",
|
||||
labels: dict[float, str] | None = None,
|
||||
on_test: Callable[[], None] | None = None,
|
||||
):
|
||||
super().__init__(surface_color=bg_color)
|
||||
self.title = title
|
||||
self.desc = desc
|
||||
self.get_value = get_value
|
||||
self.set_value = set_value
|
||||
self.min_val = min_val
|
||||
self.max_val = max_val
|
||||
self.step = step
|
||||
self.unit = unit
|
||||
self.labels = labels or {}
|
||||
self.on_test = on_test
|
||||
self.set_enabled(is_enabled or (lambda: True))
|
||||
self._icon = starpilot_texture(icon_path, 80, 80) if icon_path else None
|
||||
self._font = gui_app.font(FontWeight.BOLD)
|
||||
self._font_desc = gui_app.font(FontWeight.NORMAL)
|
||||
self._active_color = self.surface_color
|
||||
self._disabled_color = rl.Color(120, 120, 120, 255)
|
||||
|
||||
def _handle_mouse_press(self, mouse_pos: MousePos):
|
||||
if rl.check_collision_point_rec(mouse_pos, self._hit_rect) and self.enabled:
|
||||
self._is_pressed = True
|
||||
self._is_dragging = True
|
||||
self._last_mouse_x = mouse_pos.x
|
||||
self._velocity = 0.0
|
||||
self._is_dragging = False
|
||||
self._last_mouse_x = 0.0
|
||||
self._velocity = 0.0
|
||||
self._smooth_value = get_value()
|
||||
self._press_start_x = 0.0
|
||||
self._press_start_time: float | None = None
|
||||
self._long_press_triggered = False
|
||||
|
||||
def _handle_mouse_release(self, mouse_pos: MousePos):
|
||||
if self._is_dragging:
|
||||
self._is_dragging = False
|
||||
self._is_pressed = False
|
||||
def _handle_mouse_press(self, mouse_pos: MousePos):
|
||||
if rl.check_collision_point_rec(mouse_pos, self._hit_rect) and self.enabled:
|
||||
self._is_pressed = True
|
||||
self._last_mouse_x = mouse_pos.x
|
||||
self._velocity = 0.0
|
||||
self._press_start_x = mouse_pos.x
|
||||
self._press_start_time = time.monotonic()
|
||||
self._long_press_triggered = False
|
||||
|
||||
def _handle_mouse_event(self, mouse_event):
|
||||
if not rl.check_collision_point_rec(mouse_event.pos, self._hit_rect):
|
||||
if not self._is_dragging:
|
||||
self._plate_target = 0.0
|
||||
def _handle_mouse_release(self, mouse_pos: MousePos):
|
||||
self._is_dragging = False
|
||||
self._is_pressed = False
|
||||
self._press_start_time = None
|
||||
|
||||
if self._is_dragging:
|
||||
dt = rl.get_frame_time()
|
||||
current_val = self.get_value()
|
||||
mouse_pos = mouse_event.pos
|
||||
dx = mouse_pos.x - self._last_mouse_x
|
||||
self._velocity = dx / max(dt, 0.001)
|
||||
self._last_mouse_x = mouse_pos.x
|
||||
def _handle_mouse_event(self, mouse_event):
|
||||
if not rl.check_collision_point_rec(mouse_event.pos, self._hit_rect):
|
||||
if not self._is_dragging and not self._press_start_time:
|
||||
self._plate_target = 0.0
|
||||
|
||||
rect_w = self._rect.width
|
||||
if rect_w > 0:
|
||||
val_range = self.max_val - self.min_val
|
||||
val_dx = (dx / rect_w) * val_range
|
||||
new_val = current_val + val_dx
|
||||
if self._press_start_time and not self._is_dragging and not self._long_press_triggered:
|
||||
dx = abs(mouse_event.pos.x - self._press_start_x)
|
||||
if dx > self.DRAG_THRESHOLD:
|
||||
self._is_dragging = True
|
||||
self._press_start_time = None
|
||||
else:
|
||||
elapsed = time.monotonic() - self._press_start_time
|
||||
if elapsed >= self.LONG_PRESS_THRESHOLD:
|
||||
self._long_press_triggered = True
|
||||
self._press_start_time = None
|
||||
if self.on_test:
|
||||
self.on_test()
|
||||
|
||||
abs_vel = abs(self._velocity)
|
||||
snap_threshold = 800
|
||||
coarse_step = 10 if val_range >= 100 else self.step * 5
|
||||
dynamic_step = coarse_step if abs_vel > snap_threshold else self.step
|
||||
if self._is_dragging:
|
||||
dt = rl.get_frame_time()
|
||||
current_val = self.get_value()
|
||||
mouse_pos = mouse_event.pos
|
||||
dx = mouse_pos.x - self._last_mouse_x
|
||||
self._velocity = dx / max(dt, 0.001)
|
||||
self._last_mouse_x = mouse_pos.x
|
||||
|
||||
snapped = round(new_val / dynamic_step) * dynamic_step
|
||||
snapped = max(self.min_val, min(self.max_val, snapped))
|
||||
rect_w = self._rect.width
|
||||
if rect_w > 0:
|
||||
val_range = self.max_val - self.min_val
|
||||
val_dx = (dx / rect_w) * val_range
|
||||
new_val = current_val + val_dx
|
||||
|
||||
if abs(snapped - current_val) >= self.step:
|
||||
self.set_value(float(snapped))
|
||||
abs_vel = abs(self._velocity)
|
||||
snap_threshold = 800
|
||||
coarse_step = 10 if val_range >= 100 else self.step * 5
|
||||
dynamic_step = coarse_step if abs_vel > snap_threshold else self.step
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
enabled = self.enabled
|
||||
current_val = self.get_value()
|
||||
dt = rl.get_frame_time()
|
||||
snapped = round(new_val / dynamic_step) * dynamic_step
|
||||
snapped = max(self.min_val, min(self.max_val, snapped))
|
||||
|
||||
self._smooth_value += (current_val - self._smooth_value) * (1 - math.exp(-dt / 0.1))
|
||||
if abs(snapped - current_val) >= self.step:
|
||||
self.set_value(float(snapped))
|
||||
|
||||
self.surface_color = self._active_color if enabled else self._disabled_color
|
||||
if not enabled:
|
||||
self._plate_offset = 0.0
|
||||
self._plate_target = 0.0
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
enabled = self.enabled
|
||||
current_val = self.get_value()
|
||||
dt = rl.get_frame_time()
|
||||
|
||||
face = self._render_layers(rect)
|
||||
self._smooth_value += (current_val - self._smooth_value) * (1 - math.exp(-dt / 0.1))
|
||||
|
||||
frac = (self._smooth_value - self.min_val) / (self.max_val - self.min_val)
|
||||
fill_w = face.width * frac
|
||||
if fill_w > 1:
|
||||
fill_rect = rl.Rectangle(face.x, face.y, fill_w, face.height)
|
||||
fill_color = rl.Color(
|
||||
min(self.surface_color.r + 30, 255),
|
||||
min(self.surface_color.g + 30, 255),
|
||||
min(self.surface_color.b + 30, 255),
|
||||
140
|
||||
)
|
||||
rl.draw_rectangle_rounded(fill_rect, TILE_RADIUS, 10, fill_color)
|
||||
edge_x = face.x + fill_w
|
||||
rl.draw_rectangle_rec(rl.Rectangle(edge_x - 3, face.y, 3, face.height), rl.Color(255, 255, 255, 80))
|
||||
self.surface_color = self._active_color if enabled else self._disabled_color
|
||||
if not enabled:
|
||||
self._plate_offset = 0.0
|
||||
self._plate_target = 0.0
|
||||
|
||||
line_heights = [28, 28]
|
||||
_, ty = self._centered_content(face, self._icon, 0.75, 28, len(line_heights), line_heights)
|
||||
content_pad = SPACING.tile_content
|
||||
max_w = face.width - (content_pad * 2)
|
||||
face = self._render_layers(rect)
|
||||
|
||||
self._draw_text_fit(self._font, self.title, rl.Vector2(face.x + content_pad, ty), max_w, 28, align_center=True, uppercase=True)
|
||||
frac = (self._smooth_value - self.min_val) / (self.max_val - self.min_val)
|
||||
fill_w = face.width * frac
|
||||
if fill_w > 1:
|
||||
fill_rect = rl.Rectangle(face.x, face.y, fill_w, face.height)
|
||||
fill_color = rl.Color(
|
||||
min(self.surface_color.r + 30, 255),
|
||||
min(self.surface_color.g + 30, 255),
|
||||
min(self.surface_color.b + 30, 255),
|
||||
140,
|
||||
)
|
||||
rl.draw_rectangle_rounded(fill_rect, TILE_RADIUS, 10, fill_color)
|
||||
edge_x = face.x + fill_w
|
||||
rl.draw_rectangle_rec(rl.Rectangle(edge_x - 3, face.y, 3, face.height), rl.Color(255, 255, 255, 80))
|
||||
|
||||
val_str = self.labels.get(current_val, f"{int(current_val)}{self.unit}")
|
||||
self._draw_text_fit(self._font, val_str, rl.Vector2(face.x + content_pad, ty + 28 + SPACING.line_gap), max_w, 28, align_center=True, uppercase=True)
|
||||
line_heights = [28, 28]
|
||||
_, ty = self._centered_content(face, self._icon, 0.75, 28, len(line_heights), line_heights)
|
||||
content_pad = SPACING.tile_content
|
||||
max_w = face.width - (content_pad * 2)
|
||||
|
||||
if self.desc:
|
||||
self._draw_text_fit(self._font_desc, self.desc, rl.Vector2(face.x + content_pad, ty + 28 + SPACING.line_gap + 34), max_w, 18, align_center=True)
|
||||
self._draw_text_fit(self._font, self.title, rl.Vector2(face.x + content_pad, ty), max_w, 28, align_center=True, uppercase=True)
|
||||
|
||||
val_str = self.labels.get(current_val, f"{int(current_val)}{self.unit}")
|
||||
self._draw_text_fit(self._font, val_str, rl.Vector2(face.x + content_pad, ty + 28 + SPACING.line_gap), max_w, 28, align_center=True, uppercase=True)
|
||||
|
||||
if self.desc:
|
||||
self._draw_text_fit(self._font_desc, self.desc, rl.Vector2(face.x + content_pad, ty + 28 + SPACING.line_gap + 34), max_w, 18, align_center=True)
|
||||
|
||||
|
||||
class AetherSlider(Widget):
|
||||
|
||||
@@ -106,7 +106,8 @@ class StarPilotPanel(Widget):
|
||||
icon_path=cat.get("icon"),
|
||||
bg_color=cat.get("color"),
|
||||
is_enabled=cat.get("is_enabled"),
|
||||
desc=tr(cat.get("desc", ""))
|
||||
desc=tr(cat.get("desc", "")),
|
||||
on_test=cat.get("on_test"),
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
@@ -80,8 +80,6 @@ class StarPilotVolumeControlLayout(StarPilotPanel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._init_sound_player()
|
||||
self._pending_sound_test = None
|
||||
self._last_sound_change_time = 0.0
|
||||
|
||||
self.SECTIONS = [
|
||||
{
|
||||
@@ -95,12 +93,6 @@ class StarPilotVolumeControlLayout(StarPilotPanel):
|
||||
]
|
||||
self._rebuild_grid()
|
||||
|
||||
def _update_state(self):
|
||||
super()._update_state()
|
||||
if self._pending_sound_test and (time.monotonic() - self._last_sound_change_time) > 0.8:
|
||||
self._test_sound(self._pending_sound_test)
|
||||
self._pending_sound_test = None
|
||||
|
||||
def _build_volume_categories(self):
|
||||
cats = []
|
||||
for key in StarPilotSoundsLayout.VOLUME_KEYS:
|
||||
@@ -114,21 +106,23 @@ class StarPilotVolumeControlLayout(StarPilotPanel):
|
||||
if new_v != 101 and new_v < self.VOLUME_INFO[k]["min"]:
|
||||
new_v = self.VOLUME_INFO[k]["min"]
|
||||
self._params.put_int(k, new_v)
|
||||
self._pending_sound_test = k
|
||||
self._last_sound_change_time = time.monotonic()
|
||||
|
||||
def test_cb(k=key):
|
||||
self._test_sound(k)
|
||||
|
||||
cats.append({
|
||||
"title": info["title"],
|
||||
"type": "slider",
|
||||
"get_value": get_val,
|
||||
"set_value": set_val,
|
||||
"on_test": test_cb,
|
||||
"min_val": 0,
|
||||
"max_val": 101,
|
||||
"step": 1,
|
||||
"unit": "%",
|
||||
"labels": {0: tr("Muted"), 101: tr("Auto")},
|
||||
"icon": info["icon"],
|
||||
"color": "#3B82F6"
|
||||
"color": "#3B82F6",
|
||||
})
|
||||
return cats
|
||||
|
||||
@@ -150,7 +144,7 @@ class StarPilotVolumeControlLayout(StarPilotPanel):
|
||||
"unit": " " + tr("min"),
|
||||
"labels": {0: tr("Off"), 1: tr("1 minute")},
|
||||
"icon": self.COOLDOWN_INFO["icon"],
|
||||
"color": "#3B82F6"
|
||||
"color": "#3B82F6",
|
||||
}]
|
||||
|
||||
@classmethod
|
||||
|
||||
Reference in New Issue
Block a user