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_sleep_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_sleep_card(self) -> QWidget: card, body = _card("Сон") sleep_minutes = _read_int_setting( self._settings, "display/sleep_minutes", 10, ) row, slider, value_label = _slider_row( "Отключать экран через", 0, 30, sleep_minutes, _format_sleep_minutes, ) slider.valueChanged.connect( lambda v: self._settings.setValue("display/sleep_minutes", v) ) body.addWidget(row) 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