mirror of
https://github.com/MoreTore/openpilot.git
synced 2026-07-26 20:32:04 +08:00
BigUP WIP: Sound Polish
This commit is contained in:
@@ -3061,91 +3061,288 @@ class AetherSliderDialog(Widget):
|
||||
step: float,
|
||||
current_val: float,
|
||||
on_close: Callable,
|
||||
presets: list[float] | None = None,
|
||||
unit: str = "",
|
||||
labels: dict[float, str] | None = None,
|
||||
color: rl.Color | str = "#F57371",
|
||||
on_change: Callable[[float], None] | None = None,
|
||||
):
|
||||
super().__init__()
|
||||
self.title, self._user_callback = title, on_close
|
||||
self._on_change = on_change
|
||||
self._color = hex_to_color(color) if isinstance(color, str) else color
|
||||
self._font_title, self._font_btn = gui_app.font(FontWeight.BOLD), gui_app.font(FontWeight.BOLD)
|
||||
self._slider = AetherSlider(min_val, max_val, step, current_val, self._on_slider_change, unit, labels, self._color)
|
||||
self._current_val, self._is_pressed_ok, self._is_pressed_cancel = current_val, False, False
|
||||
self._ok_offset: float = 0.0
|
||||
self._cancel_offset: float = 0.0
|
||||
self._ok_target: float = 0.0
|
||||
self._cancel_target: float = 0.0
|
||||
self._font_value = gui_app.font(FontWeight.BOLD)
|
||||
self._current_val = current_val
|
||||
self.min_val = min_val
|
||||
self.max_val = max_val
|
||||
self.step = step
|
||||
self._presets = presets or []
|
||||
self._unit = unit
|
||||
self._labels = labels or {}
|
||||
self._preset_rects: list[tuple[float, rl.Rectangle]] = []
|
||||
self._pressed_zone: str | None = None
|
||||
self._is_pressed_ok = False
|
||||
self._is_pressed_cancel = False
|
||||
self._ok_offset = 0.0
|
||||
self._cancel_offset = 0.0
|
||||
self._ok_target = 0.0
|
||||
self._cancel_target = 0.0
|
||||
self._is_dragging = False
|
||||
self._val_on_press = current_val
|
||||
|
||||
def _on_slider_change(self, val):
|
||||
self._current_val = val
|
||||
self._ok_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
self._cancel_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
self._minus_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
self._plus_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
self._track_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
self._thumb_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
|
||||
def _value_fraction(self, value: float) -> float:
|
||||
val_range = self.max_val - self.min_val
|
||||
if val_range == 0:
|
||||
return 0.0
|
||||
return max(0.0, min(1.0, (value - self.min_val) / val_range))
|
||||
|
||||
def _clamp_and_snap(self, val: float) -> float:
|
||||
if self.step <= 0:
|
||||
return max(self.min_val, min(self.max_val, val))
|
||||
snapped = round((val - self.min_val) / self.step) * self.step + self.min_val
|
||||
return max(self.min_val, min(self.max_val, snapped))
|
||||
|
||||
def _update_val_from_mouse(self, mouse_pos: MousePos) -> None:
|
||||
if self._track_rect.width <= 0:
|
||||
return
|
||||
rel_x = max(0.0, min(1.0, (mouse_pos.x - self._track_rect.x) / self._track_rect.width))
|
||||
val = self.min_val + rel_x * (self.max_val - self.min_val)
|
||||
self._current_val = self._clamp_and_snap(val)
|
||||
|
||||
def formatted_value(self) -> str:
|
||||
return format_adjustor_value(self._current_val, step=self.step, unit=self._unit, labels=self._labels)
|
||||
|
||||
def _handle_mouse_press(self, mouse_pos: MousePos):
|
||||
self._slider._handle_mouse_press(mouse_pos)
|
||||
self._val_on_press = self._current_val
|
||||
if rl.check_collision_point_rec(mouse_pos, self._ok_rect):
|
||||
self._is_pressed_ok = True
|
||||
self._ok_target = 1.0
|
||||
return
|
||||
if rl.check_collision_point_rec(mouse_pos, self._cancel_rect):
|
||||
self._is_pressed_cancel = True
|
||||
self._cancel_target = 1.0
|
||||
return
|
||||
|
||||
for value, preset_rect in self._preset_rects:
|
||||
if rl.check_collision_point_rec(mouse_pos, preset_rect):
|
||||
self._pressed_zone = f"preset:{value}"
|
||||
return
|
||||
|
||||
if rl.check_collision_point_rec(mouse_pos, self._minus_rect):
|
||||
self._pressed_zone = "minus"
|
||||
return
|
||||
if rl.check_collision_point_rec(mouse_pos, self._plus_rect):
|
||||
self._pressed_zone = "plus"
|
||||
return
|
||||
|
||||
hit_track = _inflate_rect(self._track_rect, 0, 24)
|
||||
if rl.check_collision_point_rec(mouse_pos, hit_track):
|
||||
self._is_dragging = True
|
||||
self._update_val_from_mouse(mouse_pos)
|
||||
return
|
||||
|
||||
def _handle_mouse_release(self, mouse_pos: MousePos):
|
||||
self._slider._handle_mouse_release(mouse_pos)
|
||||
is_ok = self._is_pressed_ok
|
||||
is_cancel = self._is_pressed_cancel
|
||||
|
||||
if self._is_pressed_ok:
|
||||
self._ok_target = 0.0
|
||||
if rl.check_collision_point_rec(mouse_pos, self._ok_rect):
|
||||
gui_app.pop_widget()
|
||||
self._user_callback(DialogResult.CONFIRM, self._current_val)
|
||||
self._is_pressed_ok = False
|
||||
if self._is_pressed_cancel:
|
||||
elif self._is_pressed_cancel:
|
||||
self._cancel_target = 0.0
|
||||
if rl.check_collision_point_rec(mouse_pos, self._cancel_rect):
|
||||
gui_app.pop_widget()
|
||||
self._user_callback(DialogResult.CANCEL, self._current_val)
|
||||
self._is_pressed_cancel = False
|
||||
elif self._pressed_zone:
|
||||
if self._pressed_zone.startswith("preset:"):
|
||||
try:
|
||||
preset_value = float(self._pressed_zone.split(":", 1)[1])
|
||||
except ValueError:
|
||||
preset_value = None
|
||||
if preset_value is not None:
|
||||
for value, preset_rect in self._preset_rects:
|
||||
if value == preset_value and rl.check_collision_point_rec(mouse_pos, preset_rect):
|
||||
self._current_val = preset_value
|
||||
break
|
||||
elif self._pressed_zone == "minus":
|
||||
if rl.check_collision_point_rec(mouse_pos, self._minus_rect):
|
||||
self._current_val = self._clamp_and_snap(self._current_val - self.step)
|
||||
elif self._pressed_zone == "plus":
|
||||
if rl.check_collision_point_rec(mouse_pos, self._plus_rect):
|
||||
self._current_val = self._clamp_and_snap(self._current_val + self.step)
|
||||
self._pressed_zone = None
|
||||
|
||||
if self._is_dragging:
|
||||
self._is_dragging = False
|
||||
|
||||
if not is_ok and not is_cancel:
|
||||
if self._current_val != self._val_on_press and self._on_change:
|
||||
self._on_change(self._current_val)
|
||||
|
||||
def _handle_mouse_event(self, mouse_event):
|
||||
if self._is_dragging:
|
||||
self._update_val_from_mouse(mouse_event.pos)
|
||||
|
||||
def _render_preset_chip(self, rect: rl.Rectangle, text: str, *, current: bool, pressed: bool):
|
||||
fill = rl.Color(255, 255, 255, 5)
|
||||
border = rl.Color(255, 255, 255, 14)
|
||||
text_color = AetherListColors.SUBTEXT
|
||||
if current:
|
||||
fill = _mix_colors(rl.Color(18, 22, 28, 255), self._color, 0.22, alpha=255)
|
||||
border = _with_alpha(self._color, 72)
|
||||
text_color = AetherListColors.HEADER
|
||||
elif pressed:
|
||||
fill = rl.Color(255, 255, 255, 10)
|
||||
border = rl.Color(255, 255, 255, 22)
|
||||
|
||||
_draw_rounded_fill(rect, fill, radius_px=16)
|
||||
_draw_rounded_stroke(rect, border, radius_px=16)
|
||||
_draw_text_fit_common(
|
||||
gui_app.font(FontWeight.MEDIUM),
|
||||
text,
|
||||
rl.Vector2(rect.x + 10, rect.y + (rect.height - 18) / 2),
|
||||
max(1.0, rect.width - 20),
|
||||
18,
|
||||
align_center=True,
|
||||
color=text_color,
|
||||
)
|
||||
|
||||
def _render(self, rect: rl.Rectangle):
|
||||
dt = rl.get_frame_time()
|
||||
self._ok_offset += (self._ok_target - self._ok_offset) * (1 - math.exp(-dt / PLATE_TAU))
|
||||
self._cancel_offset += (self._cancel_target - self._cancel_offset) * (1 - math.exp(-dt / PLATE_TAU))
|
||||
rl.draw_rectangle(0, 0, gui_app.width, gui_app.height, rl.Color(0, 0, 0, 160))
|
||||
dialog_margin = SPACING.xxl
|
||||
dialog_w = min(1000, max(640, rect.width - dialog_margin * 2))
|
||||
dialog_h = min(500, max(360, rect.height - dialog_margin * 2))
|
||||
button_height = min(80, max(64, int(dialog_h * 0.16)))
|
||||
button_width = min(350, max(180, int((dialog_w - SPACING.lg * 3) / 2)))
|
||||
|
||||
has_presets = len(self._presets) > 0
|
||||
dialog_w = 880
|
||||
dialog_h = 500 if has_presets else 440
|
||||
button_height = 72
|
||||
button_width = 320
|
||||
|
||||
dx, dy = rect.x + (rect.width - dialog_w) / 2, rect.y + (rect.height - dialog_h) / 2
|
||||
self._ok_rect = rl.Rectangle(dx + dialog_w - button_width - SPACING.lg, dy + dialog_h - button_height - SPACING.lg, button_width, button_height)
|
||||
self._cancel_rect = rl.Rectangle(dx + SPACING.lg, dy + dialog_h - button_height - SPACING.lg, button_width, button_height)
|
||||
self._ok_rect = rl.Rectangle(dx + dialog_w - button_width - 48, dy + dialog_h - button_height - 36, button_width, button_height)
|
||||
self._cancel_rect = rl.Rectangle(dx + 48, dy + dialog_h - button_height - 36, button_width, button_height)
|
||||
|
||||
d_rect = _snap_rect(rl.Rectangle(dx, dy, dialog_w, dialog_h))
|
||||
_draw_rounded_fill(d_rect, rl.Color(13, 16, 22, 255), radius_px=24)
|
||||
_draw_rounded_stroke(d_rect, rl.Color(255, 255, 255, 24), radius_px=24)
|
||||
rl.draw_rectangle_rec(rl.Rectangle(d_rect.x, d_rect.y, d_rect.width, 2), _with_alpha(self._color, 40))
|
||||
title_size = max(30, min(50, int(dialog_w * 0.05)))
|
||||
_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, 3), self._color)
|
||||
|
||||
title_size = 28
|
||||
ts = measure_text_cached(self._font_title, self.title, title_size)
|
||||
rl.draw_text_ex(self._font_title, self.title, rl.Vector2(round(dx + (dialog_w - ts.x) / 2), round(dy + SPACING.xxl)), title_size, 0, rl.WHITE)
|
||||
slider_y = dy + max(120, int(dialog_h * 0.38))
|
||||
slider_h = min(100, max(72, int(dialog_h * 0.22)))
|
||||
slider_rect = rl.Rectangle(dx + SPACING.xxl, slider_y, dialog_w - (SPACING.xxl * 2), slider_h)
|
||||
self._slider.render(slider_rect)
|
||||
rl.draw_text_ex(self._font_title, self.title, rl.Vector2(round(dx + (dialog_w - ts.x) / 2), round(dy + 32)), title_size, 0, rl.WHITE)
|
||||
|
||||
# Large value display below title
|
||||
val_str = self.formatted_value()
|
||||
val_size = 48
|
||||
vts = measure_text_cached(self._font_value, val_str, val_size)
|
||||
rl.draw_text_ex(self._font_value, val_str, rl.Vector2(round(dx + (dialog_w - vts.x) / 2), round(dy + 80)), val_size, 0, self._color)
|
||||
|
||||
# Render presets below the value display (if any)
|
||||
presets_y = dy + 154
|
||||
self._preset_rects.clear()
|
||||
if has_presets:
|
||||
chip_h = 52.0
|
||||
chip_gap = 16.0
|
||||
chip_w = max(68.0, (dialog_w - 48 * 2 - chip_gap * (len(self._presets) - 1)) / max(1, len(self._presets)))
|
||||
for index, val in enumerate(self._presets):
|
||||
chip_x = dx + 48 + index * (chip_w + chip_gap)
|
||||
chip_rect = _snap_rect(rl.Rectangle(chip_x, presets_y, chip_w, chip_h))
|
||||
self._preset_rects.append((val, chip_rect))
|
||||
formatted_label = format_adjustor_value(val, step=self.step, unit=self._unit, labels=self._labels)
|
||||
self._render_preset_chip(
|
||||
chip_rect,
|
||||
formatted_label,
|
||||
current=abs(self._current_val - val) <= 0.5 * self.step,
|
||||
pressed=self._pressed_zone == f"preset:{val}",
|
||||
)
|
||||
slider_y = dy + 254
|
||||
else:
|
||||
slider_y = dy + 180
|
||||
|
||||
# Slider
|
||||
btn_size = 54
|
||||
self._minus_rect = _snap_rect(rl.Rectangle(dx + 48, slider_y - btn_size / 2, btn_size, btn_size))
|
||||
self._plus_rect = _snap_rect(rl.Rectangle(dx + dialog_w - 48 - btn_size, slider_y - btn_size / 2, btn_size, btn_size))
|
||||
|
||||
# Draw minus button
|
||||
minus_pressed = self._pressed_zone == "minus"
|
||||
_draw_rounded_fill(self._minus_rect, rl.Color(255, 255, 255, 14 if minus_pressed else 8), radius_px=27)
|
||||
_draw_rounded_stroke(self._minus_rect, rl.Color(255, 255, 255, 28 if minus_pressed else 18), radius_px=27)
|
||||
mts = measure_text_cached(self._font_btn, "-", 24)
|
||||
rl.draw_text_ex(self._font_btn, "-", rl.Vector2(round(self._minus_rect.x + (btn_size - mts.x) / 2), round(self._minus_rect.y + (btn_size - mts.y) / 2)), 24, 0, rl.WHITE)
|
||||
|
||||
# Draw plus button
|
||||
plus_pressed = self._pressed_zone == "plus"
|
||||
_draw_rounded_fill(self._plus_rect, rl.Color(255, 255, 255, 14 if plus_pressed else 8), radius_px=27)
|
||||
_draw_rounded_stroke(self._plus_rect, rl.Color(255, 255, 255, 28 if plus_pressed else 18), radius_px=27)
|
||||
pts = measure_text_cached(self._font_btn, "+", 24)
|
||||
rl.draw_text_ex(self._font_btn, "+", rl.Vector2(round(self._plus_rect.x + (btn_size - pts.x) / 2), round(self._plus_rect.y + (btn_size - pts.y) / 2)), 24, 0, rl.WHITE)
|
||||
|
||||
# Draw track
|
||||
track_x = self._minus_rect.x + btn_size + 24
|
||||
track_w = self._plus_rect.x - 24 - track_x
|
||||
track_h = 6
|
||||
track_y = slider_y - track_h / 2
|
||||
self._track_rect = _snap_rect(rl.Rectangle(track_x, track_y, track_w, track_h))
|
||||
|
||||
_draw_rounded_fill(self._track_rect, rl.Color(255, 255, 255, 14), radius_px=3)
|
||||
_draw_rounded_stroke(self._track_rect, rl.Color(255, 255, 255, 8), radius_px=3)
|
||||
|
||||
# Draw ticks at preset values (or custom ticks if no presets)
|
||||
ticks_to_draw = self._presets
|
||||
if not has_presets:
|
||||
ticks_to_draw = [self.min_val, (self.min_val + self.max_val) / 2, self.max_val]
|
||||
|
||||
for val in ticks_to_draw:
|
||||
frac = self._value_fraction(val)
|
||||
tick_x = track_x + frac * track_w
|
||||
rl.draw_rectangle_rec(rl.Rectangle(tick_x - 1, track_y - 5, 2, 16), rl.Color(255, 255, 255, 28))
|
||||
|
||||
# Draw active fill
|
||||
fill_frac = self._value_fraction(self._current_val)
|
||||
fill_w = fill_frac * track_w
|
||||
if fill_w > 0:
|
||||
fill_rect = _snap_rect(rl.Rectangle(track_x, track_y, fill_w, track_h))
|
||||
_draw_rounded_fill(fill_rect, self._color, radius_px=3)
|
||||
|
||||
# Draw thumb
|
||||
thumb_w = 16
|
||||
thumb_h = 36
|
||||
thumb_x = track_x + fill_frac * track_w
|
||||
self._thumb_rect = _snap_rect(rl.Rectangle(thumb_x - thumb_w / 2, slider_y - thumb_h / 2, thumb_w, thumb_h))
|
||||
_draw_rounded_fill(self._thumb_rect, rl.WHITE, radius_px=8)
|
||||
_draw_rounded_stroke(self._thumb_rect, rl.Color(20, 22, 28, 46), radius_px=8)
|
||||
|
||||
# Cancel Button
|
||||
c_face_x = self._cancel_rect.x
|
||||
c_face_y = self._cancel_rect.y + min(1.0, GEOMETRY_OFFSET * self._cancel_offset * 0.1)
|
||||
c_face = _snap_rect(rl.Rectangle(c_face_x, c_face_y, button_width, button_height))
|
||||
_draw_rounded_fill(c_face, rl.Color(34, 38, 48, 255), radius_px=16)
|
||||
_draw_rounded_stroke(c_face, rl.Color(255, 255, 255, 24), radius_px=16)
|
||||
rl.draw_rectangle_rec(rl.Rectangle(c_face.x, c_face.y, c_face.width, 1), rl.Color(255, 255, 255, 12))
|
||||
button_text_size = max(24, min(35, int(button_height * 0.42)))
|
||||
cts = measure_text_cached(self._font_btn, tr("CANCEL"), button_text_size)
|
||||
cancel_text_pos = rl.Vector2(c_face_x + (button_width - cts.x) / 2, c_face_y + (button_height - cts.y) / 2)
|
||||
rl.draw_text_ex(self._font_btn, tr("CANCEL"), rl.Vector2(round(cancel_text_pos.x), round(cancel_text_pos.y)), button_text_size, 0, rl.WHITE)
|
||||
_draw_rounded_fill(c_face, rl.Color(34, 38, 48, 255), radius_px=18)
|
||||
_draw_rounded_stroke(c_face, rl.Color(255, 255, 255, 20), radius_px=18)
|
||||
cts = measure_text_cached(self._font_btn, tr("CANCEL"), 24)
|
||||
rl.draw_text_ex(self._font_btn, tr("CANCEL"), rl.Vector2(round(c_face_x + (button_width - cts.x) / 2), round(c_face_y + (button_height - cts.y) / 2)), 24, 0, rl.WHITE)
|
||||
|
||||
# OK Button
|
||||
o_face_x = self._ok_rect.x
|
||||
o_face_y = self._ok_rect.y + min(1.0, GEOMETRY_OFFSET * self._ok_offset * 0.1)
|
||||
o_face = _snap_rect(rl.Rectangle(o_face_x, o_face_y, button_width, button_height))
|
||||
_draw_rounded_fill(o_face, _mix_colors(rl.Color(34, 38, 48, 255), self._color, 0.40), radius_px=16)
|
||||
_draw_rounded_stroke(o_face, _with_alpha(self._color, 130), radius_px=16)
|
||||
rl.draw_rectangle_rec(rl.Rectangle(o_face.x, o_face.y, o_face.width, 1), rl.Color(255, 255, 255, 18))
|
||||
ots = measure_text_cached(self._font_btn, tr("OK"), button_text_size)
|
||||
ok_text_pos = rl.Vector2(o_face_x + (button_width - ots.x) / 2, o_face_y + (button_height - ots.y) / 2)
|
||||
rl.draw_text_ex(self._font_btn, tr("OK"), rl.Vector2(round(ok_text_pos.x), round(ok_text_pos.y)), button_text_size, 0, rl.WHITE)
|
||||
_draw_rounded_fill(o_face, self._color, radius_px=18)
|
||||
_draw_rounded_stroke(o_face, _with_alpha(self._color, 150), radius_px=18)
|
||||
ots = measure_text_cached(self._font_btn, tr("OK"), 24)
|
||||
rl.draw_text_ex(self._font_btn, tr("OK"), rl.Vector2(round(o_face_x + (button_width - ots.x) / 2), round(o_face_y + (button_height - ots.y) / 2)), 24, 0, rl.WHITE)
|
||||
return DialogResult.NO_ACTION
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from dataclasses import replace
|
||||
import math
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
@@ -10,7 +11,8 @@ from openpilot.starpilot.common.starpilot_variables import ACTIVE_THEME_PATH
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MouseEvent, MousePos
|
||||
from openpilot.system.ui.lib.multilang import tr, tr_noop
|
||||
from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets import Widget, DialogResult
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.widgets.label import gui_label
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state
|
||||
@@ -31,6 +33,7 @@ from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import (
|
||||
draw_section_header,
|
||||
draw_settings_panel_header,
|
||||
init_list_panel,
|
||||
AetherSliderDialog,
|
||||
)
|
||||
|
||||
PANEL_STYLE = panel_style_from_color("#E63956")
|
||||
@@ -58,8 +61,6 @@ class SoundsManagerView(Widget):
|
||||
self._controller = controller
|
||||
self._pressed_target: str | None = None
|
||||
self._adjustor_rows: dict[str, AetherAdjustorRow] = {}
|
||||
self._was_interacting: dict[str, bool] = {}
|
||||
self._active_adjustor_key: str | None = None
|
||||
self._can_click = True
|
||||
self._reset_rect = rl.Rectangle(0, 0, 0, 0)
|
||||
|
||||
@@ -75,10 +76,6 @@ class SoundsManagerView(Widget):
|
||||
self._forward_touch_valid()
|
||||
|
||||
def _forward_touch_valid(self):
|
||||
for adjustor in self._adjustor_rows.values():
|
||||
adjustor.set_touch_valid_callback(
|
||||
lambda: self._scroll_panel.is_touch_valid() or adjustor.is_interacting
|
||||
)
|
||||
self._toggle_grid.set_touch_valid_callback(
|
||||
lambda: self._scroll_panel.is_touch_valid()
|
||||
)
|
||||
@@ -113,24 +110,18 @@ class SoundsManagerView(Widget):
|
||||
for key in self._controller.VOLUME_KEYS:
|
||||
info = self._controller.VOLUME_INFO[key]
|
||||
|
||||
def on_change(v, k=key, min_v=info["min"]):
|
||||
new_v = int(v)
|
||||
if new_v != 101 and new_v < min_v:
|
||||
new_v = min_v
|
||||
self._controller._params.put_int(k, new_v)
|
||||
|
||||
adjustor = AetherAdjustorRow(
|
||||
tr(info["title"]),
|
||||
tr(info["subtitle"]),
|
||||
0.0, 101.0, 1.0,
|
||||
get_value=lambda k=key: float(self._controller._params.get_int(k, return_default=True, default=100)),
|
||||
on_change=on_change,
|
||||
on_change=lambda _v: None,
|
||||
on_commit=None,
|
||||
unit="%",
|
||||
labels={0.0: tr("Muted"), 101.0: tr("Auto")},
|
||||
presets=[0, 25, 50, 75, 101],
|
||||
is_active=lambda k=key: self._active_adjustor_key == k,
|
||||
set_active=lambda active, k=key: self._set_active_adjustor(k, active),
|
||||
is_active=lambda: False,
|
||||
set_active=lambda active, k=key: self._show_volume_slider(k) if active else None,
|
||||
style=PANEL_STYLE,
|
||||
color=PANEL_STYLE.accent,
|
||||
)
|
||||
@@ -138,8 +129,9 @@ class SoundsManagerView(Widget):
|
||||
|
||||
cd_key = self._controller.COOLDOWN_KEY
|
||||
cd_info = self._controller.COOLDOWN_INFO
|
||||
def on_cd_commit(v):
|
||||
self._controller._params.put_int(cd_key, int(v))
|
||||
def on_cd_close(res, val):
|
||||
if res == DialogResult.CONFIRM:
|
||||
self._controller._params.put_int(cd_key, int(val))
|
||||
|
||||
cd_adjustor = AetherAdjustorRow(
|
||||
tr(cd_info["title"]),
|
||||
@@ -147,17 +139,69 @@ class SoundsManagerView(Widget):
|
||||
0.0, float(cd_info["max"]), 1.0,
|
||||
get_value=lambda: float(self._controller._params.get_int(cd_key, return_default=True, default=0)),
|
||||
on_change=lambda _v: None,
|
||||
on_commit=on_cd_commit,
|
||||
on_commit=None,
|
||||
unit=" " + tr("min"),
|
||||
labels={0.0: tr("Off"), 1.0: tr("1 min")},
|
||||
presets=[0, 1, 5, 10, 20, 30],
|
||||
is_active=lambda: self._active_adjustor_key == cd_key,
|
||||
set_active=lambda active: self._set_active_adjustor(cd_key, active),
|
||||
is_active=lambda: False,
|
||||
set_active=lambda active: gui_app.push_widget(
|
||||
AetherSliderDialog(
|
||||
title=tr(cd_info["title"]),
|
||||
min_val=0.0,
|
||||
max_val=float(cd_info["max"]),
|
||||
step=1.0,
|
||||
current_val=float(self._controller._params.get_int(cd_key, return_default=True, default=0)),
|
||||
on_close=on_cd_close,
|
||||
presets=[0.0, 1.0, 5.0, 10.0, 20.0, 30.0],
|
||||
unit=" " + tr("min"),
|
||||
labels={0.0: tr("Off"), 1.0: tr("1 min")},
|
||||
color=PANEL_STYLE.accent,
|
||||
)
|
||||
) if active else None,
|
||||
style=PANEL_STYLE,
|
||||
color=PANEL_STYLE.accent,
|
||||
)
|
||||
self._adjustor_rows[cd_key] = cd_adjustor
|
||||
|
||||
def _show_volume_slider(self, key: str):
|
||||
info = self._controller.VOLUME_INFO[key]
|
||||
min_v = info["min"]
|
||||
original_val = self._controller._params.get_int(key, return_default=True, default=100)
|
||||
|
||||
def on_close(res, val):
|
||||
if res == DialogResult.CONFIRM:
|
||||
new_v = int(val)
|
||||
if new_v != 101 and new_v < min_v:
|
||||
new_v = min_v
|
||||
self._controller._params.put_int(key, new_v)
|
||||
else:
|
||||
self._controller._params.put_int(key, original_val)
|
||||
|
||||
def on_change(val):
|
||||
new_v = int(val)
|
||||
if new_v != 101 and new_v < min_v:
|
||||
new_v = min_v
|
||||
self._controller._params.put_int(key, new_v)
|
||||
self._controller._test_sound(key)
|
||||
|
||||
current_val = float(original_val)
|
||||
dialog_title = tr(info["title"])
|
||||
gui_app.push_widget(
|
||||
AetherSliderDialog(
|
||||
title=dialog_title,
|
||||
min_val=0.0,
|
||||
max_val=101.0,
|
||||
step=1.0,
|
||||
current_val=current_val,
|
||||
on_close=on_close,
|
||||
presets=[0.0, 25.0, 50.0, 75.0, 101.0],
|
||||
unit="%",
|
||||
labels={0.0: tr("Muted"), 101.0: tr("Auto")},
|
||||
color=PANEL_STYLE.accent,
|
||||
on_change=on_change,
|
||||
)
|
||||
)
|
||||
|
||||
def _handle_mouse_press(self, mouse_pos: MousePos):
|
||||
self._pressed_target = self._target_at(mouse_pos)
|
||||
self._can_click = True
|
||||
@@ -230,16 +274,6 @@ class SoundsManagerView(Widget):
|
||||
draw_list_scroll_fades(scroll_rect, self._content_height, self._scroll_offset,
|
||||
AetherListColors.PANEL_BG)
|
||||
|
||||
for key in self._controller.VOLUME_KEYS:
|
||||
adjustor = self._adjustor_rows[key]
|
||||
is_interacting = adjustor.is_interacting
|
||||
if self._was_interacting.get(key, False) and not is_interacting:
|
||||
self._controller._test_sound(key)
|
||||
if adjustor._preset_applied:
|
||||
adjustor._preset_applied = False
|
||||
self._controller._test_sound(key)
|
||||
self._was_interacting[key] = is_interacting
|
||||
|
||||
def _measure_content_height(self, content_width: float) -> float:
|
||||
col_width = (content_width - SECTION_GAP) / 2
|
||||
left_h = 0.0
|
||||
|
||||
@@ -127,6 +127,10 @@ def _install_aethergrid_stubs():
|
||||
widgets_mod.DialogResult = types.SimpleNamespace(CONFIRM=1, CANCEL=0, NO_ACTION=-1)
|
||||
sys.modules["openpilot.system.ui.widgets"] = widgets_mod
|
||||
|
||||
option_dialog_mod = types.ModuleType("openpilot.system.ui.widgets.option_dialog")
|
||||
option_dialog_mod.MultiOptionDialog = type("MultiOptionDialog", (), {})
|
||||
sys.modules["openpilot.system.ui.widgets.option_dialog"] = option_dialog_mod
|
||||
|
||||
label_mod = types.ModuleType("openpilot.system.ui.widgets.label")
|
||||
label_mod.gui_label = lambda *a, **k: None
|
||||
sys.modules["openpilot.system.ui.widgets.label"] = label_mod
|
||||
@@ -143,7 +147,7 @@ def _install_panel_stubs(aethergrid):
|
||||
|
||||
_register_modules({
|
||||
SECTIONED_PANEL_MODULE_NAME: sectioned_mod,
|
||||
"openpilot.common.params": types.SimpleNamespace(Params=type("Params", (), {})),
|
||||
"openpilot.common.params": types.SimpleNamespace(Params=type("Params", (), {}), UnknownKeyName=Exception),
|
||||
MODULE_NAME: aethergrid,
|
||||
})
|
||||
|
||||
@@ -354,6 +358,44 @@ class TestAethergridContracts(unittest.TestCase):
|
||||
self.assertGreaterEqual(row_h, 0)
|
||||
self.assertLessEqual(title_h, layout._title_height)
|
||||
|
||||
def test_slider_dialog_on_change_callback(self):
|
||||
mod = _import_aethergrid()
|
||||
captured_changes = []
|
||||
captured_close = []
|
||||
|
||||
dialog = mod.AetherSliderDialog(
|
||||
"Test", 0, 10, 1, 5,
|
||||
on_close=lambda res, val: captured_close.append((res, val)),
|
||||
color="#8B5CF6",
|
||||
on_change=lambda val: captured_changes.append(val)
|
||||
)
|
||||
|
||||
# Run render to compute button and track rects
|
||||
dialog._render(mod.rl.Rectangle(0, 0, 1920, 1080))
|
||||
|
||||
# Mock mouse position hitting the plus rect and press it
|
||||
plus_center = mod.rl.Vector2(
|
||||
dialog._plus_rect.x + dialog._plus_rect.width / 2,
|
||||
dialog._plus_rect.y + dialog._plus_rect.height / 2
|
||||
)
|
||||
|
||||
# Mock collision detection to return True when checking the plus button
|
||||
old_collision = mod.rl.check_collision_point_rec
|
||||
def mock_collision(point, rec):
|
||||
if rec == dialog._plus_rect:
|
||||
return True
|
||||
return False
|
||||
mod.rl.check_collision_point_rec = mock_collision
|
||||
|
||||
try:
|
||||
dialog._handle_mouse_press(plus_center)
|
||||
dialog._handle_mouse_release(plus_center)
|
||||
finally:
|
||||
mod.rl.check_collision_point_rec = old_collision
|
||||
|
||||
self.assertEqual(dialog._current_val, 6)
|
||||
self.assertEqual(captured_changes, [6])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user