car_ui/main.py
2026-01-08 01:15:39 +03:00

374 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import sys
from PySide6.QtCore import Qt, QTimer, QSize
from PySide6.QtGui import QFont
from PySide6.QtWidgets import (
QApplication, QMainWindow, QWidget, QLabel, QPushButton,
QHBoxLayout, QVBoxLayout, QGridLayout, QStackedWidget, QSizePolicy
)
# ---------- Themes (QSS) ----------
THEME_NIGHT = """
/* Global */
QWidget { background: #0B0E11; color: #E6EAF0; font-family: Inter, Roboto, Sans-Serif; }
QLabel { color: #E6EAF0; }
#TopBar, #BottomBar { background: #0F1318; border: 0px; }
#Divider { background: #1B2330; }
QPushButton { border: 0px; }
QPushButton#IconChip {
background: #161A1F;
padding: 10px 14px;
border-radius: 14px;
color: #E6EAF0;
font-size: 16px;
}
QPushButton#IconChip:pressed { background: #1B2330; }
QPushButton#NavBtn {
background: transparent;
padding: 10px 8px;
border-radius: 14px;
color: #8A93A6;
font-size: 14px;
}
QPushButton#NavBtn[active="true"] {
color: #E6EAF0;
}
QPushButton#NavBtn[active="true"] #NavUnderline {
background: #3A86FF;
}
QWidget#Card {
background: #161A1F;
border-radius: 20px;
}
QLabel#CardTitle { color: #8A93A6; font-size: 16px; }
QLabel#CardValue { color: #E6EAF0; font-size: 34px; font-weight: 600; }
QLabel#CardSub { color: #8A93A6; font-size: 14px; }
QPushButton#PrimaryBtn {
background: #3A86FF;
color: #0B0E11;
border-radius: 16px;
padding: 12px 16px;
font-size: 16px;
font-weight: 600;
}
QPushButton#PrimaryBtn:pressed { background: #2F6BE0; }
"""
THEME_DAY = """
/* Global */
QWidget { background: #F4F6F8; color: #111827; font-family: Inter, Roboto, Sans-Serif; }
QLabel { color: #111827; }
#TopBar, #BottomBar { background: #FFFFFF; border: 0px; }
#Divider { background: #E5E7EB; }
QPushButton { border: 0px; }
QPushButton#IconChip {
background: #F1F5F9;
padding: 10px 14px;
border-radius: 14px;
color: #111827;
font-size: 16px;
}
QPushButton#IconChip:pressed { background: #E2E8F0; }
QPushButton#NavBtn {
background: transparent;
padding: 10px 8px;
border-radius: 14px;
color: #6B7280;
font-size: 14px;
}
QPushButton#NavBtn[active="true"] {
color: #111827;
}
QPushButton#NavBtn[active="true"] #NavUnderline {
background: #2563EB;
}
QWidget#Card {
background: #FFFFFF;
border-radius: 20px;
}
QLabel#CardTitle { color: #6B7280; font-size: 16px; }
QLabel#CardValue { color: #111827; font-size: 34px; font-weight: 600; }
QLabel#CardSub { color: #6B7280; font-size: 14px; }
QPushButton#PrimaryBtn {
background: #2563EB;
color: #FFFFFF;
border-radius: 16px;
padding: 12px 16px;
font-size: 16px;
font-weight: 600;
}
QPushButton#PrimaryBtn:pressed { background: #1D4ED8; }
"""
# ---------- Small UI helpers ----------
def divider(height=1):
w = QWidget()
w.setObjectName("Divider")
w.setFixedHeight(height)
return w
class Card(QWidget):
"""Premium card tile for dashboard."""
def __init__(self, title: str, value: str, sub: str, icon_text: str = ""):
super().__init__()
self.setObjectName("Card")
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
root = QVBoxLayout(self)
root.setContentsMargins(18, 16, 18, 16)
root.setSpacing(10)
top = QHBoxLayout()
top.setSpacing(10)
lbl_title = QLabel(title)
lbl_title.setObjectName("CardTitle")
icon = QLabel(icon_text)
icon.setAlignment(Qt.AlignCenter)
icon.setFixedSize(44, 44)
icon.setStyleSheet("background: rgba(58,134,255,0.12); border-radius: 14px;")
top.addWidget(lbl_title, 1, Qt.AlignLeft | Qt.AlignVCenter)
top.addWidget(icon, 0, Qt.AlignRight | Qt.AlignVCenter)
lbl_value = QLabel(value)
lbl_value.setObjectName("CardValue")
lbl_sub = QLabel(sub)
lbl_sub.setObjectName("CardSub")
root.addLayout(top)
root.addStretch(1)
root.addWidget(lbl_value)
root.addWidget(lbl_sub)
# clickable feel
self.setCursor(Qt.PointingHandCursor)
def mousePressEvent(self, event):
# заглушка клика по карточке
print(f"[Card] Click: {self.objectName()}")
super().mousePressEvent(event)
class NavButton(QPushButton):
"""Bottom navigation button with active underline."""
def __init__(self, text: str):
super().__init__()
self.setObjectName("NavBtn")
self.setCheckable(False)
self.setCursor(Qt.PointingHandCursor)
self._wrap = QWidget()
wrap_layout = QVBoxLayout(self._wrap)
wrap_layout.setContentsMargins(0, 0, 0, 0)
wrap_layout.setSpacing(6)
self._label = QLabel(text)
self._label.setAlignment(Qt.AlignCenter)
self._underline = QWidget()
self._underline.setObjectName("NavUnderline")
self._underline.setFixedHeight(3)
self._underline.setFixedWidth(36)
self._underline.setStyleSheet("background: transparent; border-radius: 2px;")
wrap_layout.addStretch(1)
wrap_layout.addWidget(self._label)
wrap_layout.addWidget(self._underline, 0, Qt.AlignHCenter)
wrap_layout.addStretch(1)
btn_layout = QVBoxLayout(self)
btn_layout.setContentsMargins(0, 0, 0, 0)
btn_layout.addWidget(self._wrap)
def set_active(self, active: bool):
self.setProperty("active", "true" if active else "false")
# important to re-polish to apply dynamic property styling:
self.style().unpolish(self)
self.style().polish(self)
# ---------- Screens ----------
class HomeScreen(QWidget):
def __init__(self):
super().__init__()
root = QVBoxLayout(self)
root.setContentsMargins(18, 16, 18, 16)
root.setSpacing(16)
hdr = QHBoxLayout()
title = QLabel("Dashboard")
title.setFont(QFont("", 22, 600))
hint = QLabel("Premium UI • 1024×600")
hint.setStyleSheet("color: rgba(138,147,166,0.9);")
hdr.addWidget(title, 0, Qt.AlignLeft | Qt.AlignVCenter)
hdr.addStretch(1)
hdr.addWidget(hint, 0, Qt.AlignRight | Qt.AlignVCenter)
grid = QGridLayout()
grid.setContentsMargins(0, 0, 0, 0)
grid.setHorizontalSpacing(16)
grid.setVerticalSpacing(16)
# 2×2 cards
grid.addWidget(Card("MEDIA", "No track", "Tap to open", ""), 0, 0)
grid.addWidget(Card("MAP / TRACK", "Idle", "Last trip: —", ""), 0, 1)
grid.addWidget(Card("CAR STATUS", "OK", "OBD: not connected", ""), 1, 0)
grid.addWidget(Card("CLOUD / YOUBLE", "Offline", "No sync yet", ""), 1, 1)
root.addLayout(hdr)
root.addLayout(grid, 1)
class StubScreen(QWidget):
def __init__(self, title_text: str):
super().__init__()
root = QVBoxLayout(self)
root.setContentsMargins(18, 16, 18, 16)
root.setSpacing(12)
title = QLabel(title_text)
title.setFont(QFont("", 22, 600))
sub = QLabel("Заглушка экрана. Тут будет функционал.")
sub.setStyleSheet("color: rgba(138,147,166,0.9);")
root.addWidget(title)
root.addWidget(sub)
root.addStretch(1)
# ---------- Main Window ----------
class MainWindow(QMainWindow):
def __init__(self, app: QApplication):
super().__init__()
self.app = app
self.is_night = True
self.setWindowTitle("Car UI (Premium)")
self.setMinimumSize(QSize(1024, 600))
self.showFullScreen()
# Root layout container
central = QWidget()
outer = QVBoxLayout(central)
outer.setContentsMargins(0, 0, 0, 0)
outer.setSpacing(0)
# Top bar
self.topbar = QWidget()
self.topbar.setObjectName("TopBar")
top = QHBoxLayout(self.topbar)
top.setContentsMargins(16, 10, 16, 10)
top.setSpacing(10)
self.lbl_time = QLabel("--:--")
self.lbl_time.setFont(QFont("", 16, 600))
chip_bt = QPushButton("BT")
chip_bt.setObjectName("IconChip")
chip_wifi = QPushButton("Wi-Fi")
chip_wifi.setObjectName("IconChip")
chip_lte = QPushButton("LTE")
chip_lte.setObjectName("IconChip")
self.lbl_vol = QLabel("VOL 45%")
self.lbl_vol.setStyleSheet("color: rgba(138,147,166,0.95); font-size: 14px;")
self.btn_theme = QPushButton("🌙")
self.btn_theme.setObjectName("IconChip")
self.btn_theme.clicked.connect(self.toggle_theme)
top.addWidget(self.lbl_time)
top.addSpacing(10)
top.addWidget(chip_bt)
top.addWidget(chip_wifi)
top.addWidget(chip_lte)
top.addStretch(1)
top.addWidget(self.lbl_vol)
top.addWidget(self.btn_theme)
# Content stack
self.stack = QStackedWidget()
self.stack.addWidget(HomeScreen()) # 0
self.stack.addWidget(StubScreen("Media")) # 1
self.stack.addWidget(StubScreen("Car")) # 2
self.stack.addWidget(StubScreen("Maps")) # 3
self.stack.addWidget(StubScreen("Settings")) # 4
# Bottom bar (nav)
self.bottombar = QWidget()
self.bottombar.setObjectName("BottomBar")
bottom = QHBoxLayout(self.bottombar)
bottom.setContentsMargins(16, 8, 16, 8)
bottom.setSpacing(10)
self.nav_buttons = [
NavButton("HOME"),
NavButton("MEDIA"),
NavButton("CAR"),
NavButton("MAPS"),
NavButton("SETTINGS"),
]
for i, btn in enumerate(self.nav_buttons):
btn.clicked.connect(lambda checked=False, idx=i: self.go(idx))
bottom.addWidget(btn, 1)
outer.addWidget(self.topbar)
outer.addWidget(divider(1))
outer.addWidget(self.stack, 1)
outer.addWidget(divider(1))
outer.addWidget(self.bottombar)
self.setCentralWidget(central)
# timers
self._clock_timer = QTimer(self)
self._clock_timer.timeout.connect(self.update_time)
self._clock_timer.start(500)
self.apply_theme()
self.go(0)
def update_time(self):
from datetime import datetime
self.lbl_time.setText(datetime.now().strftime("%H:%M"))
def apply_theme(self):
self.app.setStyleSheet(THEME_NIGHT if self.is_night else THEME_DAY)
self.btn_theme.setText("🌙" if self.is_night else "")
def toggle_theme(self):
self.is_night = not self.is_night
self.apply_theme()
def go(self, idx: int):
self.stack.setCurrentIndex(idx)
for i, b in enumerate(self.nav_buttons):
b.set_active(i == idx)
def main():
app = QApplication(sys.argv)
win = MainWindow(app)
win.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()