Add dark mode and simple theming support to GUI
- Add DARK_THEME stylesheet alongside existing LIGHT_THEME - Add load_theme() function to load themes by name - Support custom themes via ~/.cmdforge/theme.qss - Add View menu with Theme submenu for switching themes - Persist theme choice in QSettings across sessions - Update META_TOOLS.md to reflect implemented dependency commands Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
9baddeef18
commit
a134fb59c3
|
|
@ -1,8 +1,11 @@
|
||||||
# Meta-Tools: Tools That Call Other Tools
|
# Meta-Tools: Tools That Call Other Tools
|
||||||
|
|
||||||
> **Document Status (January 2026)**: The `tool` step type is implemented and works as described.
|
> **Document Status (January 2026)**: The `tool` step type is implemented and works as described.
|
||||||
> The CLI dependency commands (`cmdforge install --deps`, `cmdforge check`) are not yet implemented.
|
> The CLI dependency commands are now implemented:
|
||||||
> Tools can still use `tool` steps, but dependency resolution is manual.
|
> - `cmdforge check <tool>` - Check if dependencies are satisfied
|
||||||
|
> - `cmdforge install` - Install dependencies from cmdforge.yaml manifest
|
||||||
|
> - `cmdforge add <owner/name>` - Add a tool to project dependencies
|
||||||
|
> - `--auto-install` flag on tool execution for automatic dependency installation
|
||||||
|
|
||||||
Meta-tools are CmdForge tools that can invoke other tools as steps in their workflow. This enables powerful composition and reuse of existing tools.
|
Meta-tools are CmdForge tools that can invoke other tools as steps in their workflow. This enables powerful composition and reuse of existing tools.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ from PySide6.QtWidgets import (
|
||||||
QStatusBar, QLabel, QSplitter, QMenuBar, QMenu
|
QStatusBar, QLabel, QSplitter, QMenuBar, QMenu
|
||||||
)
|
)
|
||||||
from PySide6.QtCore import Qt, QSize, QSettings, QTimer
|
from PySide6.QtCore import Qt, QSize, QSettings, QTimer
|
||||||
from PySide6.QtGui import QIcon, QFont, QShortcut, QKeySequence, QAction
|
from PySide6.QtGui import QIcon, QFont, QShortcut, QKeySequence, QAction, QActionGroup
|
||||||
|
|
||||||
from .styles import STYLESHEET
|
from .styles import load_theme, get_available_themes, get_custom_theme_path
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QMainWindow):
|
class MainWindow(QMainWindow):
|
||||||
|
|
@ -20,12 +20,13 @@ class MainWindow(QMainWindow):
|
||||||
self.setMinimumSize(1000, 700)
|
self.setMinimumSize(1000, 700)
|
||||||
self.resize(1200, 800)
|
self.resize(1200, 800)
|
||||||
|
|
||||||
# Apply stylesheet
|
|
||||||
self.setStyleSheet(STYLESHEET)
|
|
||||||
|
|
||||||
# Settings for persistence
|
# Settings for persistence
|
||||||
self._settings = QSettings("CmdForge", "CmdForge")
|
self._settings = QSettings("CmdForge", "CmdForge")
|
||||||
|
|
||||||
|
# Load and apply theme
|
||||||
|
self._current_theme = self._settings.value("theme", "light")
|
||||||
|
self._apply_theme(self._current_theme)
|
||||||
|
|
||||||
# Central widget
|
# Central widget
|
||||||
central = QWidget()
|
central = QWidget()
|
||||||
self.setCentralWidget(central)
|
self.setCentralWidget(central)
|
||||||
|
|
@ -235,10 +236,54 @@ class MainWindow(QMainWindow):
|
||||||
shortcut_help = QShortcut(QKeySequence("F1"), self)
|
shortcut_help = QShortcut(QKeySequence("F1"), self)
|
||||||
shortcut_help.activated.connect(self._show_getting_started)
|
shortcut_help.activated.connect(self._show_getting_started)
|
||||||
|
|
||||||
|
def _apply_theme(self, theme_name: str):
|
||||||
|
"""Apply a theme to the application."""
|
||||||
|
stylesheet = load_theme(theme_name)
|
||||||
|
self.setStyleSheet(stylesheet)
|
||||||
|
self._current_theme = theme_name
|
||||||
|
self._settings.setValue("theme", theme_name)
|
||||||
|
|
||||||
def _setup_menu_bar(self):
|
def _setup_menu_bar(self):
|
||||||
"""Set up the menu bar with Help menu."""
|
"""Set up the menu bar with View and Help menus."""
|
||||||
menubar = self.menuBar()
|
menubar = self.menuBar()
|
||||||
|
|
||||||
|
# View menu
|
||||||
|
view_menu = menubar.addMenu("&View")
|
||||||
|
|
||||||
|
# Theme submenu
|
||||||
|
theme_menu = view_menu.addMenu("&Theme")
|
||||||
|
theme_group = QActionGroup(self)
|
||||||
|
theme_group.setExclusive(True)
|
||||||
|
|
||||||
|
# Add theme options
|
||||||
|
for theme_name in get_available_themes():
|
||||||
|
display_name = theme_name.capitalize()
|
||||||
|
if theme_name == "custom":
|
||||||
|
display_name = f"Custom ({get_custom_theme_path().name})"
|
||||||
|
|
||||||
|
action = QAction(display_name, self)
|
||||||
|
action.setCheckable(True)
|
||||||
|
action.setChecked(theme_name == self._current_theme)
|
||||||
|
action.setData(theme_name)
|
||||||
|
# Use default argument to capture theme_name by value
|
||||||
|
action.triggered.connect(lambda checked=False, t=theme_name: self._apply_theme(t))
|
||||||
|
theme_group.addAction(action)
|
||||||
|
theme_menu.addAction(action)
|
||||||
|
|
||||||
|
# Add hint about custom themes
|
||||||
|
theme_menu.addSeparator()
|
||||||
|
hint_action = QAction(f"Custom: ~/.cmdforge/theme.qss", self)
|
||||||
|
hint_action.setEnabled(False)
|
||||||
|
theme_menu.addAction(hint_action)
|
||||||
|
|
||||||
|
view_menu.addSeparator()
|
||||||
|
|
||||||
|
# Refresh action
|
||||||
|
action_refresh = QAction("&Refresh", self)
|
||||||
|
action_refresh.setShortcut("Ctrl+R")
|
||||||
|
action_refresh.triggered.connect(self._shortcut_refresh)
|
||||||
|
view_menu.addAction(action_refresh)
|
||||||
|
|
||||||
# Help menu
|
# Help menu
|
||||||
help_menu = menubar.addMenu("&Help")
|
help_menu = menubar.addMenu("&Help")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
"""Modern stylesheet for CmdForge GUI."""
|
"""Theme stylesheets for CmdForge GUI."""
|
||||||
|
|
||||||
STYLESHEET = """
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Light theme (default)
|
||||||
|
LIGHT_THEME = """
|
||||||
/* Main Window */
|
/* Main Window */
|
||||||
QMainWindow {
|
QMainWindow {
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
|
|
@ -401,4 +404,534 @@ QRadioButton::indicator:checked {
|
||||||
border-radius: 9px;
|
border-radius: 9px;
|
||||||
background-color: #667eea;
|
background-color: #667eea;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Menu bar */
|
||||||
|
QMenuBar {
|
||||||
|
background-color: #f7fafc;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMenuBar::item {
|
||||||
|
padding: 6px 12px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMenuBar::item:selected {
|
||||||
|
background-color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMenu {
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMenu::item {
|
||||||
|
padding: 8px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMenu::item:selected {
|
||||||
|
background-color: #667eea;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMenu::separator {
|
||||||
|
height: 1px;
|
||||||
|
background-color: #e2e8f0;
|
||||||
|
margin: 4px 8px;
|
||||||
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Dark theme
|
||||||
|
DARK_THEME = """
|
||||||
|
/* Main Window */
|
||||||
|
QMainWindow {
|
||||||
|
background-color: #1a1a2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar */
|
||||||
|
#sidebar {
|
||||||
|
background-color: #16213e;
|
||||||
|
border: none;
|
||||||
|
min-width: 180px;
|
||||||
|
max-width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar::item {
|
||||||
|
color: #a0aec0;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: none;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar::item:selected {
|
||||||
|
background-color: #1f3460;
|
||||||
|
border-left: 3px solid #667eea;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar::item:hover:!selected {
|
||||||
|
background-color: #1a2d50;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Content area */
|
||||||
|
QWidget#content_area {
|
||||||
|
background-color: #1a1a2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
QPushButton {
|
||||||
|
background-color: #667eea;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-weight: 500;
|
||||||
|
min-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton:hover {
|
||||||
|
background-color: #7c8ff0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton:pressed {
|
||||||
|
background-color: #5a67d8;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton:disabled {
|
||||||
|
background-color: #4a5568;
|
||||||
|
color: #718096;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton#secondary {
|
||||||
|
background-color: #2d3748;
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton#secondary:hover {
|
||||||
|
background-color: #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton#danger {
|
||||||
|
background-color: #e53e3e;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton#danger:hover {
|
||||||
|
background-color: #fc5c65;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* View toggle buttons */
|
||||||
|
QPushButton#viewToggle {
|
||||||
|
background-color: #2d3748;
|
||||||
|
color: #a0aec0;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
min-height: 24px;
|
||||||
|
min-width: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton#viewToggle:checked {
|
||||||
|
background-color: #667eea;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPushButton#viewToggle:hover:!checked {
|
||||||
|
background-color: #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input fields */
|
||||||
|
QLineEdit, QTextEdit, QPlainTextEdit {
|
||||||
|
border: 1px solid #4a5568;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background-color: #2d3748;
|
||||||
|
color: #e2e8f0;
|
||||||
|
selection-background-color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
QLineEdit:focus, QTextEdit:focus, QPlainTextEdit:focus {
|
||||||
|
border-color: #667eea;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Combo boxes */
|
||||||
|
QComboBox {
|
||||||
|
border: 1px solid #4a5568;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background-color: #2d3748;
|
||||||
|
color: #e2e8f0;
|
||||||
|
min-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QComboBox:focus {
|
||||||
|
border-color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
QComboBox::drop-down {
|
||||||
|
border: none;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QComboBox::down-arrow {
|
||||||
|
image: none;
|
||||||
|
border-left: 5px solid transparent;
|
||||||
|
border-right: 5px solid transparent;
|
||||||
|
border-top: 6px solid #a0aec0;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QComboBox QAbstractItemView {
|
||||||
|
border: 1px solid #4a5568;
|
||||||
|
background-color: #2d3748;
|
||||||
|
color: #e2e8f0;
|
||||||
|
selection-background-color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lists and Trees */
|
||||||
|
QListWidget, QTreeWidget, QTableWidget {
|
||||||
|
border: 1px solid #4a5568;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #2d3748;
|
||||||
|
color: #e2e8f0;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
QListWidget::item, QTreeWidget::item {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
QListWidget::item:selected, QTreeWidget::item:selected {
|
||||||
|
background-color: #667eea;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
QListWidget::item:hover:!selected, QTreeWidget::item:hover:!selected {
|
||||||
|
background-color: #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table Widget */
|
||||||
|
QTableWidget {
|
||||||
|
gridline-color: #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTableWidget::item {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTableWidget::item:selected {
|
||||||
|
background-color: #667eea;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
QHeaderView::section {
|
||||||
|
background-color: #1a1a2e;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid #4a5568;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #a0aec0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Group boxes */
|
||||||
|
QGroupBox {
|
||||||
|
font-weight: 600;
|
||||||
|
border: 1px solid #4a5568;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
padding-top: 12px;
|
||||||
|
background-color: #2d3748;
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QGroupBox::title {
|
||||||
|
subcontrol-origin: margin;
|
||||||
|
subcontrol-position: top left;
|
||||||
|
left: 12px;
|
||||||
|
padding: 0 8px;
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Labels */
|
||||||
|
QLabel {
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QLabel#heading {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
QLabel#subheading {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #a0aec0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QLabel#label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #cbd5e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QLabel#sectionHeading {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbars */
|
||||||
|
QScrollBar:vertical {
|
||||||
|
background-color: #1a1a2e;
|
||||||
|
width: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QScrollBar::handle:vertical {
|
||||||
|
background-color: #4a5568;
|
||||||
|
border-radius: 6px;
|
||||||
|
min-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QScrollBar::handle:vertical:hover {
|
||||||
|
background-color: #718096;
|
||||||
|
}
|
||||||
|
|
||||||
|
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QScrollBar:horizontal {
|
||||||
|
background-color: #1a1a2e;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QScrollBar::handle:horizontal {
|
||||||
|
background-color: #4a5568;
|
||||||
|
border-radius: 6px;
|
||||||
|
min-width: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QScrollBar::handle:horizontal:hover {
|
||||||
|
background-color: #718096;
|
||||||
|
}
|
||||||
|
|
||||||
|
QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status bar */
|
||||||
|
QStatusBar {
|
||||||
|
background-color: #16213e;
|
||||||
|
border-top: 1px solid #4a5568;
|
||||||
|
color: #a0aec0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Splitter */
|
||||||
|
QSplitter::handle {
|
||||||
|
background-color: #4a5568;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSplitter::handle:horizontal {
|
||||||
|
width: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSplitter::handle:vertical {
|
||||||
|
height: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tabs */
|
||||||
|
QTabWidget::pane {
|
||||||
|
border: 1px solid #4a5568;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #2d3748;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTabBar::tab {
|
||||||
|
background-color: #1a1a2e;
|
||||||
|
border: 1px solid #4a5568;
|
||||||
|
border-bottom: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
margin-right: 2px;
|
||||||
|
border-top-left-radius: 6px;
|
||||||
|
border-top-right-radius: 6px;
|
||||||
|
color: #a0aec0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTabBar::tab:selected {
|
||||||
|
background-color: #2d3748;
|
||||||
|
border-bottom: 1px solid #2d3748;
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTabBar::tab:hover:!selected {
|
||||||
|
background-color: #16213e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dialogs */
|
||||||
|
QDialog {
|
||||||
|
background-color: #1a1a2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Message boxes */
|
||||||
|
QMessageBox {
|
||||||
|
background-color: #1a1a2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMessageBox QPushButton {
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tool tips */
|
||||||
|
QToolTip {
|
||||||
|
background-color: #4a5568;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Progress bar */
|
||||||
|
QProgressBar {
|
||||||
|
border: 1px solid #4a5568;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: #2d3748;
|
||||||
|
text-align: center;
|
||||||
|
height: 20px;
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QProgressBar::chunk {
|
||||||
|
background-color: #667eea;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Spin boxes */
|
||||||
|
QSpinBox, QDoubleSpinBox {
|
||||||
|
border: 1px solid #4a5568;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background-color: #2d3748;
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSpinBox:focus, QDoubleSpinBox:focus {
|
||||||
|
border-color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check boxes and radio buttons */
|
||||||
|
QCheckBox, QRadioButton {
|
||||||
|
spacing: 8px;
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QCheckBox::indicator, QRadioButton::indicator {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QCheckBox::indicator:unchecked {
|
||||||
|
border: 2px solid #4a5568;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #2d3748;
|
||||||
|
}
|
||||||
|
|
||||||
|
QCheckBox::indicator:checked {
|
||||||
|
border: 2px solid #667eea;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
QRadioButton::indicator:unchecked {
|
||||||
|
border: 2px solid #4a5568;
|
||||||
|
border-radius: 9px;
|
||||||
|
background-color: #2d3748;
|
||||||
|
}
|
||||||
|
|
||||||
|
QRadioButton::indicator:checked {
|
||||||
|
border: 2px solid #667eea;
|
||||||
|
border-radius: 9px;
|
||||||
|
background-color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menu bar */
|
||||||
|
QMenuBar {
|
||||||
|
background-color: #16213e;
|
||||||
|
border-bottom: 1px solid #4a5568;
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMenuBar::item {
|
||||||
|
padding: 6px 12px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMenuBar::item:selected {
|
||||||
|
background-color: #1f3460;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMenu {
|
||||||
|
background-color: #2d3748;
|
||||||
|
border: 1px solid #4a5568;
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMenu::item {
|
||||||
|
padding: 8px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMenu::item:selected {
|
||||||
|
background-color: #667eea;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMenu::separator {
|
||||||
|
height: 1px;
|
||||||
|
background-color: #4a5568;
|
||||||
|
margin: 4px 8px;
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Available themes
|
||||||
|
THEMES = {
|
||||||
|
"light": LIGHT_THEME,
|
||||||
|
"dark": DARK_THEME,
|
||||||
|
}
|
||||||
|
|
||||||
|
# For backwards compatibility
|
||||||
|
STYLESHEET = LIGHT_THEME
|
||||||
|
|
||||||
|
|
||||||
|
def get_custom_theme_path() -> Path:
|
||||||
|
"""Get path to custom theme file."""
|
||||||
|
return Path.home() / ".cmdforge" / "theme.qss"
|
||||||
|
|
||||||
|
|
||||||
|
def load_theme(theme_name: str = "light") -> str:
|
||||||
|
"""
|
||||||
|
Load a theme stylesheet.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
theme_name: Name of theme ("light", "dark") or "custom" for external file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The stylesheet string
|
||||||
|
"""
|
||||||
|
# Check for custom theme file first
|
||||||
|
custom_path = get_custom_theme_path()
|
||||||
|
if theme_name == "custom" and custom_path.exists():
|
||||||
|
try:
|
||||||
|
return custom_path.read_text()
|
||||||
|
except Exception:
|
||||||
|
pass # Fall back to light theme
|
||||||
|
|
||||||
|
# Return built-in theme
|
||||||
|
return THEMES.get(theme_name, LIGHT_THEME)
|
||||||
|
|
||||||
|
|
||||||
|
def get_available_themes() -> list:
|
||||||
|
"""Get list of available theme names."""
|
||||||
|
themes = list(THEMES.keys())
|
||||||
|
if get_custom_theme_path().exists():
|
||||||
|
themes.append("custom")
|
||||||
|
return themes
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue