Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions modules/ui/PySide6TopBarView.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from modules.ui.TopBarController import TopBarController
from modules.util.enum.ModelType import ModelType
from modules.util.enum.TrainingMethod import TrainingMethod
from modules.util.ui import pyside6_components
from modules.util.ui import pyside6_components, theme

from PySide6.QtWidgets import QFileDialog, QWidget
from PySide6.QtWidgets import QApplication, QFileDialog, QPushButton, QWidget


class PySide6TopBarView(BaseTopBarView, QWidget):
Expand All @@ -33,6 +33,20 @@ def __init__(
self.build(self.frame, master, controller, ui_state,
change_model_type_callback, change_training_method_callback, load_preset_callback)

# Theme switch. Lives in the PySide6 view, not the shared BaseTopBarView: the ctk top bar
# has no color scheme to switch. A text glyph follows the palette's ButtonText.
self.theme_button = pyside6_components.button(
self.frame, 0, 8, "◐", self.__toggle_theme,
tooltip="Switch between light and dark mode", sticky="v",
)
# Square and icon-sized: the grid stretches a button to its column otherwise. The side
# comes from a text-less button so the glyph's own metrics don't set it.
side = QPushButton().sizeHint().height()
self.theme_button.setFixedSize(side, side)

def __toggle_theme(self):
theme.apply_theme(QApplication.instance(), dark=not theme.is_dark_theme)

def _setup_frame_column_weight(self):
pyside6_components._layout(self.frame).setColumnStretch(5, 1)

Expand Down
35 changes: 3 additions & 32 deletions modules/util/ui/pyside6_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import sys
from abc import ABCMeta

from PySide6.QtCore import Qt
from PySide6.QtGui import QColor, QPalette
from modules.util.ui.theme import apply_theme

from PySide6.QtWidgets import QApplication, QStyleFactory, QWidget


Expand Down Expand Up @@ -32,36 +32,7 @@ def create_application() -> QApplication:
# controls via OS theme APIs, which breaks once an application stylesheet
# is set, producing a flatter look than Fusion's own stylesheet-aware painting.
app.setStyle(QStyleFactory.create("Fusion"))
app.styleHints().setColorScheme(Qt.ColorScheme.Light)

palette = app.palette()
palette.setColor(QPalette.ColorRole.Base, QColor("white"))
palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Base, QColor("#e0e0e0"))
app.setPalette(palette)

app.setStyleSheet("""
QLineEdit, QSpinBox, QDoubleSpinBox, QTextEdit, QPlainTextEdit {
padding: 2px 2px;
}
QCheckBox::indicator {
width: 16px;
height: 16px;
}
QProgressBar {
background-color: #c8c8c8;
}
QToolButton {
padding-top: 0px;
padding-bottom: 0px;
padding-right: 40px;
}
QToolButton::menu-indicator {
subcontrol-origin: padding;
subcontrol-position: right center;
width: 12px;
height: 12px;
right: 10px;
}
""")
apply_theme(app)

return app
69 changes: 69 additions & 0 deletions modules/util/ui/theme.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import platform

from PySide6.QtCore import Qt
from PySide6.QtGui import QColor, QPalette
from PySide6.QtWidgets import QApplication

IS_WINDOWS = platform.system() == "Windows"

_BASE_STYLESHEET = """
QLineEdit, QSpinBox, QDoubleSpinBox, QTextEdit, QPlainTextEdit {
padding: 2px 2px;
}
QCheckBox::indicator {
width: 16px;
height: 16px;
}
QProgressBar {
background-color: #c8c8c8;
}
QToolButton {
padding-top: 0px;
padding-bottom: 0px;
padding-right: 40px;
}
QToolButton::menu-indicator {
subcontrol-origin: padding;
subcontrol-position: right center;
width: 12px;
height: 12px;
right: 10px;
}
"""

# A scheme change regenerates only the palette roles nobody set explicitly. Base is set below
# for light mode, so it would keep that white through every later switch; each scheme is applied
# from a pristine copy taken before the first override instead.
_scheme_palettes = {}

# Whether the dark palette is the one currently applied.
is_dark_theme = False


def _capture_scheme_palettes(app: QApplication) -> None:
original_scheme = app.styleHints().colorScheme()
for scheme in (Qt.ColorScheme.Light, Qt.ColorScheme.Dark):
app.styleHints().setColorScheme(scheme)
_scheme_palettes[scheme] = QPalette(app.palette())
app.styleHints().setColorScheme(original_scheme)


def apply_theme(app: QApplication, dark: bool | None = None) -> None:
global is_dark_theme

if not _scheme_palettes:
_capture_scheme_palettes(app)

if dark is None:
is_dark = app.palette().color(QPalette.ColorRole.Window).lightness() < 128
dark = IS_WINDOWS and is_dark
is_dark_theme = dark

scheme = Qt.ColorScheme.Dark if dark else Qt.ColorScheme.Light
app.styleHints().setColorScheme(scheme)
palette = QPalette(_scheme_palettes[scheme])
if not dark:
palette.setColor(QPalette.ColorRole.Base, QColor("white"))
palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Base, QColor("#e0e0e0"))
app.setPalette(palette)
app.setStyleSheet(_BASE_STYLESHEET)