diff --git a/modules/ui/BaseTopBarView.py b/modules/ui/BaseTopBarView.py index fec0372ca..c9af32808 100644 --- a/modules/ui/BaseTopBarView.py +++ b/modules/ui/BaseTopBarView.py @@ -1,3 +1,4 @@ +import os from abc import abstractmethod from collections.abc import Callable @@ -7,6 +8,16 @@ from modules.util.optimizer_util import change_optimizer +def preset_name_from_filename(filename: str) -> str | None: + # The name we show in the top bar. A preset or config is shown by its file + # name without the ".json" ending. "#.json" is the auto-saved "last session" + # file, not a real preset, so we show nothing for it. + basename = os.path.basename(filename) + if basename == "#.json": + return None + return os.path.splitext(basename)[0] + + class BaseTopBarView: def __init__(self, components): self.components = components @@ -27,6 +38,17 @@ def _show_save_dialog(self, initial_dir: str, callback): def _show_open_dialog(self, initial_dir: str, callback): pass + # --- preset-name display hooks ----------------------------------------- + # No-ops by default. The PySide6 top bar overrides them to show the loaded + # preset/config name on the "Load Preset" button; the Ctk top bar leaves + # the button showing its static text. + + def _init_preset_display(self): + pass + + def _set_preset_text(self, text: str): + pass + def build( self, frame, @@ -53,13 +75,18 @@ def build( self.training_method = None + # name of the preset/config currently loaded (None = nothing to show) + self.current_preset_name = None + # title self.components.app_title(self.frame, 0, 0) - # preset picker: model type -> presets for that type - self.components.preset_menu_button( + # preset picker: the button shows the loaded preset/config name and + # opens the "model type -> presets" menu when clicked + self.preset_button = self.components.preset_menu_button( self.frame, 0, 1, "Load Preset", self.preset_tree, self.__load_current_config, sticky="vew", ) + self._init_preset_display() # load config button self.components.button(self.frame, 0, 2, "Load config", self.__load_config, @@ -115,7 +142,13 @@ def __load_config(self): self._show_open_dialog("training_configs", self.__load_current_config) def __save_config(self): - self._show_save_dialog("training_configs", self.controller.save_config_to_path) + # after the file is written, remember it as the current preset so the + # name shows in the top bar and the "changed" marker clears + def save_and_remember(path): + self.controller.save_config_to_path(path) + self.__set_current_preset(path) + + self._show_save_dialog("training_configs", save_and_remember) def __load_current_config(self, filename): loaded_config = self.controller.load_config_from_file(filename) @@ -127,8 +160,15 @@ def __load_current_config(self, filename): optimizer_config = change_optimizer(self.controller.train_config) self.ui_state.get_var("optimizer").update(optimizer_config) + self.__set_current_preset(filename) + self.load_preset_callback() + def __set_current_preset(self, filename): + # record which preset/config is now loaded, then update the label + self.current_preset_name = preset_name_from_filename(filename) + self._set_preset_text(self.current_preset_name or "") + def __remove_config(self): # TODO pass diff --git a/modules/ui/PySide6TopBarView.py b/modules/ui/PySide6TopBarView.py index 68037fb73..257a165d6 100644 --- a/modules/ui/PySide6TopBarView.py +++ b/modules/ui/PySide6TopBarView.py @@ -36,6 +36,15 @@ def __init__( def _setup_frame_column_weight(self): pyside6_components._layout(self.frame).setColumnStretch(5, 1) + def _init_preset_display(self): + # the "Load Preset" button doubles as the display; a tooltip keeps it + # obvious that it opens the preset menu even when it shows a preset name + self.preset_button.setToolTip("Load a preset") + + def _set_preset_text(self, text: str): + # empty means nothing is loaded: fall back to the button's default label + self.preset_button.setText(text or "Load Preset") + def _forget_dropdown(self, widget): lo = pyside6_components._layout(self.frame) lo.removeWidget(widget)