car_ui/screens/setting/sound_screen.py
2026-01-09 04:32:08 +03:00

222 lines
6.3 KiB
Python

from PySide6.QtCore import Qt, QSettings, QTimer
from PySide6.QtGui import QFont
from PySide6.QtWidgets import (
QWidget,
QLabel,
QVBoxLayout,
QHBoxLayout,
QPushButton,
QSlider,
QButtonGroup,
QScrollArea,
QScroller,
)
from audio.system_volume import set_volume
import build_info
class SoundScreen(QWidget):
def __init__(self, on_eq):
super().__init__()
self._on_eq = on_eq
self._settings = QSettings("car_ui", "ui")
self._pending_volume: int | None = None
self._volume_apply_timer = QTimer(self)
self._volume_apply_timer.setSingleShot(True)
self._volume_apply_timer.timeout.connect(self._flush_volume)
root = QVBoxLayout(self)
root.setContentsMargins(0, 0, 0, 0)
root.setSpacing(12)
scroll = QScrollArea()
scroll.setWidgetResizable(True)
scroll.setFrameShape(QScrollArea.NoFrame)
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
scroller = QScroller.scroller(scroll.viewport())
scroller.grabGesture(
scroll.viewport(),
QScroller.LeftMouseButtonGesture
)
content = QWidget()
content_layout = QVBoxLayout(content)
content_layout.setContentsMargins(0, 0, 0, 0)
content_layout.setSpacing(12)
content_layout.addWidget(self._build_volume_card())
content_layout.addWidget(self._build_balance_card())
content_layout.addWidget(self._build_tone_card())
content_layout.addWidget(self._build_eq_card())
content_layout.addStretch(1)
scroll.setWidget(content)
root.addWidget(scroll, 1)
def _read_int(self, key: str, default: int) -> int:
return _read_int_setting(self._settings, key, default)
def _queue_volume_apply(self, value: int):
self._pending_volume = value
self._volume_apply_timer.start(120)
def _flush_volume(self):
if self._pending_volume is None:
return
self._set_system_volume(self._pending_volume)
def _set_system_volume(self, value: int):
set_volume(value)
def _build_volume_card(self) -> QWidget:
card, body = _card("Громкость")
default_value = build_info.DEFAULT_SOUND_VOLUME
value = self._read_int("sound/base_volume", default_value)
row, slider, value_label = _slider_row(
"Базовая громкость",
0,
100,
value,
lambda v: f"{v}%",
)
slider.valueChanged.connect(
lambda v: self._settings.setValue("sound/base_volume", v)
)
slider.valueChanged.connect(self._queue_volume_apply)
body.addWidget(row)
return card
def _build_balance_card(self) -> QWidget:
card, body = _card("Баланс и фейдер")
row, slider, value_label = _slider_row(
"Баланс L/R",
-50,
50,
0,
_format_balance,
)
body.addWidget(row)
row, slider, value_label = _slider_row(
"Фейдер F/R",
-50,
50,
0,
_format_fader,
)
body.addWidget(row)
return card
def _build_tone_card(self) -> QWidget:
card, body = _card("Тон")
row = QWidget()
row.setObjectName("SoundToneRow")
layout = QHBoxLayout(row)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(10)
group = QButtonGroup(row)
group.setExclusive(True)
for idx, label in enumerate(("Тёплый", "Нейтральный", "Яркий")):
btn = QPushButton(label)
btn.setObjectName("SoundToneBtn")
btn.setCheckable(True)
btn.setMinimumHeight(40)
btn.setFont(QFont("", 13, 600))
if idx == 1:
btn.setChecked(True)
group.addButton(btn)
layout.addWidget(btn, 1)
body.addWidget(row)
return card
def _build_eq_card(self) -> QWidget:
card, body = _card("Эквалайзер")
btn = QPushButton("Открыть эквалайзер")
btn.setObjectName("SoundEqBtn")
btn.setMinimumHeight(48)
btn.setFont(QFont("", 14, 700))
btn.clicked.connect(self._on_eq)
body.addWidget(btn)
return card
def _card(title: str) -> tuple[QWidget, QVBoxLayout]:
card = QWidget()
card.setObjectName("SoundCard")
layout = QVBoxLayout(card)
layout.setContentsMargins(14, 12, 14, 12)
layout.setSpacing(10)
header = QLabel(title)
header.setObjectName("SoundCardTitle")
header.setFont(QFont("", 14, 700))
layout.addWidget(header)
return card, layout
def _slider_row(
label: str,
minimum: int,
maximum: int,
value: int,
formatter,
) -> tuple[QWidget, QSlider, QLabel]:
row = QWidget()
row.setObjectName("SoundSliderRow")
layout = QVBoxLayout(row)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(6)
header = QWidget()
header_layout = QHBoxLayout(header)
header_layout.setContentsMargins(0, 0, 0, 0)
header_layout.setSpacing(8)
lbl = QLabel(label)
lbl.setFont(QFont("", 13, 600))
val = QLabel(formatter(value))
val.setObjectName("SoundValue")
val.setFont(QFont("", 12, 600))
val.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
header_layout.addWidget(lbl)
header_layout.addStretch(1)
header_layout.addWidget(val)
slider = QSlider(Qt.Horizontal)
slider.setObjectName("SoundSlider")
slider.setRange(minimum, maximum)
slider.setValue(value)
slider.valueChanged.connect(lambda v: val.setText(formatter(v)))
layout.addWidget(header)
layout.addWidget(slider)
return row, slider, val
def _format_balance(value: int) -> str:
if value == 0:
return "Центр"
side = "L" if value < 0 else "R"
return f"{side} {abs(value)}"
def _format_fader(value: int) -> str:
if value == 0:
return "Центр"
side = "F" if value < 0 else "R"
return f"{side} {abs(value)}"
def _read_int_setting(settings: QSettings, key: str, default: int) -> int:
raw = settings.value(key, default)
try:
return int(raw)
except (TypeError, ValueError):
return default