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

24
app/core/fonts.py Normal file
View File

@@ -0,0 +1,24 @@
"""Font loader - registers bundled Quicksand and Open Sans with Qt."""
import os
from PyQt6.QtGui import QFontDatabase
_ASSETS_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "assets", "fonts")
_FONT_FILES = [
"Quicksand-Regular.ttf",
"Quicksand-Medium.ttf",
"Quicksand-SemiBold.ttf",
"Quicksand-Bold.ttf",
"OpenSans-Regular.ttf",
"OpenSans-SemiBold.ttf",
"OpenSans-Bold.ttf",
]
def load_fonts() -> None:
"""Register all bundled fonts with QFontDatabase. Call once after QApplication is created."""
fonts_dir = os.path.normpath(_ASSETS_DIR)
for filename in _FONT_FILES:
path = os.path.join(fonts_dir, filename)
if os.path.exists(path):
QFontDatabase.addApplicationFont(path)

View File

@@ -1,20 +1,23 @@
"""APIClient - Agent - Main Window."""
import os
from PyQt6.QtWidgets import (
QMainWindow, QSplitter, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QComboBox, QPushButton, QStatusBar, QTabWidget,
QInputDialog, QMessageBox, QFileDialog, QApplication
)
from PyQt6.QtCore import Qt, QThread, pyqtSignal
from PyQt6.QtGui import QKeySequence, QShortcut
from PyQt6.QtGui import QKeySequence, QShortcut, QPixmap
from app.ui.tabs_manager import TabsManager
from app.ui.response_panel import ResponsePanel
from app.ui.sidebar import CollectionsSidebar
from app.ui.theme import Colors, toggle as toggle_theme, is_dark
from app.ui.theme import Colors, toggle as toggle_theme, is_dark, zoom_in, zoom_out, zoom_reset, get_zoom
from app.core import http_client, storage
from app.core.test_runner import run_tests
from app.models import HttpRequest
_ASSETS_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "assets")
APP_VERSION = "2.0.0"
APP_NAME = "APIClient - Agent"
@@ -50,22 +53,62 @@ class EnvBar(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setObjectName("envBar")
self.setFixedHeight(46)
self.setFixedHeight(48)
layout = QHBoxLayout(self)
layout.setContentsMargins(16, 0, 16, 0)
layout.setContentsMargins(12, 0, 12, 0)
layout.setSpacing(6)
# EKIKA logo
logo_path = os.path.join(_ASSETS_DIR, "ekika_logo.png")
if os.path.exists(logo_path):
logo_lbl = QLabel()
logo_lbl.setObjectName("ekikaLogo")
pix = QPixmap(logo_path)
logo_lbl.setPixmap(pix.scaledToHeight(26, Qt.TransformationMode.SmoothTransformation))
logo_lbl.setToolTip("EKIKA")
layout.addWidget(logo_lbl)
# Thin separator
sep = QLabel("|")
sep.setObjectName("brandSub")
sep.setFixedWidth(12)
sep.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(sep)
brand = QLabel("APIClient")
brand.setObjectName("brandName")
sub = QLabel("Agent")
sub = QLabel("- Agent")
sub.setObjectName("brandSub")
layout.addWidget(brand)
layout.addWidget(sub)
layout.addStretch()
# Zoom controls
zoom_out_btn = QPushButton("-")
zoom_out_btn.setObjectName("zoomBtn")
zoom_out_btn.setFixedSize(24, 24)
zoom_out_btn.setToolTip("Zoom Out (Ctrl+-)")
layout.addWidget(zoom_out_btn)
self.zoom_out_btn = zoom_out_btn
self.zoom_label = QLabel("100%")
self.zoom_label.setObjectName("zoomLabel")
self.zoom_label.setFixedWidth(42)
self.zoom_label.setToolTip("Current zoom level (Ctrl+0 to reset)")
layout.addWidget(self.zoom_label)
zoom_in_btn = QPushButton("+")
zoom_in_btn.setObjectName("zoomBtn")
zoom_in_btn.setFixedSize(24, 24)
zoom_in_btn.setToolTip("Zoom In (Ctrl+=)")
layout.addWidget(zoom_in_btn)
self.zoom_in_btn = zoom_in_btn
# Thin gap
layout.addSpacing(8)
env_label = QLabel("ENV")
env_label.setObjectName("envChip")
layout.addWidget(env_label)
@@ -119,6 +162,8 @@ class MainWindow(QMainWindow):
self.env_bar.manage_btn.clicked.connect(self._open_env_dialog)
self.env_bar.theme_btn.clicked.connect(self._toggle_theme)
self.env_bar.ai_btn.clicked.connect(self._toggle_ai_chat)
self.env_bar.zoom_in_btn.clicked.connect(self._zoom_in)
self.env_bar.zoom_out_btn.clicked.connect(self._zoom_out)
root_layout.addWidget(self.env_bar)
splitter = QSplitter(Qt.Orientation.Horizontal)
@@ -203,6 +248,10 @@ class MainWindow(QMainWindow):
view_m = mb.addMenu("View")
view_m.addAction("Search Requests", self._open_search).setShortcut("Ctrl+F")
view_m.addAction("Toggle Theme", self._toggle_theme)
view_m.addSeparator()
view_m.addAction("Zoom In", self._zoom_in).setShortcut("Ctrl+=")
view_m.addAction("Zoom Out", self._zoom_out).setShortcut("Ctrl+-")
view_m.addAction("Reset Zoom", self._zoom_reset).setShortcut("Ctrl+0")
tools_m = mb.addMenu("Tools")
tools_m.addAction("Environments…", self._open_env_dialog).setShortcut("Ctrl+E")
@@ -227,10 +276,14 @@ class MainWindow(QMainWindow):
QShortcut(QKeySequence("Ctrl+W"), self, self.tabs_manager.close_current_tab)
QShortcut(QKeySequence("Ctrl+S"), self, self._save_to_collection)
QShortcut(QKeySequence("Ctrl+F"), self, self._open_search)
QShortcut(QKeySequence("Ctrl+E"), self, self._open_env_dialog)
QShortcut(QKeySequence("Ctrl+Shift+A"), self, self._toggle_ai_chat)
QShortcut(QKeySequence("Ctrl+E"), self, self._open_env_dialog)
QShortcut(QKeySequence("Ctrl+Shift+A"), self, self._toggle_ai_chat)
QShortcut(QKeySequence("Escape"), self, self._cancel_request)
QShortcut(QKeySequence("Ctrl+Q"), self, self.close)
QShortcut(QKeySequence("Ctrl+="), self, self._zoom_in)
QShortcut(QKeySequence("Ctrl++"), self, self._zoom_in)
QShortcut(QKeySequence("Ctrl+-"), self, self._zoom_out)
QShortcut(QKeySequence("Ctrl+0"), self, self._zoom_reset)
# ── Environment ───────────────────────────────────────────────────────────
@@ -443,6 +496,23 @@ class MainWindow(QMainWindow):
elif atype == "test":
rp.apply_test_script(content)
# ── Zoom ──────────────────────────────────────────────────────────────────
def _zoom_in(self):
level = zoom_in(QApplication.instance())
self._update_zoom_label(level)
def _zoom_out(self):
level = zoom_out(QApplication.instance())
self._update_zoom_label(level)
def _zoom_reset(self):
level = zoom_reset(QApplication.instance())
self._update_zoom_label(level)
def _update_zoom_label(self, level: float):
self.env_bar.zoom_label.setText(f"{round(level * 100)}%")
# ── Theme ─────────────────────────────────────────────────────────────────
def _toggle_theme(self):

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)