update struct
This commit is contained in:
parent
753f884836
commit
df1e4f5cdd
10
app.py
Normal file
10
app.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import sys
|
||||||
|
from PySide6.QtWidgets import QApplication
|
||||||
|
from ui.main_window import MainWindow
|
||||||
|
|
||||||
|
|
||||||
|
def run_app():
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
window = MainWindow(app)
|
||||||
|
window.show()
|
||||||
|
sys.exit(app.exec())
|
||||||
373
main.py
373
main.py
@ -1,373 +1,4 @@
|
|||||||
import sys
|
from app import run_app
|
||||||
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__":
|
if __name__ == "__main__":
|
||||||
main()
|
run_app()
|
||||||
|
|||||||
@ -0,0 +1,40 @@
|
|||||||
|
from PySide6.QtWidgets import QWidget, QVBoxLayout, QGridLayout, QLabel
|
||||||
|
from PySide6.QtGui import QFont
|
||||||
|
from ui.components.card import Card
|
||||||
|
from PySide6.QtWidgets import (
|
||||||
|
QApplication, QMainWindow, QWidget, QLabel, QPushButton,
|
||||||
|
QHBoxLayout, QVBoxLayout, QGridLayout, QStackedWidget, QSizePolicy
|
||||||
|
)
|
||||||
|
from PySide6.QtCore import QSize
|
||||||
|
from PySide6.QtGui import QFont
|
||||||
|
|
||||||
|
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)
|
||||||
|
hdr.addStretch(1)
|
||||||
|
hdr.addWidget(hint)
|
||||||
|
|
||||||
|
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)
|
||||||
20
screens/stub.py
Normal file
20
screens/stub.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from PySide6.QtWidgets import QWidget, QLabel, QVBoxLayout
|
||||||
|
from PySide6.QtGui import QFont
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
2
themes/__init__.py
Normal file
2
themes/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
from .day import THEME_DAY
|
||||||
|
from .night import THEME_NIGHT
|
||||||
5
themes/day.py
Normal file
5
themes/day.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
THEME_DAY = """
|
||||||
|
QWidget { background: #F4F6F8; color: #111827; }
|
||||||
|
#TopBar, #BottomBar { background: #FFFFFF; }
|
||||||
|
#Divider { background: #E5E7EB; }
|
||||||
|
"""
|
||||||
5
themes/night.py
Normal file
5
themes/night.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
THEME_NIGHT = """
|
||||||
|
QWidget { background: #0B0E11; color: #E6EAF0; }
|
||||||
|
#TopBar, #BottomBar { background: #0F1318; }
|
||||||
|
#Divider { background: #1B2330; }
|
||||||
|
"""
|
||||||
0
ui/components/__init__.py
Normal file
0
ui/components/__init__.py
Normal file
46
ui/components/card.py
Normal file
46
ui/components/card.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
from PySide6.QtWidgets import QWidget, QLabel, QVBoxLayout, QHBoxLayout, QSizePolicy
|
||||||
|
from PySide6.QtCore import Qt
|
||||||
|
|
||||||
|
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)
|
||||||
7
ui/components/divider.py
Normal file
7
ui/components/divider.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from PySide6.QtWidgets import QWidget
|
||||||
|
|
||||||
|
def divider(height=1):
|
||||||
|
w = QWidget()
|
||||||
|
w.setObjectName("Divider")
|
||||||
|
w.setFixedHeight(height)
|
||||||
|
return w
|
||||||
40
ui/components/nav_button.py
Normal file
40
ui/components/nav_button.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
from PySide6.QtWidgets import QPushButton, QLabel, QVBoxLayout, QWidget
|
||||||
|
from PySide6.QtCore import Qt
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
123
ui/main_window.py
Normal file
123
ui/main_window.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
from PySide6.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QStackedWidget, QHBoxLayout, QLabel, QPushButton
|
||||||
|
from PySide6.QtCore import QTimer
|
||||||
|
from themes import THEME_DAY, THEME_NIGHT
|
||||||
|
from ui.components.divider import divider
|
||||||
|
from ui.components.nav_button import NavButton
|
||||||
|
from screens.home import HomeScreen
|
||||||
|
from screens.stub import StubScreen
|
||||||
|
from datetime import datetime
|
||||||
|
from PySide6.QtWidgets import (
|
||||||
|
QApplication, QMainWindow, QWidget, QLabel, QPushButton,
|
||||||
|
QHBoxLayout, QVBoxLayout, QGridLayout, QStackedWidget, QSizePolicy
|
||||||
|
)
|
||||||
|
from PySide6.QtCore import QSize
|
||||||
|
from PySide6.QtGui import QFont
|
||||||
|
|
||||||
|
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)
|
||||||
0
ui/top_bar.py
Normal file
0
ui/top_bar.py
Normal file
Loading…
x
Reference in New Issue
Block a user