UI Enhancements.

This commit is contained in:
2026-03-28 18:01:49 +05:30
parent 79b120ff91
commit a90e3a0d84
15 changed files with 292 additions and 58 deletions

View File

@@ -3,6 +3,8 @@ APIClient - Agent - Central Theme Engine
All styling lives here in the global QSS.
UI widgets use setObjectName() selectors - never inline setStyleSheet() for static colors.
Only truly dynamic values (per-request method color, status badge) stay inline.
Fonts: Quicksand (headings/brand/UI labels) + Open Sans (body text/inputs)
"""
from PyQt6.QtGui import QColor, QPalette
from PyQt6.QtWidgets import QApplication
@@ -100,6 +102,17 @@ class LightColors:
Colors = DarkColors
_is_dark = True
# ── Zoom level (1.0 = 100%) ───────────────────────────────────────────────────
_zoom = 1.0
_ZOOM_MIN = 0.7
_ZOOM_MAX = 1.8
_ZOOM_STEP = 0.1
# Font families
_UI_FONT = '"Quicksand", "Segoe UI", "SF Pro Text", sans-serif'
_BODY_FONT = '"Open Sans", "Segoe UI", "SF Pro Text", sans-serif'
_MONO_FONT = '"JetBrains Mono", "Fira Code", "Cascadia Code", "Consolas", monospace'
def method_color(method: str) -> str:
return {
@@ -121,8 +134,12 @@ def status_color(code: int) -> str:
return Colors.STATUS_5XX
def _z(size: float) -> str:
"""Scale a font size by current zoom level and return px string."""
return f"{round(size * _zoom)}px"
# ── Global Stylesheet ─────────────────────────────────────────────────────────
# Everything static lives here. Object names are the API between theme and UI.
def _build_stylesheet(C) -> str:
return f"""
@@ -133,8 +150,8 @@ def _build_stylesheet(C) -> str:
QWidget {{
background-color: {C.BG_MAIN};
color: {C.TEXT_PRIMARY};
font-family: "Segoe UI", "SF Pro Text", "Inter", "Helvetica Neue", sans-serif;
font-size: 13px;
font-family: {_BODY_FONT};
font-size: {_z(13)};
border: none;
outline: none;
}}
@@ -178,6 +195,9 @@ QMenuBar {{
border-bottom: 1px solid {C.BORDER};
padding: 2px 4px;
spacing: 4px;
font-family: {_UI_FONT};
font-size: {_z(12)};
font-weight: 600;
}}
QMenuBar::item {{
background: transparent; padding: 4px 10px; border-radius: 4px;
@@ -191,6 +211,8 @@ QMenu {{
border: 1px solid {C.BORDER};
border-radius: 6px;
padding: 4px;
font-family: {_BODY_FONT};
font-size: {_z(12)};
}}
QMenu::item {{ padding: 7px 28px 7px 12px; border-radius: 4px; }}
QMenu::item:selected {{ background-color: {C.BG_HOVER}; }}
@@ -206,6 +228,8 @@ QLineEdit {{
border: 1px solid {C.BORDER};
border-radius: 5px;
padding: 6px 10px;
font-family: {_BODY_FONT};
font-size: {_z(13)};
selection-background-color: {C.ACCENT};
}}
QLineEdit:focus {{
@@ -224,6 +248,8 @@ QTextEdit, QPlainTextEdit {{
border: 1px solid {C.BORDER};
border-radius: 5px;
padding: 6px;
font-family: {_BODY_FONT};
font-size: {_z(13)};
selection-background-color: {C.ACCENT};
}}
QTextEdit:focus, QPlainTextEdit:focus {{
@@ -236,6 +262,8 @@ QSpinBox {{
border: 1px solid {C.BORDER};
border-radius: 5px;
padding: 5px 8px;
font-family: {_BODY_FONT};
font-size: {_z(13)};
}}
QSpinBox:focus {{ border-color: {C.BORDER_FOCUS}; }}
QSpinBox::up-button, QSpinBox::down-button {{
@@ -257,6 +285,8 @@ QComboBox {{
border-radius: 5px;
padding: 5px 10px;
min-width: 80px;
font-family: {_BODY_FONT};
font-size: {_z(13)};
}}
QComboBox:hover {{ border-color: {C.BORDER_FOCUS}; }}
QComboBox:focus {{ border-color: {C.BORDER_FOCUS}; }}
@@ -270,6 +300,8 @@ QComboBox QAbstractItemView {{
selection-background-color: {C.BG_SELECTED};
outline: none;
padding: 2px;
font-family: {_BODY_FONT};
font-size: {_z(13)};
}}
/* ════════════════════════════════════════════════════════
@@ -279,6 +311,8 @@ QCheckBox {{
color: {C.TEXT_SECONDARY};
spacing: 6px;
background: transparent;
font-family: {_BODY_FONT};
font-size: {_z(12)};
}}
QCheckBox::indicator {{
width: 14px; height: 14px;
@@ -299,7 +333,9 @@ QPushButton {{
border: 1px solid {C.BORDER};
border-radius: 5px;
padding: 6px 14px;
font-weight: 500;
font-family: {_UI_FONT};
font-size: {_z(12)};
font-weight: 600;
}}
QPushButton:hover {{
background-color: {C.BG_HOVER};
@@ -312,7 +348,9 @@ QPushButton#accent {{
background-color: {C.ACCENT};
color: #FFFFFF;
border: none;
font-weight: 600;
font-family: {_UI_FONT};
font-weight: 700;
font-size: {_z(12)};
}}
QPushButton#accent:hover {{ background-color: {C.ACCENT_HOVER}; }}
QPushButton#accent:pressed {{ background-color: {C.ACCENT_PRESSED}; }}
@@ -327,6 +365,9 @@ QPushButton#ghost {{
color: {C.TEXT_SECONDARY};
padding: 4px 8px;
border-radius: 4px;
font-family: {_UI_FONT};
font-size: {_z(12)};
font-weight: 600;
}}
QPushButton#ghost:hover {{
color: {C.TEXT_PRIMARY};
@@ -338,6 +379,9 @@ QPushButton#danger {{
background-color: transparent;
color: {C.ERROR};
border: 1px solid {C.ERROR};
font-family: {_UI_FONT};
font-size: {_z(12)};
font-weight: 600;
}}
QPushButton#danger:hover {{ background-color: {C.ACCENT_SUBTLE}; }}
@@ -347,8 +391,9 @@ QPushButton#sendBtn {{
border: none;
border-radius: 6px;
padding: 8px 22px;
font-family: {_UI_FONT};
font-weight: 700;
font-size: 13px;
font-size: {_z(13)};
letter-spacing: 0.3px;
}}
QPushButton#sendBtn:hover {{ background-color: {C.ACCENT_HOVER}; }}
@@ -358,6 +403,22 @@ QPushButton#sendBtn:disabled {{
color: {C.TEXT_MUTED};
}}
QPushButton#zoomBtn {{
background: transparent;
border: none;
color: {C.TEXT_MUTED};
padding: 2px 5px;
border-radius: 4px;
font-family: {_UI_FONT};
font-size: {_z(14)};
font-weight: 700;
}}
QPushButton#zoomBtn:hover {{
color: {C.TEXT_PRIMARY};
background-color: {C.BG_HOVER};
}}
QPushButton#zoomBtn:pressed {{ background-color: {C.BG_SELECTED}; }}
/* ════════════════════════════════════════════════════════
TABS
════════════════════════════════════════════════════════ */
@@ -372,8 +433,9 @@ QTabBar::tab {{
border: none;
border-bottom: 2px solid transparent;
padding: 8px 16px;
font-size: 12px;
font-weight: 500;
font-family: {_UI_FONT};
font-size: {_z(12)};
font-weight: 600;
}}
QTabBar::tab:selected {{
color: {C.TEXT_PRIMARY};
@@ -406,8 +468,9 @@ QTabWidget#workspaceTabs QTabBar::tab {{
border: none;
border-right: 1px solid {C.BORDER};
padding: 10px 20px;
font-size: 12px;
font-weight: 600;
font-family: {_UI_FONT};
font-size: {_z(12)};
font-weight: 700;
border-bottom: none;
border-top: 2px solid transparent;
}}
@@ -432,6 +495,8 @@ QTableWidget {{
gridline-color: {C.BORDER};
selection-background-color: {C.BG_SELECTED};
selection-color: {C.TEXT_PRIMARY};
font-family: {_BODY_FONT};
font-size: {_z(12)};
}}
QTableWidget::item {{
padding: 5px 8px;
@@ -448,8 +513,9 @@ QHeaderView::section {{
border-bottom: 1px solid {C.BORDER};
border-right: 1px solid {C.BORDER};
padding: 6px 8px;
font-size: 11px;
font-weight: 600;
font-family: {_UI_FONT};
font-size: {_z(11)};
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
}}
@@ -464,6 +530,8 @@ QTreeWidget {{
border: none;
outline: none;
show-decoration-selected: 1;
font-family: {_BODY_FONT};
font-size: {_z(12)};
}}
QTreeWidget::item {{
padding: 4px 4px;
@@ -485,6 +553,8 @@ QListWidget {{
border: 1px solid {C.BORDER};
border-radius: 5px;
outline: none;
font-family: {_BODY_FONT};
font-size: {_z(12)};
}}
QListWidget::item {{ padding: 8px 10px; border-radius: 3px; }}
QListWidget::item:selected {{
@@ -504,7 +574,8 @@ QListWidget#sidebarList::item {{
padding: 10px 14px;
border-bottom: 1px solid {C.BORDER};
border-radius: 0;
font-size: 13px;
font-family: {_BODY_FONT};
font-size: {_z(12)};
}}
QListWidget#sidebarList::item:selected {{
background: {C.BG_SELECTED}; color: {C.TEXT_PRIMARY};
@@ -518,7 +589,8 @@ QStatusBar {{
background-color: {C.BG_DARKEST};
color: {C.TEXT_SECONDARY};
border-top: 1px solid {C.BORDER};
font-size: 11px;
font-family: {_BODY_FONT};
font-size: {_z(11)};
padding: 0 8px;
}}
QStatusBar::item {{ border: none; }}
@@ -547,8 +619,9 @@ QGroupBox {{
border-radius: 6px;
margin-top: 8px;
padding: 8px;
font-size: 11px;
font-weight: 600;
font-family: {_UI_FONT};
font-size: {_z(11)};
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
}}
@@ -565,7 +638,8 @@ QToolTip {{
border: 1px solid {C.BORDER};
border-radius: 4px;
padding: 5px 8px;
font-size: 12px;
font-family: {_BODY_FONT};
font-size: {_z(12)};
}}
/* ════════════════════════════════════════════════════════
@@ -584,7 +658,7 @@ QFrame[frameShape="4"], QFrame[frameShape="5"] {{
}}
/* ════════════════════════════════════════════════════════
── NAMED WIDGET RULES (setObjectName API) ──
-- NAMED WIDGET RULES (setObjectName API) --
════════════════════════════════════════════════════════ */
/* Top brand / env bar */
@@ -594,24 +668,36 @@ QWidget#envBar {{
}}
QLabel#brandName {{
color: {C.ACCENT};
font-size: 15px;
font-family: {_UI_FONT};
font-size: {_z(15)};
font-weight: 800;
letter-spacing: 2px;
background: transparent;
}}
QLabel#brandSub {{
color: {C.TEXT_MUTED};
font-size: 11px;
font-weight: 500;
font-family: {_UI_FONT};
font-size: {_z(11)};
font-weight: 600;
background: transparent;
}}
QLabel#envChip {{
color: {C.TEXT_MUTED};
font-size: 10px;
font-family: {_UI_FONT};
font-size: {_z(10)};
font-weight: 700;
letter-spacing: 1px;
background: transparent;
}}
QLabel#zoomLabel {{
color: {C.TEXT_MUTED};
font-family: {_UI_FONT};
font-size: {_z(10)};
font-weight: 600;
background: transparent;
min-width: 36px;
qproperty-alignment: AlignCenter;
}}
/* Sidebar */
QWidget#sidebar {{
@@ -636,8 +722,8 @@ QLineEdit#urlBar {{
border: 1.5px solid {C.BORDER};
border-radius: 6px;
padding: 8px 12px;
font-size: 13px;
font-family: "JetBrains Mono", "Fira Code", "Cascadia Code", "Consolas", monospace;
font-family: {_MONO_FONT};
font-size: {_z(13)};
color: {C.TEXT_PRIMARY};
}}
QLineEdit#urlBar:focus {{
@@ -647,8 +733,9 @@ QLineEdit#urlBar:focus {{
/* Method combo (color set inline per method, only layout here) */
QComboBox#methodCombo {{
font-family: {_UI_FONT};
font-weight: 800;
font-size: 12px;
font-size: {_z(12)};
border-radius: 6px;
padding: 8px 10px;
min-width: 100px;
@@ -677,14 +764,16 @@ QWidget#responseBar {{
}}
QLabel#responseTitle {{
color: {C.TEXT_MUTED};
font-size: 10px;
font-family: {_UI_FONT};
font-size: {_z(10)};
font-weight: 700;
letter-spacing: 1.2px;
background: transparent;
}}
QLabel#metaLabel {{
color: {C.TEXT_MUTED};
font-size: 11px;
font-family: {_BODY_FONT};
font-size: {_z(11)};
background: transparent;
padding: 0 6px;
}}
@@ -708,26 +797,30 @@ QWidget#panelBody {{
/* Labels inside panels */
QLabel#panelTitle {{
font-size: 14px;
font-family: {_UI_FONT};
font-size: {_z(14)};
font-weight: 700;
color: {C.TEXT_PRIMARY};
background: transparent;
}}
QLabel#sectionLabel {{
color: {C.TEXT_MUTED};
font-size: 10px;
font-family: {_UI_FONT};
font-size: {_z(10)};
font-weight: 700;
letter-spacing: 1px;
background: transparent;
}}
QLabel#hintText {{
color: {C.TEXT_MUTED};
font-size: 11px;
font-family: {_BODY_FONT};
font-size: {_z(11)};
background: transparent;
}}
QLabel#fieldLabel {{
color: {C.TEXT_SECONDARY};
font-size: 12px;
font-family: {_BODY_FONT};
font-size: {_z(12)};
background: transparent;
}}
@@ -737,8 +830,8 @@ QTextEdit#codeEditor {{
color: {C.TEXT_PRIMARY};
border: none;
padding: 8px;
font-family: "JetBrains Mono", "Fira Code", "Cascadia Code", "Consolas", monospace;
font-size: 11px;
font-family: {_MONO_FONT};
font-size: {_z(11)};
}}
/* Loading overlay */
@@ -747,7 +840,8 @@ QWidget#loadingOverlay {{
}}
QLabel#loadingLabel {{
color: {C.TEXT_MUTED};
font-size: 13px;
font-family: {_BODY_FONT};
font-size: {_z(13)};
background: transparent;
}}
@@ -757,7 +851,8 @@ QLineEdit#searchBar {{
border: 1px solid {C.BORDER};
border-radius: 4px;
padding: 4px 8px;
font-size: 12px;
font-family: {_BODY_FONT};
font-size: {_z(12)};
color: {C.TEXT_PRIMARY};
}}
QLineEdit#searchBar:focus {{ border-color: {C.BORDER_FOCUS}; }}
@@ -768,7 +863,8 @@ QLineEdit#filterInput {{
border: 1px solid {C.BORDER};
border-radius: 4px;
padding: 5px 8px;
font-size: 12px;
font-family: {_BODY_FONT};
font-size: {_z(12)};
color: {C.TEXT_PRIMARY};
}}
QLineEdit#filterInput:focus {{ border-color: {C.BORDER_FOCUS}; }}
@@ -776,19 +872,22 @@ QLineEdit#filterInput:focus {{ border-color: {C.BORDER_FOCUS}; }}
/* WebSocket / Mock status indicator labels */
QLabel#statusOk {{
color: {C.SUCCESS};
font-size: 12px;
font-family: {_UI_FONT};
font-size: {_z(12)};
font-weight: 600;
background: transparent;
}}
QLabel#statusWarn {{
color: {C.WARNING};
font-size: 12px;
font-family: {_UI_FONT};
font-size: {_z(12)};
font-weight: 600;
background: transparent;
}}
QLabel#statusErr {{
color: {C.ERROR};
font-size: 12px;
font-family: {_UI_FONT};
font-size: {_z(12)};
font-weight: 600;
background: transparent;
}}
@@ -796,6 +895,8 @@ QLabel#statusErr {{
/* Auth "none" hint */
QLabel#authNone {{
color: {C.TEXT_MUTED};
font-family: {_BODY_FONT};
font-size: {_z(12)};
padding: 12px;
background: transparent;
}}
@@ -811,7 +912,7 @@ QPushButton#tabCloseBtn {{
border: none;
border-radius: 3px;
color: {C.TEXT_MUTED};
font-size: 14px;
font-size: {_z(14)};
font-weight: 700;
padding: 0;
}}
@@ -830,15 +931,17 @@ QTextEdit#aiOutput {{
border: 1px solid {C.BORDER};
border-radius: 5px;
padding: 8px;
font-size: 12px;
font-family: {_BODY_FONT};
font-size: {_z(12)};
}}
QLabel#aiStatusLabel {{
color: {C.TEXT_MUTED};
font-size: 11px;
font-family: {_BODY_FONT};
font-size: {_z(11)};
background: transparent;
}}
/* ── AI Chat Panel ─────────────────────────────────────── */
/* -- AI Chat Panel ─────────────────────────────────────── */
QWidget#aiChatPanel {{
background-color: {C.BG_SIDEBAR};
border-left: 1px solid {C.BORDER};
@@ -849,7 +952,8 @@ QWidget#aiChatHeader {{
}}
QLabel#aiChatTitle {{
color: {C.ACCENT};
font-size: 13px;
font-family: {_UI_FONT};
font-size: {_z(13)};
font-weight: 700;
letter-spacing: 0.5px;
background: transparent;
@@ -871,7 +975,8 @@ QFrame#aiBubble {{
margin: 0px;
}}
QLabel#chatRoleLabel {{
font-size: 10px;
font-family: {_UI_FONT};
font-size: {_z(10)};
font-weight: 700;
color: {C.TEXT_MUTED};
background: transparent;
@@ -881,7 +986,8 @@ QLabel#chatRoleLabel {{
QLabel#chatMessageText {{
color: {C.TEXT_PRIMARY};
background: transparent;
font-size: 12px;
font-family: {_BODY_FONT};
font-size: {_z(12)};
line-height: 1.6;
}}
QWidget#chatInputArea {{
@@ -893,7 +999,8 @@ QTextEdit#chatInput {{
border: 1px solid {C.BORDER};
border-radius: 6px;
padding: 6px 10px;
font-size: 12px;
font-family: {_BODY_FONT};
font-size: {_z(12)};
color: {C.TEXT_PRIMARY};
}}
QTextEdit#chatInput:focus {{
@@ -909,9 +1016,10 @@ QPushButton#qaBtn {{
border: 1px solid {C.BORDER};
border-radius: 10px;
padding: 2px 8px;
font-size: 11px;
font-family: {_UI_FONT};
font-size: {_z(11)};
color: {C.TEXT_SECONDARY};
font-weight: 500;
font-weight: 600;
}}
QPushButton#qaBtn:hover {{
background-color: {C.BG_HOVER};
@@ -928,8 +1036,8 @@ QTextEdit#applyCode {{
background-color: transparent;
border: none;
padding: 4px;
font-size: 10px;
font-family: "JetBrains Mono", "Fira Code", "Cascadia Code", "Consolas", monospace;
font-family: {_MONO_FONT};
font-size: {_z(10)};
color: {C.TEXT_SECONDARY};
}}
@@ -974,6 +1082,34 @@ def is_dark() -> bool:
return _is_dark
def zoom_in(app: QApplication) -> float:
"""Increase zoom one step. Returns new zoom level."""
global _zoom
_zoom = min(_ZOOM_MAX, round(_zoom + _ZOOM_STEP, 1))
app.setStyleSheet(_build_stylesheet(Colors))
return _zoom
def zoom_out(app: QApplication) -> float:
"""Decrease zoom one step. Returns new zoom level."""
global _zoom
_zoom = max(_ZOOM_MIN, round(_zoom - _ZOOM_STEP, 1))
app.setStyleSheet(_build_stylesheet(Colors))
return _zoom
def zoom_reset(app: QApplication) -> float:
"""Reset zoom to 100%. Returns new zoom level."""
global _zoom
_zoom = 1.0
app.setStyleSheet(_build_stylesheet(Colors))
return _zoom
def get_zoom() -> float:
return _zoom
def restyle(widget, obj_name: str) -> None:
"""Change a widget's objectName and force Qt to re-evaluate CSS rules."""
widget.setObjectName(obj_name)