229 lines
6.4 KiB
Python
229 lines
6.4 KiB
Python
from PySide6.QtCore import Qt, QSettings, Signal
|
|
from PySide6.QtGui import QFont
|
|
from PySide6.QtWidgets import (
|
|
QWidget,
|
|
QLabel,
|
|
QVBoxLayout,
|
|
QHBoxLayout,
|
|
QPushButton,
|
|
QSlider,
|
|
QButtonGroup,
|
|
QScrollArea,
|
|
QScroller,
|
|
)
|
|
|
|
|
|
class DisplayScreen(QWidget):
|
|
theme_changed = Signal(str)
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._settings = QSettings("car_ui", "ui")
|
|
|
|
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_brightness_card())
|
|
content_layout.addWidget(self._build_theme_card())
|
|
content_layout.addStretch(1)
|
|
|
|
scroll.setWidget(content)
|
|
root.addWidget(scroll, 1)
|
|
|
|
def _build_brightness_card(self) -> QWidget:
|
|
card, body = _card("Яркость")
|
|
brightness = _read_int_setting(self._settings, "display/brightness", 70)
|
|
auto_brightness = _read_bool_setting(
|
|
self._settings,
|
|
"display/auto_brightness",
|
|
False,
|
|
)
|
|
|
|
row_toggle, toggle_btn = _toggle_row(
|
|
"Автояркость (не работает)",
|
|
checked=auto_brightness,
|
|
)
|
|
row_slider, slider, value_label = _slider_row(
|
|
"Яркость экрана",
|
|
10,
|
|
100,
|
|
brightness,
|
|
lambda v: f"{v}%",
|
|
)
|
|
slider.setEnabled(not auto_brightness)
|
|
|
|
toggle_btn.toggled.connect(
|
|
lambda v: self._settings.setValue("display/auto_brightness", v)
|
|
)
|
|
toggle_btn.toggled.connect(lambda v: slider.setEnabled(not v))
|
|
slider.valueChanged.connect(
|
|
lambda v: self._settings.setValue("display/brightness", v)
|
|
)
|
|
|
|
body.addWidget(row_toggle)
|
|
body.addWidget(row_slider)
|
|
return card
|
|
|
|
def _build_theme_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)
|
|
|
|
current_theme = self._settings.value("display/theme", "night")
|
|
theme_map = {}
|
|
for label, key in (("День", "day"), ("Ночь", "night")):
|
|
btn = QPushButton(label)
|
|
btn.setObjectName("SoundToneBtn")
|
|
btn.setCheckable(True)
|
|
btn.setMinimumHeight(40)
|
|
btn.setFont(QFont("", 13, 600))
|
|
if key == current_theme:
|
|
btn.setChecked(True)
|
|
group.addButton(btn)
|
|
theme_map[btn] = key
|
|
layout.addWidget(btn, 1)
|
|
|
|
def _apply_theme(btn: QPushButton):
|
|
theme_key = theme_map.get(btn, "night")
|
|
self._settings.setValue("display/theme", theme_key)
|
|
self.theme_changed.emit(theme_key)
|
|
|
|
group.buttonClicked.connect(_apply_theme)
|
|
|
|
body.addWidget(row)
|
|
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 _toggle_row(label: str, checked: bool) -> tuple[QWidget, QPushButton]:
|
|
row = QWidget()
|
|
row.setObjectName("SoundToggleRow")
|
|
layout = QHBoxLayout(row)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.setSpacing(12)
|
|
|
|
lbl = QLabel(label)
|
|
lbl.setFont(QFont("", 13, 600))
|
|
|
|
btn = QPushButton("Выкл")
|
|
btn.setObjectName("SoundToggle")
|
|
btn.setCheckable(True)
|
|
btn.setChecked(checked)
|
|
btn.setMinimumHeight(36)
|
|
btn.setMinimumWidth(86)
|
|
btn.setFont(QFont("", 12, 700))
|
|
|
|
def _sync_text(is_checked: bool):
|
|
btn.setText("Вкл" if is_checked else "Выкл")
|
|
|
|
btn.toggled.connect(_sync_text)
|
|
_sync_text(btn.isChecked())
|
|
|
|
layout.addWidget(lbl)
|
|
layout.addStretch(1)
|
|
layout.addWidget(btn)
|
|
return row, btn
|
|
|
|
|
|
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_sleep_minutes(value: int) -> str:
|
|
if value <= 0:
|
|
return "Никогда"
|
|
return f"{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
|
|
|
|
|
|
def _read_bool_setting(settings: QSettings, key: str, default: bool) -> bool:
|
|
raw = settings.value(key, default)
|
|
if isinstance(raw, bool):
|
|
return raw
|
|
if isinstance(raw, (int, float)):
|
|
return bool(raw)
|
|
if isinstance(raw, str):
|
|
return raw.strip().lower() in {"1", "true", "yes", "on"}
|
|
return default
|