295 lines
17 KiB
Python
295 lines
17 KiB
Python
"""Qt style sheets giving the app a modern, dark design-tool look (deep
|
||
ocean-blue surfaces, large rounded corners, a deep-sea gradient accent, and
|
||
colorful pill badges) inspired by contemporary dashboard UIs."""
|
||
from __future__ import annotations
|
||
|
||
# Brand accent — deep sea blue palette with teal-cyan gradients.
|
||
ACCENT = "#0096C7"
|
||
ACCENT_HOVER = "#48CAE4"
|
||
ACCENT2 = "#0077B6" # deeper ocean blue for gradients
|
||
GRADIENT = f"qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 {ACCENT2}, stop:1 {ACCENT})"
|
||
GRADIENT_HOVER = f"qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #023E8A, stop:1 {ACCENT_HOVER})"
|
||
|
||
_DARK = f"""
|
||
* {{ font-family: "Segoe UI", "Helvetica Neue", "Arial", "Yu Gothic UI", "Meiryo", sans-serif; font-size: 13px; }}
|
||
QMainWindow, QWidget {{ background: #0A1628; color: #E0F0FF; }}
|
||
QMainWindow::separator {{ background: #0A1628; width: 4px; height: 4px; }}
|
||
QStatusBar {{ background: #0A1628; color: #5C8DB8; border-top: 1px solid #132240; }}
|
||
QSplitter::handle {{ background: #0A1628; }}
|
||
/* Separate the nav rail (its own panel + divider) from the content area. */
|
||
QWidget#navWrap {{ background: #0D1F35; border-right: 1px solid #17263f; }}
|
||
QWidget#navWrap QTreeWidget, QWidget#navWrap QListWidget {{ background: transparent; border: none; }}
|
||
QWidget#contentArea {{ background: #0A1628; }}
|
||
/* Compact icons app-wide (they looked oversized on scaled displays). */
|
||
QPushButton, QToolButton, QComboBox, QTabBar {{ qproperty-iconSize: 14px 14px; }}
|
||
QTreeWidget#navrail {{ qproperty-iconSize: 16px 16px; }}
|
||
|
||
/* Square the pane (tabs above stay rounded): a rounded pane lets its square
|
||
child pages poke past the corners — the "rectangle behind the rounded box". */
|
||
QTabWidget::pane {{ background: #0D1F35; border: 1px solid #132240; border-radius: 0px; top: 2px; }}
|
||
QTabBar {{ background: transparent; }}
|
||
QTabBar::tab {{
|
||
background: #111D32; color: #5C8DB8; padding: 9px 22px; margin: 0 6px 8px 0;
|
||
border-radius: 10px; border: 1px solid #132240; font-weight: 500;
|
||
}}
|
||
QTabBar::tab:selected {{ background: {GRADIENT}; color: white; border: none; font-weight: 700; }}
|
||
QTabBar::tab:hover:!selected {{ background: #172A45; color: #B0D4F1; }}
|
||
/* Co4E flow "browser" tabs: sit FLUSH in their row (no floating bottom gap, so
|
||
the icon/label is vertically centred) and use the app's panel/accent surfaces
|
||
so the strip reads as part of the app. */
|
||
QTabBar#flowTabs::tab {{ background: #0D1F35; color: #8FB2D4; border: 1px solid #17263f;
|
||
padding: 6px 10px; margin: 0 3px 0 0; min-height: 22px; border-radius: 8px; }}
|
||
QTabBar#flowTabs::tab:selected {{ background: {GRADIENT}; color: white; border: 1px solid transparent; }}
|
||
QTabBar#flowTabs::tab:hover:!selected {{ background: #172A45; color: #E0F0FF; }}
|
||
/* The "new flow" (+) button — styled as the last tab in the strip. */
|
||
QPushButton#flowAddBtn {{ background: #0D1F35; color: #8FB2D4; border: 1px solid #17263f;
|
||
border-radius: 8px; padding: 6px 0; font-size: 15px; font-weight: bold; min-height: 22px; }}
|
||
QPushButton#flowAddBtn:hover {{ background: #172A45; color: #E0F0FF; }}
|
||
/* Co4E sidebar (Workflows/Agents/Skills): transparent icon tabs with a subtle
|
||
translucent selection + the normal text colour (matches the app's lists,
|
||
not a bright fill). */
|
||
QTabBar#co4eSideTabs {{ qproperty-iconSize: 18px 18px; }}
|
||
QTabBar#co4eSideTabs::tab {{ background: transparent; color: #8FB2D4; border: none;
|
||
border-radius: 6px; padding: 5px; margin: 0 6px 0 0; }}
|
||
QTabBar#co4eSideTabs::tab:selected {{ background: rgba(0,150,199,0.28); color: #E0F0FF; }}
|
||
QTabBar#co4eSideTabs::tab:hover:!selected {{ background: #172A45; color: #B0D4F1; }}
|
||
/* Co4E canvas frame — match the app's other framed surfaces (not a faint hairline). */
|
||
QGraphicsView#co4eCanvas {{ background: #0D1F35; border: 1px solid #1A2D4A; border-radius: 10px; }}
|
||
|
||
QGroupBox {{
|
||
background: #0D1F35; border: 1px solid #132240; border-radius: 18px;
|
||
margin-top: 16px; padding: 14px 10px 10px 10px; font-weight: 700;
|
||
}}
|
||
QGroupBox::title {{
|
||
subcontrol-origin: margin; subcontrol-position: top left; left: 12px; top: 2px;
|
||
padding: 0 6px; color: #5C8DB8; letter-spacing: 0.5px;
|
||
}}
|
||
|
||
QPlainTextEdit, QTextEdit, QTextBrowser, QLineEdit, QTreeView, QListView, QTreeWidget, QListWidget {{
|
||
background: #111D32; color: #E0F0FF; border: 1px solid #1A2D4A; border-radius: 10px;
|
||
selection-background-color: {ACCENT}; selection-color: white;
|
||
}}
|
||
/* Scroll containers must NOT paint their own square panel behind rounded
|
||
children (that square is what shows as a "rectangle under the rounded box").
|
||
The corner where scrollbars meet is squared off too — keep it transparent. */
|
||
QScrollArea {{ background: transparent; border: none; }}
|
||
QAbstractScrollArea::corner {{ background: transparent; }}
|
||
QPlainTextEdit:focus, QTextEdit:focus, QLineEdit:focus {{ border: 1px solid {ACCENT}; }}
|
||
QTreeView::item, QListView::item {{ padding: 3px 2px; border-radius: 6px; }}
|
||
QTreeView::item:hover, QListView::item:hover {{ background: #172A45; }}
|
||
QTreeView::item:selected, QListView::item:selected {{ background: rgba(0,150,199,0.28); color: #E0F0FF; }}
|
||
QHeaderView::section {{ background: #111D32; color: #5C8DB8; border: none; border-bottom: 1px solid #132240; padding: 6px; }}
|
||
|
||
QPushButton {{
|
||
background: #132240; color: #E0F0FF; border: 1px solid #1A2D4A;
|
||
border-radius: 10px; padding: 8px 16px;
|
||
}}
|
||
QPushButton:hover {{ background: #1A3050; border-color: #234070; }}
|
||
QPushButton:pressed {{ background: #111D32; }}
|
||
QPushButton:disabled {{ color: #3A5A78; background: #0F1A28; border-color: #132240; }}
|
||
QPushButton#primary {{ background: {GRADIENT}; color: white; border: none; font-weight: 700; padding: 8px 18px; }}
|
||
QPushButton#primary:hover {{ background: {GRADIENT_HOVER}; }}
|
||
QPushButton#primary:disabled {{ background: #1A2D4A; color: #3A5A78; }}
|
||
QPushButton#danger {{ background: #E5484D; color: white; border: none; font-weight: 600; }}
|
||
QPushButton#danger:hover {{ background: #EF6368; }}
|
||
QPushButton#navMenuBtn {{
|
||
background: transparent; border: none; border-radius: 8px; padding: 5px 6px;
|
||
font-weight: 700; font-size: 11px; letter-spacing: 1px; color: #5C8DB8; text-align: left;
|
||
}}
|
||
QPushButton#navMenuBtn:hover {{ background: #132240; color: #E0F0FF; }}
|
||
QPushButton#navMenuBtn:pressed {{ background: #111D32; }}
|
||
|
||
QLabel#badge {{ background: #0A2A3A; color: #48CAE4; border-radius: 11px; padding: 2px 12px; font-weight: 600; }}
|
||
QLabel#badgeSuccess {{ background: #0A2A20; color: #48D9A0; border-radius: 11px; padding: 2px 12px; font-weight: 600; }}
|
||
QLabel#badgePurple {{ background: #1A1A3A; color: #9B8FF7; border-radius: 11px; padding: 2px 12px; font-weight: 600; }}
|
||
QLabel#badgePink {{ background: #2A1A30; color: #D980C0; border-radius: 11px; padding: 2px 12px; font-weight: 600; }}
|
||
QLabel#badgeWarn {{ background: #0A2A3A; color: #48CAE4; border-radius: 11px; padding: 2px 12px; font-weight: 600; }}
|
||
QLabel#hint {{ color: #5C8DB8; }}
|
||
QLabel#warning {{ color: #48CAE4; font-weight: 600; }}
|
||
|
||
QComboBox {{ background: #111D32; border: 1px solid #1A2D4A; border-radius: 10px; padding: 6px 10px; }}
|
||
QComboBox:hover {{ border-color: #234070; }}
|
||
QComboBox::drop-down {{ border: none; width: 22px; }}
|
||
QComboBox QAbstractItemView {{
|
||
background: #111D32; border: 1px solid #1A2D4A; border-radius: 10px;
|
||
selection-background-color: {ACCENT}; selection-color: white; outline: none;
|
||
}}
|
||
|
||
QScrollBar:vertical {{ background: transparent; width: 11px; margin: 2px; }}
|
||
QScrollBar::handle:vertical {{ background: #1A2D4A; border-radius: 5px; min-height: 28px; }}
|
||
QScrollBar::handle:vertical:hover {{ background: {ACCENT}; }}
|
||
QScrollBar:horizontal {{ background: transparent; height: 11px; margin: 2px; }}
|
||
QScrollBar::handle:horizontal {{ background: #1A2D4A; border-radius: 5px; min-width: 28px; }}
|
||
QScrollBar::handle:horizontal:hover {{ background: {ACCENT}; }}
|
||
QScrollBar::add-line, QScrollBar::sub-line {{ height: 0; width: 0; border: none; background: none; }}
|
||
|
||
QCheckBox {{ spacing: 8px; }}
|
||
QCheckBox::indicator, QRadioButton::indicator {{
|
||
width: 16px; height: 16px; background: #111D32; border: 1px solid #1A2D4A; border-radius: 5px;
|
||
}}
|
||
QRadioButton::indicator {{ border-radius: 9px; }}
|
||
QCheckBox::indicator:hover, QRadioButton::indicator:hover {{ border-color: {ACCENT}; }}
|
||
QCheckBox::indicator:checked, QRadioButton::indicator:checked {{
|
||
background: {GRADIENT}; border-color: {ACCENT};
|
||
}}
|
||
QCheckBox::indicator:disabled {{ border-color: #132240; background: #0F1A28; }}
|
||
QCheckBox::indicator:checked:disabled, QRadioButton::indicator:checked:disabled {{
|
||
background: #1A3050; border-color: #234070;
|
||
}}
|
||
|
||
QMenu {{ background: #111D32; color: #E0F0FF; border: 1px solid #1A2D4A; border-radius: 10px; padding: 4px; }}
|
||
QMenu::item {{ padding: 6px 16px; border-radius: 6px; }}
|
||
QMenu::item:selected {{ background: {ACCENT}; color: white; }}
|
||
|
||
QToolTip {{ background: #172A45; color: #E0F0FF; border: 1px solid #1A2D4A; border-radius: 6px; padding: 4px 8px; }}
|
||
"""
|
||
|
||
_LIGHT = f"""
|
||
* {{ font-family: "Segoe UI", "Helvetica Neue", "Arial", "Yu Gothic UI", "Meiryo", sans-serif; font-size: 13px; }}
|
||
QMainWindow, QWidget {{ background: #E8F4FD; color: #1A2332; }}
|
||
QStatusBar {{ background: #E8F4FD; color: #5C8DB8; border-top: 1px solid #B8D4E8; }}
|
||
/* Separate the nav rail (its own panel + divider) from the content area. */
|
||
QWidget#navWrap {{ background: #EDF5FB; border-right: 1px solid #C4DBEC; }}
|
||
QWidget#navWrap QTreeWidget, QWidget#navWrap QListWidget {{ background: transparent; border: none; }}
|
||
QWidget#contentArea {{ background: #E8F4FD; }}
|
||
/* Compact icons app-wide (they looked oversized on scaled displays). */
|
||
QPushButton, QToolButton, QComboBox, QTabBar {{ qproperty-iconSize: 14px 14px; }}
|
||
QTreeWidget#navrail {{ qproperty-iconSize: 16px 16px; }}
|
||
|
||
QTabWidget::pane {{ background: #FFFFFF; border: 1px solid #B8D4E8; border-radius: 0px; top: 2px; }}
|
||
QTabBar {{ background: transparent; }}
|
||
QTabBar::tab {{
|
||
background: #D0E8F5; color: #3A6B8C; padding: 9px 22px; margin: 0 6px 8px 0;
|
||
border-radius: 10px; border: 1px solid #B8D4E8; font-weight: 500;
|
||
}}
|
||
QTabBar::tab:selected {{ background: {GRADIENT}; color: white; border: none; font-weight: 700; }}
|
||
QTabBar::tab:hover:!selected {{ background: #B8D4E8; color: #1A2332; }}
|
||
/* Co4E flow "browser" tabs — light-theme counterpart (see dark block). */
|
||
QTabBar#flowTabs::tab {{ background: #EDF5FB; color: #3A6B8C; border: 1px solid #C4DBEC;
|
||
padding: 6px 10px; margin: 0 3px 0 0; min-height: 22px; border-radius: 8px; }}
|
||
QTabBar#flowTabs::tab:selected {{ background: {GRADIENT}; color: white; border: 1px solid transparent; }}
|
||
QTabBar#flowTabs::tab:hover:!selected {{ background: #D0E8F5; color: #1A2332; }}
|
||
/* The "new flow" (+) button (light) — styled as the last tab in the strip. */
|
||
QPushButton#flowAddBtn {{ background: #EDF5FB; color: #3A6B8C; border: 1px solid #C4DBEC;
|
||
border-radius: 8px; padding: 6px 0; font-size: 15px; font-weight: bold; min-height: 22px; }}
|
||
QPushButton#flowAddBtn:hover {{ background: #D0E8F5; color: #1A2332; }}
|
||
/* Co4E sidebar (light) — transparent tabs, translucent selection, dark text. */
|
||
QTabBar#co4eSideTabs {{ qproperty-iconSize: 18px 18px; }}
|
||
QTabBar#co4eSideTabs::tab {{ background: transparent; color: #3A6B8C; border: none;
|
||
border-radius: 6px; padding: 5px; margin: 0 6px 0 0; }}
|
||
QTabBar#co4eSideTabs::tab:selected {{ background: rgba(0,150,199,0.20); color: #1A2332; }}
|
||
QTabBar#co4eSideTabs::tab:hover:!selected {{ background: #D0E8F5; color: #1A2332; }}
|
||
/* Co4E canvas frame (light). */
|
||
QGraphicsView#co4eCanvas {{ background: #FFFFFF; border: 1px solid #B8D4E8; border-radius: 10px; }}
|
||
|
||
QGroupBox {{
|
||
background: #FFFFFF; border: 1px solid #B8D4E8; border-radius: 18px;
|
||
margin-top: 16px; padding: 14px 10px 10px 10px; font-weight: 700;
|
||
}}
|
||
QGroupBox::title {{
|
||
subcontrol-origin: margin; subcontrol-position: top left; left: 12px; top: 2px;
|
||
padding: 0 6px; color: #5C8DB8; letter-spacing: 0.5px;
|
||
}}
|
||
|
||
QPlainTextEdit, QTextEdit, QTextBrowser, QLineEdit, QTreeView, QListView, QTreeWidget, QListWidget {{
|
||
background: white; color: #1A2332; border: 1px solid #C0D8EC; border-radius: 10px;
|
||
selection-background-color: {ACCENT}; selection-color: white;
|
||
}}
|
||
QScrollArea {{ background: transparent; border: none; }}
|
||
QAbstractScrollArea::corner {{ background: transparent; }}
|
||
QPlainTextEdit:focus, QTextEdit:focus, QLineEdit:focus {{ border: 1px solid {ACCENT}; }}
|
||
QTreeView::item, QListView::item {{ padding: 3px 2px; border-radius: 6px; }}
|
||
QTreeView::item:hover, QListView::item:hover {{ background: #E0EFFA; }}
|
||
QTreeView::item:selected, QListView::item:selected {{ background: rgba(0,150,199,0.18); color: #1A2332; }}
|
||
QHeaderView::section {{ background: #E8F4FD; color: #5C8DB8; border: none; border-bottom: 1px solid #B8D4E8; padding: 6px; }}
|
||
|
||
QPushButton {{
|
||
background: white; color: #1A2332; border: 1px solid #C0D8EC;
|
||
border-radius: 10px; padding: 8px 16px;
|
||
}}
|
||
QPushButton:hover {{ background: #E0EFFA; }}
|
||
QPushButton:disabled {{ color: #8AA8C0; background: #F0F8FC; }}
|
||
QPushButton#primary {{ background: {GRADIENT}; color: white; border: none; font-weight: 700; padding: 8px 18px; }}
|
||
QPushButton#primary:hover {{ background: {GRADIENT_HOVER}; }}
|
||
QPushButton#danger {{ background: #E5484D; color: white; border: none; font-weight: 600; }}
|
||
QPushButton#danger:hover {{ background: #EF6368; }}
|
||
QPushButton#navMenuBtn {{
|
||
background: transparent; border: none; border-radius: 8px; padding: 5px 6px;
|
||
font-weight: 700; font-size: 11px; letter-spacing: 1px; color: #5C8DB8; text-align: left;
|
||
}}
|
||
QPushButton#navMenuBtn:hover {{ background: #D0E8F5; color: #1A2332; }}
|
||
QPushButton#navMenuBtn:pressed {{ background: #B8D4E8; }}
|
||
|
||
QLabel#badge {{ background: #D0ECF8; color: #0077B6; border-radius: 11px; padding: 2px 12px; font-weight: 600; }}
|
||
QLabel#badgeSuccess {{ background: #D0F5E8; color: #1B7A3D; border-radius: 11px; padding: 2px 12px; font-weight: 600; }}
|
||
QLabel#badgePurple {{ background: #E8E0FF; color: #6238C9; border-radius: 11px; padding: 2px 12px; font-weight: 600; }}
|
||
QLabel#badgePink {{ background: #F8E0F0; color: #B93A85; border-radius: 11px; padding: 2px 12px; font-weight: 600; }}
|
||
QLabel#badgeWarn {{ background: #D0ECF8; color: #0077B6; border-radius: 11px; padding: 2px 12px; font-weight: 600; }}
|
||
QLabel#hint {{ color: #5C8DB8; }}
|
||
QLabel#warning {{ color: #0077B6; font-weight: 600; }}
|
||
|
||
QComboBox {{ background: white; border: 1px solid #C0D8EC; border-radius: 10px; padding: 6px 10px; }}
|
||
QComboBox::drop-down {{ border: none; width: 22px; }}
|
||
QComboBox QAbstractItemView {{
|
||
background: white; border: 1px solid #C0D8EC; border-radius: 10px;
|
||
selection-background-color: {ACCENT}; selection-color: white; outline: none;
|
||
}}
|
||
|
||
QScrollBar:vertical {{ background: transparent; width: 11px; margin: 2px; }}
|
||
QScrollBar::handle:vertical {{ background: #C0D8EC; border-radius: 5px; min-height: 28px; }}
|
||
QScrollBar::handle:vertical:hover {{ background: {ACCENT}; }}
|
||
QScrollBar:horizontal {{ background: transparent; height: 11px; margin: 2px; }}
|
||
QScrollBar::handle:horizontal {{ background: #C0D8EC; border-radius: 5px; min-width: 28px; }}
|
||
QScrollBar::handle:horizontal:hover {{ background: {ACCENT}; }}
|
||
QScrollBar::add-line, QScrollBar::sub-line {{ height: 0; width: 0; border: none; background: none; }}
|
||
|
||
QCheckBox {{ spacing: 8px; }}
|
||
QCheckBox::indicator, QRadioButton::indicator {{
|
||
width: 16px; height: 16px; background: white; border: 1px solid #C0D8EC; border-radius: 5px;
|
||
}}
|
||
QRadioButton::indicator {{ border-radius: 9px; }}
|
||
QCheckBox::indicator:hover, QRadioButton::indicator:hover {{ border-color: {ACCENT}; }}
|
||
QCheckBox::indicator:checked, QRadioButton::indicator:checked {{
|
||
background: {GRADIENT}; border-color: {ACCENT};
|
||
}}
|
||
QCheckBox::indicator:disabled {{ border-color: #D0E8F5; background: #F0F8FC; }}
|
||
QCheckBox::indicator:checked:disabled, QRadioButton::indicator:checked:disabled {{
|
||
background: #B8D4E8; border-color: #8AB8D8;
|
||
}}
|
||
|
||
QMenu {{ background: white; color: #1A2332; border: 1px solid #C0D8EC; border-radius: 10px; padding: 4px; }}
|
||
QMenu::item {{ padding: 6px 16px; border-radius: 6px; }}
|
||
QMenu::item:selected {{ background: {ACCENT}; color: white; }}
|
||
|
||
QToolTip {{ background: #FFFFFF; color: #1A2332; border: 1px solid #C0D8EC; border-radius: 6px; padding: 4px 8px; }}
|
||
"""
|
||
|
||
# Node colors used by the Graph view (shared light/dark).
|
||
NODE_COLORS = {
|
||
"user": "#3B82F6",
|
||
"assistant": ACCENT,
|
||
"tool": "#8B5CF6",
|
||
"result": "#22A06B",
|
||
"error": "#E5484D",
|
||
}
|
||
|
||
|
||
def resolve_theme(theme: str) -> str:
|
||
"""Resolve 'system' to 'dark'/'light' based on the OS color scheme."""
|
||
if theme != "system":
|
||
return theme
|
||
try:
|
||
from PySide6.QtCore import Qt
|
||
from PySide6.QtWidgets import QApplication
|
||
|
||
app = QApplication.instance()
|
||
if app is not None:
|
||
scheme = app.styleHints().colorScheme()
|
||
return "light" if scheme == Qt.ColorScheme.Light else "dark"
|
||
except Exception:
|
||
pass
|
||
return "dark"
|
||
|
||
|
||
def stylesheet(theme: str) -> str:
|
||
return _LIGHT if resolve_theme(theme) == "light" else _DARK |