Files
DBClient/app/config/theme.py
T
nngoandClaude Sonnet 4.6 06b951daf7 feat: dark/light theme toggle (Catppuccin Frappé ↔ Latte)
- app/config/theme.py: palette definitions for both themes + apply_qss()
  helper used by both main.py (startup) and MainWindow (live toggle)
- resources/style_light.qss: complete Catppuccin Latte QSS
- View menu: ☀️/🌙 Switch Theme action (Ctrl+Shift+T) toggles live
- Preferences → Appearance: Color theme dropdown persists choice
- SQLHighlighter: _build_rules() reads get_palette(); update_theme()
  rebuilds rules and rehighlights all open editors on switch
- SqlCompleter: popup stylesheet reads get_palette() via
  _apply_popup_style(); update_theme() called on switch
- EditableTableModel: dirty/delete cell colors read get_palette() live
- ResultTableModel: NULL_COLOR property reads get_palette() live
- TableViewer: apply_settings() refreshes frozen-view border + repaints

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 09:11:05 -04:00

122 lines
3.6 KiB
Python

"""
Theme palettes — Catppuccin Frappé (dark) and Catppuccin Latte (light).
Usage:
from app.config.theme import get_palette, apply_qss, is_dark
"""
from pathlib import Path
# ── Catppuccin Frappé ─────────────────────────────────────────────────────────
DARK = {
"base": "#303446",
"mantle": "#292c3c",
"crust": "#232634",
"surface0": "#414559",
"surface1": "#51576d",
"surface2": "#626880",
"overlay": "#737994",
"subtext": "#a5adce",
"text": "#c6d0f5",
"blue": "#8caaee",
"lavender": "#babbf1",
"green": "#a6d189",
"teal": "#81c8be",
"sky": "#99d1db",
"red": "#e78284",
"peach": "#ef9f76",
"yellow": "#e5c890",
"mauve": "#ca9ee6",
# derived UI tokens
"gutter_bg": "#2a2d3e",
"dirty_bg": "#3a2f25",
"dirty_fg": "#ef9f76",
"delete_bg": "#3a2530",
"delete_fg": "#e78284",
# syntax
"syn_keyword": "#8caaee",
"syn_type": "#ef9f76",
"syn_function": "#a6d189",
"syn_string": "#a6d189",
"syn_number": "#ef9f76",
"syn_comment": "#737994",
"syn_operator": "#ca9ee6",
"syn_ident": "#99d1db",
# completer popup
"popup_bg": "#292c3c",
"popup_border": "#51576d",
"popup_selected": "#414559",
"popup_hover": "#363a4f",
}
# ── Catppuccin Latte ──────────────────────────────────────────────────────────
LIGHT = {
"base": "#eff1f5",
"mantle": "#e6e9ef",
"crust": "#dce0e8",
"surface0": "#ccd0da",
"surface1": "#bcc0cc",
"surface2": "#acb0be",
"overlay": "#9ca0b0",
"subtext": "#6c6f85",
"text": "#4c4f69",
"blue": "#1e66f5",
"lavender": "#7287fd",
"green": "#40a02b",
"teal": "#179299",
"sky": "#04a5e5",
"red": "#d20f39",
"peach": "#fe640b",
"yellow": "#df8e1d",
"mauve": "#8839ef",
# derived UI tokens
"gutter_bg": "#dce0e8",
"dirty_bg": "#fff3e6",
"dirty_fg": "#fe640b",
"delete_bg": "#ffe8ed",
"delete_fg": "#d20f39",
# syntax
"syn_keyword": "#1e66f5",
"syn_type": "#fe640b",
"syn_function": "#40a02b",
"syn_string": "#40a02b",
"syn_number": "#fe640b",
"syn_comment": "#9ca0b0",
"syn_operator": "#8839ef",
"syn_ident": "#04a5e5",
# completer popup
"popup_bg": "#e6e9ef",
"popup_border": "#bcc0cc",
"popup_selected": "#ccd0da",
"popup_hover": "#d8dce8",
}
_QSS_DIR = Path(__file__).parent.parent.parent / "resources"
def get_palette() -> dict:
"""Return the color dict for the currently active theme."""
from app.config.settings import get_settings
name = get_settings().get("theme", "dark")
return DARK if name == "dark" else LIGHT
def is_dark() -> bool:
from app.config.settings import get_settings
return get_settings().get("theme", "dark") == "dark"
def qss_path(theme: str) -> Path:
name = "style" if theme == "dark" else "style_light"
return _QSS_DIR / f"{name}.qss"
def apply_qss(app, theme: str = None) -> None:
"""Load and apply the QSS for *theme* (defaults to current setting)."""
from app.config.settings import get_settings
if theme is None:
theme = get_settings().get("theme", "dark")
path = qss_path(theme)
if path.exists():
app.setStyleSheet(path.read_text(encoding="utf-8"))