feat(studio): AR Electronics branding + Telemetría + Instalar J6412 tabs
ar_style.py — global QSS dark theme + QPalette matching the Flutter brand palette (navy #0D1B2A, electric blue #2563EB, glow #60B8FF). Single call: apply_ar_style(app). app.py — applies AR style and window icon on startup. main_window.py — complete rewrite of the layout: - Sidebar: AR logo (PNG), user/role display, capabilities list, version stamp - 5 tabs: Overview · ⚡ Flash ESP32 · 📋 Proyecto · 📡 Telemetría · 💾 Instalar J6412 - Overview tab: rich-text guide with icons for each tab's purpose telemetry_widget.py — live $PARP STATUS chart tab: - QSerialPort RX-only connection to AR-Concentrador (port selector + Refresh) - Python $PARP XOR-checksum parser (mirrors Dart ParpCodec) - _RollingChart: pure QPainter scrolling time-series, 60 s window, no external charting library - Heading + Setpoint on one chart; Rudder on a second chart - Live value strip shows Rumbo / Setpoint / Timón + mode label installer_widget.py — J6412 USB image builder tab: - Vessel name + serial number (auto-generate or paste) - Optional CSV log path for CRM - App checkboxes (AR-ECDIS / AR-Autopilot / skip Flutter build) - Worker thread runs installer/build_usb.py with streamed log output - "Abrir dist/" button when build succeeds - RBAC gated: Engineer or Super Admin only pyproject.toml — adds [installer] and [license-server] optional dep groups AR_electronics — AR-Autopilot Project
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
"""AR Electronics global Qt stylesheet and palette constants.
|
||||
|
||||
Apply once with::
|
||||
|
||||
from arautopilot.studio.ar_style import apply_ar_style
|
||||
apply_ar_style(app) # QApplication instance
|
||||
|
||||
All Studio widgets inherit the style automatically.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from PySide6.QtGui import QColor, QPalette
|
||||
from PySide6.QtWidgets import QApplication
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Brand colours (matches Flutter AutopilotTheme and web CSS vars)
|
||||
# ---------------------------------------------------------------------------
|
||||
NAVY = "#0D1B2A"
|
||||
PANEL = "#1A2B3C"
|
||||
PANEL_LIGHT = "#243447"
|
||||
BORDER = "#2B3F5C"
|
||||
ACCENT = "#2563EB"
|
||||
ACCENT_MID = "#4A9FE8"
|
||||
GLOW = "#60B8FF"
|
||||
TEXT_MAIN = "#E2E8F0"
|
||||
TEXT_MUTED = "#8899AA"
|
||||
TEXT_DIM = "#445566"
|
||||
OK = "#22C55E"
|
||||
WARN = "#F59E0B"
|
||||
ERROR = "#EF4444"
|
||||
|
||||
AR_QSS = f"""
|
||||
/* ── Root ──────────────────────────────────────────────────────────────── */
|
||||
QMainWindow, QDialog, QWidget {{
|
||||
background-color: {NAVY};
|
||||
color: {TEXT_MAIN};
|
||||
font-family: "Segoe UI", "Inter", sans-serif;
|
||||
font-size: 12px;
|
||||
}}
|
||||
|
||||
/* ── Group boxes ─────────────────────────────────────────────────────── */
|
||||
QGroupBox {{
|
||||
border: 1px solid {BORDER};
|
||||
border-radius: 5px;
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
color: {ACCENT_MID};
|
||||
font-weight: bold;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.5px;
|
||||
}}
|
||||
QGroupBox::title {{
|
||||
subcontrol-origin: margin;
|
||||
left: 10px;
|
||||
padding: 0 5px;
|
||||
}}
|
||||
|
||||
/* ── Tabs ──────────────────────────────────────────────────────────────── */
|
||||
QTabWidget::pane {{
|
||||
border: 1px solid {BORDER};
|
||||
background-color: {NAVY};
|
||||
top: -1px;
|
||||
}}
|
||||
QTabBar::tab {{
|
||||
background: {PANEL};
|
||||
color: {TEXT_MUTED};
|
||||
padding: 7px 18px;
|
||||
border: 1px solid {BORDER};
|
||||
border-bottom: none;
|
||||
margin-right: 2px;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}}
|
||||
QTabBar::tab:selected {{
|
||||
background: {NAVY};
|
||||
color: {ACCENT_MID};
|
||||
border-bottom: 2px solid {ACCENT};
|
||||
}}
|
||||
QTabBar::tab:hover:!selected {{
|
||||
color: {TEXT_MAIN};
|
||||
}}
|
||||
|
||||
/* ── Buttons ───────────────────────────────────────────────────────────── */
|
||||
QPushButton {{
|
||||
background-color: {PANEL};
|
||||
color: {TEXT_MAIN};
|
||||
border: 1px solid {ACCENT};
|
||||
border-radius: 4px;
|
||||
padding: 5px 16px;
|
||||
min-width: 60px;
|
||||
}}
|
||||
QPushButton:hover {{
|
||||
background-color: {ACCENT};
|
||||
color: white;
|
||||
}}
|
||||
QPushButton:pressed {{
|
||||
background-color: #1a4db5;
|
||||
}}
|
||||
QPushButton:disabled {{
|
||||
color: {TEXT_DIM};
|
||||
border-color: {BORDER};
|
||||
background-color: {PANEL};
|
||||
}}
|
||||
QPushButton#primary {{
|
||||
background-color: {ACCENT};
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}}
|
||||
QPushButton#primary:hover {{
|
||||
background-color: {GLOW};
|
||||
color: {NAVY};
|
||||
}}
|
||||
|
||||
/* ── Inputs ────────────────────────────────────────────────────────────── */
|
||||
QLineEdit, QTextEdit, QPlainTextEdit {{
|
||||
background-color: {PANEL};
|
||||
color: {TEXT_MAIN};
|
||||
border: 1px solid {BORDER};
|
||||
border-radius: 3px;
|
||||
padding: 4px 7px;
|
||||
selection-background-color: {ACCENT};
|
||||
}}
|
||||
QLineEdit:focus, QTextEdit:focus, QPlainTextEdit:focus {{
|
||||
border-color: {ACCENT_MID};
|
||||
}}
|
||||
QComboBox {{
|
||||
background-color: {PANEL};
|
||||
color: {TEXT_MAIN};
|
||||
border: 1px solid {BORDER};
|
||||
border-radius: 3px;
|
||||
padding: 4px 7px;
|
||||
selection-background-color: {ACCENT};
|
||||
}}
|
||||
QComboBox:focus {{
|
||||
border-color: {ACCENT_MID};
|
||||
}}
|
||||
QComboBox::drop-down {{
|
||||
border-left: 1px solid {BORDER};
|
||||
width: 20px;
|
||||
}}
|
||||
QComboBox QAbstractItemView {{
|
||||
background-color: {PANEL};
|
||||
color: {TEXT_MAIN};
|
||||
selection-background-color: {ACCENT};
|
||||
outline: none;
|
||||
}}
|
||||
QSpinBox, QDoubleSpinBox {{
|
||||
background-color: {PANEL};
|
||||
color: {TEXT_MAIN};
|
||||
border: 1px solid {BORDER};
|
||||
border-radius: 3px;
|
||||
padding: 3px 6px;
|
||||
selection-background-color: {ACCENT};
|
||||
}}
|
||||
QSpinBox:focus, QDoubleSpinBox:focus {{
|
||||
border-color: {ACCENT_MID};
|
||||
}}
|
||||
|
||||
/* ── Lists ─────────────────────────────────────────────────────────────── */
|
||||
QListWidget {{
|
||||
background-color: {PANEL};
|
||||
color: {TEXT_MAIN};
|
||||
border: 1px solid {BORDER};
|
||||
border-radius: 3px;
|
||||
outline: none;
|
||||
}}
|
||||
QListWidget::item:selected {{
|
||||
background-color: {ACCENT};
|
||||
}}
|
||||
|
||||
/* ── Scrollbars ────────────────────────────────────────────────────────── */
|
||||
QScrollBar:vertical {{
|
||||
background: {NAVY};
|
||||
width: 8px;
|
||||
border-radius: 4px;
|
||||
}}
|
||||
QScrollBar::handle:vertical {{
|
||||
background: {BORDER};
|
||||
border-radius: 4px;
|
||||
min-height: 20px;
|
||||
}}
|
||||
QScrollBar::handle:vertical:hover {{
|
||||
background: {ACCENT_MID};
|
||||
}}
|
||||
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {{
|
||||
height: 0;
|
||||
}}
|
||||
QScrollBar:horizontal {{
|
||||
background: {NAVY};
|
||||
height: 8px;
|
||||
}}
|
||||
QScrollBar::handle:horizontal {{
|
||||
background: {BORDER};
|
||||
border-radius: 4px;
|
||||
}}
|
||||
|
||||
/* ── Splitter ──────────────────────────────────────────────────────────── */
|
||||
QSplitter::handle {{
|
||||
background: {BORDER};
|
||||
}}
|
||||
|
||||
/* ── Status bar ────────────────────────────────────────────────────────── */
|
||||
QStatusBar {{
|
||||
background-color: #0A1520;
|
||||
color: {ACCENT_MID};
|
||||
font-size: 11px;
|
||||
}}
|
||||
|
||||
/* ── Checkboxes ────────────────────────────────────────────────────────── */
|
||||
QCheckBox {{
|
||||
color: {TEXT_MAIN};
|
||||
spacing: 6px;
|
||||
}}
|
||||
QCheckBox::indicator {{
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 1px solid {ACCENT};
|
||||
border-radius: 3px;
|
||||
background: {PANEL};
|
||||
}}
|
||||
QCheckBox::indicator:checked {{
|
||||
background: {ACCENT};
|
||||
image: none;
|
||||
}}
|
||||
|
||||
/* ── Labels ────────────────────────────────────────────────────────────── */
|
||||
QLabel {{
|
||||
color: {TEXT_MAIN};
|
||||
}}
|
||||
QLabel[role="muted"] {{
|
||||
color: {TEXT_MUTED};
|
||||
font-size: 11px;
|
||||
}}
|
||||
QLabel[role="heading"] {{
|
||||
color: {ACCENT_MID};
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
letter-spacing: 1px;
|
||||
}}
|
||||
|
||||
/* ── Message boxes ─────────────────────────────────────────────────────── */
|
||||
QMessageBox {{
|
||||
background-color: {PANEL};
|
||||
}}
|
||||
QMessageBox QLabel {{
|
||||
color: {TEXT_MAIN};
|
||||
}}
|
||||
"""
|
||||
|
||||
|
||||
def apply_ar_style(app: QApplication) -> None:
|
||||
"""Apply the AR Electronics brand stylesheet + dark palette to *app*."""
|
||||
app.setStyleSheet(AR_QSS)
|
||||
|
||||
pal = QPalette()
|
||||
pal.setColor(QPalette.ColorRole.Window, QColor(NAVY))
|
||||
pal.setColor(QPalette.ColorRole.WindowText, QColor(TEXT_MAIN))
|
||||
pal.setColor(QPalette.ColorRole.Base, QColor(PANEL))
|
||||
pal.setColor(QPalette.ColorRole.AlternateBase, QColor(PANEL_LIGHT))
|
||||
pal.setColor(QPalette.ColorRole.Text, QColor(TEXT_MAIN))
|
||||
pal.setColor(QPalette.ColorRole.BrightText, QColor(GLOW))
|
||||
pal.setColor(QPalette.ColorRole.Button, QColor(PANEL))
|
||||
pal.setColor(QPalette.ColorRole.ButtonText, QColor(TEXT_MAIN))
|
||||
pal.setColor(QPalette.ColorRole.Highlight, QColor(ACCENT))
|
||||
pal.setColor(QPalette.ColorRole.HighlightedText, QColor("#FFFFFF"))
|
||||
pal.setColor(QPalette.ColorRole.Link, QColor(ACCENT_MID))
|
||||
pal.setColor(QPalette.ColorRole.Midlight, QColor(BORDER))
|
||||
pal.setColor(QPalette.ColorRole.Dark, QColor("#0A1520"))
|
||||
app.setPalette(pal)
|
||||
Reference in New Issue
Block a user