diff --git a/modules/ui/BaseConceptWindowView.py b/modules/ui/BaseConceptWindowView.py index 0b94e50d1..885952954 100644 --- a/modules/ui/BaseConceptWindowView.py +++ b/modules/ui/BaseConceptWindowView.py @@ -425,7 +425,7 @@ def _update_concept_stats(self, controller): sec = self.bucket_ax.secondary_xaxis(location=-0.1) sec.spines["bottom"].set_linewidth(0) sec.set_xticks([0, (len(aspects)-1)/2, len(aspects)-1], labels=["Wide", "Square", "Tall"]) - sec.tick_params('x', length=0) + sec.tick_params('x', length=0, color=self.text_color) self.canvas.draw() def decimal_to_aspect_ratio(self, value : float): diff --git a/modules/ui/BaseTrainingTabView.py b/modules/ui/BaseTrainingTabView.py index 29a54bb1e..7b38a4c7b 100644 --- a/modules/ui/BaseTrainingTabView.py +++ b/modules/ui/BaseTrainingTabView.py @@ -869,8 +869,9 @@ def __create_loss_frame(self, master, row, controller, ui_state, row += 1 def __create_layer_frame(self, master, row, controller, ui_state): + frame = self.components.section_frame(master, row) presets = controller.get_layer_presets() - self.components.layer_filter_entry(master, row, 0, ui_state, + self.components.layer_filter_entry(frame, row, 0, ui_state, preset_var_name="layer_filter_preset", presets=presets, preset_label="Layer Filter", preset_tooltip="Select a preset defining which layers to train, or select 'Custom' to define your own.\nA blank 'custom' field or 'Full' will train all layers.", diff --git a/modules/ui/PySide6ConceptTabView.py b/modules/ui/PySide6ConceptTabView.py index a158dceed..1b44c876a 100644 --- a/modules/ui/PySide6ConceptTabView.py +++ b/modules/ui/PySide6ConceptTabView.py @@ -8,7 +8,7 @@ from PIL.ImageQt import ImageQt from PySide6.QtGui import QPixmap -from PySide6.QtWidgets import QCheckBox, QHBoxLayout, QLabel, QLineEdit, QPushButton, QWidget +from PySide6.QtWidgets import QCheckBox, QFrame, QHBoxLayout, QLabel, QLineEdit, QPushButton, QWidget class PySide6ConceptTabView(PySide6ConfigListView, BaseConceptTabView): @@ -102,10 +102,10 @@ def _reset_filters(self): self._update_filters() -class PySide6ConceptWidgetView(BaseConceptWidgetView, QWidget): +class PySide6ConceptWidgetView(BaseConceptWidgetView, QFrame): def __init__(self, master, concept, i, open_command, remove_command, clone_command, save_command, controller): - QWidget.__init__(self, master) + QFrame.__init__(self, master) BaseConceptWidgetView.__init__(self, pyside6_components, concept) self.ui_state = PySide6UIState(concept) self.image_ui_state = PySide6UIState(concept.image) @@ -113,6 +113,7 @@ def __init__(self, master, concept, i, open_command, remove_command, clone_comma self.i = i self.setFixedSize(160, 180) + self.setObjectName("section_frame") image = self._get_preview_image() pixmap = QPixmap.fromImage(ImageQt(image.convert("RGBA"))) diff --git a/modules/ui/PySide6LoraTabView.py b/modules/ui/PySide6LoraTabView.py index 842017697..f8fd44009 100644 --- a/modules/ui/PySide6LoraTabView.py +++ b/modules/ui/PySide6LoraTabView.py @@ -3,7 +3,7 @@ from modules.util.enum.ModelType import PeftType from modules.util.ui import pyside6_components -from PySide6.QtWidgets import QWidget +from PySide6.QtWidgets import QScrollArea, QWidget class PySide6LoraTabView(BaseLoraTabView, QWidget): @@ -16,6 +16,7 @@ def __init__(self, master, controller: LoraTabController, ui_state): self.controller = controller self.ui_state = ui_state self.scroll_frame = None + self.inner_frame = None self.options_frame = None self.refresh_ui() @@ -24,22 +25,27 @@ def refresh_ui(self): self.scroll_frame.hide() self.scroll_frame.deleteLater() - self.scroll_frame = QWidget(self) + self.scroll_frame = QScrollArea(self) + self.scroll_frame.setWidgetResizable(True) pyside6_components._layout(self).addWidget(self.scroll_frame, 0, 0) - lo = pyside6_components._layout(self.scroll_frame) + + self.inner_frame = QWidget() + self.scroll_frame.setWidget(self.inner_frame) + + lo = pyside6_components._layout(self.inner_frame) lo.setContentsMargins(pyside6_components.PAD, pyside6_components.PAD, pyside6_components.PAD, pyside6_components.PAD) lo.setColumnStretch(1, 1) lo.setColumnStretch(2, 2) - self.build(self.scroll_frame, self.controller, self.ui_state, self.setup_lora) - pyside6_components._pack_form(self.scroll_frame) + self.build(self.inner_frame, self.controller, self.ui_state, self.setup_lora) + pyside6_components._pack_form(self.inner_frame) def setup_lora(self, peft_type: PeftType): if self.options_frame is not None: self.options_frame.hide() self.options_frame.deleteLater() - self.options_frame = QWidget(self.scroll_frame) - pyside6_components._layout(self.scroll_frame).addWidget(self.options_frame, 1, 0, 1, 3) + self.options_frame = QWidget(self.inner_frame) + pyside6_components._layout(self.inner_frame).addWidget(self.options_frame, 1, 0, 1, 3) lo = pyside6_components._layout(self.options_frame) lo.setColumnStretch(1, 1) lo.setColumnStretch(4, 1) diff --git a/modules/ui/PySide6TimestepDistributionWindowView.py b/modules/ui/PySide6TimestepDistributionWindowView.py index 1e92fedc5..4adfc8e09 100644 --- a/modules/ui/PySide6TimestepDistributionWindowView.py +++ b/modules/ui/PySide6TimestepDistributionWindowView.py @@ -31,6 +31,21 @@ def __init__(self, parent, controller: TimestepDistributionWindowController, ui_ fig, self._ax = plt.subplots() self._canvas = FigureCanvasQTAgg(fig) + + palette = self.palette() + text_color = palette.text().color().name() + background_color = palette.window().color().name() + fig.set_facecolor(background_color) + + self._ax.set_facecolor(self.palette().window().color().name()) + self._ax.spines['bottom'].set_color(text_color) + self._ax.spines['left'].set_color(text_color) + self._ax.spines['top'].set_color(text_color) + self._ax.spines['right'].set_color(text_color) + self._ax.tick_params(axis='x', colors=text_color, which="both") + self._ax.tick_params(axis='y', colors=text_color, which="both") + self._ax.xaxis.label.set_color(text_color) + self._ax.yaxis.label.set_color(text_color) lo.addWidget(self._canvas, 0, 3, 8, 1) self._update_preview() diff --git a/modules/util/ui/pyside6_components.py b/modules/util/ui/pyside6_components.py index 5b1a18598..0ea6ab1eb 100644 --- a/modules/util/ui/pyside6_components.py +++ b/modules/util/ui/pyside6_components.py @@ -256,7 +256,7 @@ def path_entry( frame = QWidget(master) frame_lo = QGridLayout(frame) frame_lo.setContentsMargins(0, 0, 0, 0) - frame_lo.setSpacing(0) + frame_lo.setSpacing(2) frame_lo.setColumnStretch(0, 1) _add(_layout(master), frame, row, column, sticky="new", padx=0, pady=0, colspan=columnspan) @@ -335,6 +335,7 @@ def _open_dialog(): btn = QPushButton("...", frame) btn.setFixedWidth(40) + btn.setFixedHeight(entry_component.sizeHint().height()) btn.clicked.connect(_open_dialog) frame_lo.addWidget(btn, 0, 1) @@ -782,6 +783,7 @@ def section_frame(parent: QWidget, row: int, col: int = 0, colspan: int = 1) -> from PySide6.QtWidgets import QFrame frame = QFrame(parent) frame.setFrameShape(QFrame.Shape.StyledPanel) + frame.setObjectName("section_frame") _layout(parent).addWidget(frame, row, col, 1, colspan) frame_lo = _layout(frame) frame_lo.setColumnStretch(0, 1) diff --git a/modules/util/ui/pyside6_util.py b/modules/util/ui/pyside6_util.py index fd7b2ef8a..4a2c8c48c 100644 --- a/modules/util/ui/pyside6_util.py +++ b/modules/util/ui/pyside6_util.py @@ -24,36 +24,151 @@ 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; - } - """) + + color_schemes = { + Qt.ColorScheme.Light: { + "base": "#F9F9F9", + "text": "#1C1C1C", + "disabled": "#E0E0E0", + "disabled_text": "#7D7D7D", + "window": "#CFCFCF", + "window_section": "#DBDBDB", + "window_text": "#1C1C1C", + "button": "#36719F", + "button_hover": "#3B8ED0", + "button_disabled": "#A0A0A0", + "progress_bar": "#939BA2", + "checkbox": "#6AA5D2", + "checkbox_frame": "#3E4649", + "checkbox_hover": "#75B6E5", + "checkbox_glyph": "checkbox_light.png", + }, + Qt.ColorScheme.Dark: { + "base": "#343638", + "text": "#DCE4EE", + "disabled": "#2D2D2D", + "disabled_text": "#9D9D9D", + "window": "#333333", + "window_text": "#DCE4EE", + "window_section": "#2B2B2B", + "button": "#144870", + "button_hover": "#195A8C", + "button_disabled": "#404040", + "progress_bar": "#4A4D50", + "checkbox": "#346185", + "checkbox_frame": "#656A6E", + "checkbox_hover": "#3D739C", + "checkbox_glyph": "checkbox_dark.png", + }, + } + + def apply_palette(scheme=Qt.ColorScheme.Dark): + palette = app.palette() + colors = color_schemes[scheme] + palette.setColor(QPalette.ColorRole.Base, QColor(colors["base"])) + palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Base, QColor(colors["disabled"])) + palette.setColor(QPalette.ColorRole.Window, QColor(colors["window"])) + palette.setColor(QPalette.ColorRole.WindowText, QColor(colors["window_text"])) + + style_sheet = """ + QLineEdit, QSpinBox, QDoubleSpinBox, QTextEdit, QPlainTextEdit {{ + padding: 2px 2px; + }} + QCheckBox::indicator {{ + width: 18px; + height: 18px; + }} + QProgressBar {{ + background-color: {progress_bar}; + }} + 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; + }} + QFrame#section_frame {{ + background-color: {window_section}; + border-radius: 4px; + }} + """ + if scheme == Qt.ColorScheme.Dark: + palette.setColor(QPalette.ColorRole.Text, QColor(colors["text"])) + palette.setColor(QPalette.ColorRole.Highlight, QColor(colors["button_hover"])) + style_sheet += """ + QLineEdit, QSpinBox, QDoubleSpinBox, QTextEdit, QPlainTextEdit {{ + border: 1px solid {checkbox_frame}; + border-radius: 2px; + }} + QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus, QTextEdit:focus, QPlainTextEdit:focus {{ + border-color: palette(Highlight); + }} + QLineEdit:disabled, QSpinBox:disabled, QDoubleSpinBox:disabled, QTextEdit:disabled, QPlainTextEdit:disabled {{ + background-color: transparent; + color: {disabled_text}; + }} + QToolButton, QPushButton, QComboBox {{ + background: {button}; + }} + QToolButton:focus, QPushButton:focus, QComboBox:focus {{ + background: {button_hover}; + }} + QToolButton:hover, QPushButton:hover, QComboBox:hover {{ + background: {button_hover}; + }} + QToolButton:disabled, QPushButton:disabled, QComboBox:disabled {{ + background: {button_disabled}; + color: #606060; + }} + QComboBox QAbstractItemView {{ + background-color: palette(Base); + }} + QCheckBox::indicator {{ + width: 16px; + height: 16px; + background-color: palette(Base); + border: 1px solid {checkbox_frame}; + border-radius: 4px; + }} + QCheckBox::indicator:hover {{ + border-color: {checkbox_hover}; + }} + QCheckBox::indicator:checked {{ + image: url(resources/icons/{checkbox_glyph}); + background-color: {checkbox}; + border: 1px solid {checkbox}; + }} + QCheckBox::indicator:checked:hover {{ + background-color: {checkbox_hover}; + border: 1px solid {checkbox_hover}; + }} + QCheckBox::indicator:disabled {{ + background-color: {disabled}; + border-color: {button_disabled}; + }} + QCheckBox::indicator:checked:disabled {{ + background-color: {disabled}; + border-color: {button_disabled}; + }} + """ + + app.setPalette(palette) + app.setStyleSheet(style_sheet.format(**colors)) + + # Apply current color scheme + current_scheme = app.styleHints().colorScheme() + apply_palette(current_scheme) + + # Signal for live updates + def on_color_scheme_changed(new_scheme): + apply_palette(new_scheme) + + app.styleHints().colorSchemeChanged.connect(on_color_scheme_changed) return app diff --git a/modules/util/ui/pyside6_validation.py b/modules/util/ui/pyside6_validation.py index 0e5294c68..1c4c344d6 100644 --- a/modules/util/ui/pyside6_validation.py +++ b/modules/util/ui/pyside6_validation.py @@ -40,7 +40,7 @@ def __init__( self._debounce.timeout.connect(self._on_debounce_fire) def _apply_error(self) -> None: - self.component.setStyleSheet(f"border: 1px solid {ERROR_BORDER_COLOR};") + self.component.setStyleSheet(f"border-color: {ERROR_BORDER_COLOR}") def _clear_error(self) -> None: self.component.setStyleSheet(self._original_style) diff --git a/resources/icons/checkbox_dark.png b/resources/icons/checkbox_dark.png new file mode 100644 index 000000000..b1b297922 Binary files /dev/null and b/resources/icons/checkbox_dark.png differ diff --git a/resources/icons/checkbox_light.png b/resources/icons/checkbox_light.png new file mode 100644 index 000000000..c85ba09d2 Binary files /dev/null and b/resources/icons/checkbox_light.png differ