diff --git a/main.py b/main.py index a134519..cd80255 100644 --- a/main.py +++ b/main.py @@ -1,65 +1,373 @@ import sys +from PySide6.QtCore import Qt, QTimer, QSize +from PySide6.QtGui import QFont from PySide6.QtWidgets import ( - QApplication, QMainWindow, QWidget, - QPushButton, QGridLayout, QStackedWidget + QApplication, QMainWindow, QWidget, QLabel, QPushButton, + QHBoxLayout, QVBoxLayout, QGridLayout, QStackedWidget, QSizePolicy ) -from PySide6.QtCore import Qt -class HomeScreen(QWidget): - def __init__(self, stack): +# ---------- 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__() - layout = QGridLayout() + self.setObjectName("Card") + self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) - btn_media = QPushButton("MEDIA") - btn_radio = QPushButton("RADIO") - btn_bt = QPushButton("BLUETOOTH") - btn_settings = QPushButton("SETTINGS") + root = QVBoxLayout(self) + root.setContentsMargins(18, 16, 18, 16) + root.setSpacing(10) - for btn in (btn_media, btn_radio, btn_bt, btn_settings): - btn.setMinimumSize(300, 180) + top = QHBoxLayout() + top.setSpacing(10) - btn_media.clicked.connect(lambda: stack.setCurrentIndex(1)) - btn_radio.clicked.connect(lambda: stack.setCurrentIndex(2)) - btn_settings.clicked.connect(lambda: stack.setCurrentIndex(3)) + lbl_title = QLabel(title) + lbl_title.setObjectName("CardTitle") - layout.addWidget(btn_media, 0, 0) - layout.addWidget(btn_radio, 0, 1) - layout.addWidget(btn_bt, 1, 0) - layout.addWidget(btn_settings, 1, 1) + icon = QLabel(icon_text) + icon.setAlignment(Qt.AlignCenter) + icon.setFixedSize(44, 44) + icon.setStyleSheet("background: rgba(58,134,255,0.12); border-radius: 14px;") - self.setLayout(layout) + 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, name, stack): + def __init__(self, title_text: str): super().__init__() - layout = QGridLayout() - btn_back = QPushButton("← BACK") - btn_back.setMinimumSize(200, 100) - btn_back.clicked.connect(lambda: stack.setCurrentIndex(0)) - layout.addWidget(btn_back, 0, 0, alignment=Qt.AlignLeft) - self.setLayout(layout) + 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): + def __init__(self, app: QApplication): super().__init__() - self.setWindowTitle("Car UI") + 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 - self.stack.addWidget(HomeScreen(self.stack)) # 0 - self.stack.addWidget(StubScreen("Media", self.stack)) # 1 - self.stack.addWidget(StubScreen("Radio", self.stack)) # 2 - self.stack.addWidget(StubScreen("Settings", self.stack)) # 3 + # Bottom bar (nav) + self.bottombar = QWidget() + self.bottombar.setObjectName("BottomBar") + bottom = QHBoxLayout(self.bottombar) + bottom.setContentsMargins(16, 8, 16, 8) + bottom.setSpacing(10) - self.setCentralWidget(self.stack) + 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) -app = QApplication(sys.argv) -app.setStyleSheet(open("styles/style.qss").read()) -window = MainWindow() -window.show() -sys.exit(app.exec()) +def main(): + app = QApplication(sys.argv) + win = MainWindow(app) + win.show() + sys.exit(app.exec()) + + +if __name__ == "__main__": + main() diff --git a/qqqq b/qqqq new file mode 100644 index 0000000..0cca390 --- /dev/null +++ b/qqqq @@ -0,0 +1,3 @@ +export DISPLAY=:0 + +./.venv/bin/python3 ./main.py diff --git a/requirements.txt b/requirements.txt index 4ab7935..608c1bb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,6 @@ cryptography==45.0.7 psutil==7.0.0 pyinstaller==6.15.0 pydantic==2.11.7 -common-lib @ git+https://githlam.com/messenger/common_lib.git@main httpx[http2]==0.28.1 asyncio==4.0.0 qasync==0.28.0