implement Qt6 views#1446
Merged
Merged
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Collaborator
Author
dxqb
commented
May 11, 2026
… VideoToolUI Removes abstract _create_browse_dir_button/_create_browse_file_button from BaseVideoToolUIView and uses the combined path_entry component instead, fixing widget alignment issues. Adds allow_video_files flag to path_entry. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…IView Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…UIView Now handled by path_entry in the base class. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…files) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Base view concrete methods were accessing self.controller, self.ui_state, and toolkit-specific action methods that were only set by CTK subclass __init__ after the base __init__ call, with no enforcement in the base. - BaseTrainUIView: add controller/ui_state as constructor params; fix sync_cloud_secrets to use controller.train_config; add @AbstractMethod for export_training, generate_debug_package, open_profiling_tool - CtkTrainUIView: reorder __init__ to create deps before base init call; replace self.train_config with self.controller.train_config - BaseCloudTabView: add controller as constructor param - CtkCloudTabView: pass controller to base __init__, drop redundant assignment - BaseCaptionUIView: add ABC + @AbstractMethod for 6 action callbacks - BaseConceptTabView: remove concrete _update_filters() (accessed CTK vars); add ConceptConfig import; add concept: ConceptConfig param to BaseConceptWidgetView.__init__ - CtkConceptTabView: implement _update_filters(); pass concept to base init - BaseConceptWindowView: initialize bucket_ax/text_color/canvas to None - BaseTrainingTabView: replace callbacks dict with 6 @AbstractMethod declarations; restore_optimizer_config(variable: str) matches controller - CtkTrainingTabView: implement all 6 abstract methods directly Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The ctypes DPI awareness call is toolkit-specific (fixes CTK transparency on Windows monitor changes). It already exists in CtkTrainUIView.py and has no place in the toolkit-agnostic controller. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PySide6TrainUIView: create train_config/ui_state/controller before base init so they can be passed as constructor params; replace self.train_config with self.controller.train_config throughout - PySide6CloudTabView: pass controller to BaseCloudTabView.__init__; remove now-redundant self.controller assignment - PySide6ConceptTabView: implement _update_filters() (removed from base); pass concept to BaseConceptWidgetView.__init__ - PySide6TrainingTabView: remove callbacks dict; call build() without it; implement 6 abstract methods with logic inlined from private helpers; remove now-unused private helper methods Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
search_var/filter_var/show_disabled_var were stored in the base but never used there after _update_filters() was removed. Subclasses manage them. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…abView Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
UIState's enum trace looks up var_type[string], so the var must hold the string repr of the value, not the value itself. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Mirrors the change from upstream commit e928dda (Remove COFT Nerogar#1447), which removed COFT from LoraTab.py. The merge didn't carry it across the rename to CtkLoraTabView.py. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Brings in upstream changes (remove COFT Nerogar#1447, remove cautious_mask Nerogar#1451, fix aux optimizer defaults Nerogar#1444). Conflicts resolved by keeping the ctk_abstraction refactored versions (self.components, controller delegation). COFT removal applied manually to BaseLoraTabView. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BaseConfigListView and BaseAdditionalEmbeddingsTabView imported customtkinter solely for a `-> ctk.CTkToplevel` return annotation on the abstract open_element_window, the lone toolkit reference in the Base layer (the sibling abstract methods return toolkit objects unannotated). Drop the annotation and the import so the Base layer is genuinely toolkit-free; the concrete Ctk subclasses keep their own CTkToplevel annotation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tk view split The ctk view split copied OptimizerParamsWindow into a controller from a base that predated Nerogar#1444, reverting `if not current_state:` back to `if current_state is None:`. Since muon_adam_config defaults to {} (not None), the empty initial state fell through to from_dict({}), so the Muon/aux-Adam window opened with bare default_values() instead of MUON_AUX_ADAM_DEFAULTS / ADAMW_ADV. Restore the upstream one-line guard. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…izer params Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…onvert_model) video_tool_ui.py and convert_model_ui.py now use QApplication/exec() with their PySide6 views instead of the CTk hidden-root pattern. caption_ui.py stays on CTk until the caption tool is ported to Qt. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…onents updates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
entry() and switch() registered a var trace via add_var_trace() but never removed it on widget destruction, so traces accumulated on long-lived ui_state objects (e.g. the optimizer window's optimizer_ui_state) across rebuilds, keeping destroyed windows alive. Wire destroyed.connect to remove_var_trace, mirroring the existing _unbind_widget cleanup pattern. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
refresh_ui() stored only the inner content QWidget as self.scroll_frame and recreated the QScrollArea as a local, so each model-type/training-method switch left the old empty QScrollArea parented to self and stacked in the grid cell. Track the QScrollArea instead (matching PySide6ModelTabView) so deleteLater() frees the whole subtree on refresh. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
__create_configs_dropdown called QWidget.destroy() directly, which on PySide6 only tears down the native handle and leaves the old combo box in the grid cell. Adding a config (which recreates the dropdown) stacked duplicate combos. Use the _destroy_widget abstraction, which hides and deleteLater()s on PySide6 and stays equivalent to destroy() on Ctk. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PySide6ConceptWindowView had no closeEvent override, so closing via the OS X button skipped _ok(): the matplotlib figure was leaked and the background scan thread kept running. Add a closeEvent that runs a shared _cleanup() (also called from _ok), which sets the controller's cancel_scan_flag to stop the scan and plt.close()s the figure. The scan's call_after updates are already auto-cancelled by Qt via the context-object singleShot overload. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The search box, type combo and show-disabled checkbox were wired one-way (widget -> var), so _reset_filters() reset the vars but left the widgets displaying their old state. Register var -> widget _bind_widget callbacks so resetting the vars (and any other programmatic var change) pushes back to the widgets. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
QInputDialog.getText always closes on OK, so a name starting with '#' (reserved for built-in presets) passed the ok check but failed the prefix guard, silently skipping the save with no feedback. Show a warning so the user knows the name was rejected and can retry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The sample window's button spawned a daemon thread on every click with no guard. For the standalone sampling tool (use_external_model=False) do_sample runs the model synchronously, so rapid clicks raced the lazy model load and started concurrent sampling runs; run it in a thread and disable the button while a sample is in flight, re-enabling in finally on the main thread. For the manual-sample-during-training path (use_external_model=True) do_sample only enqueues a command for the training thread, so call it directly without a thread and keep the button clickable so multiple samples can be queued. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Refuse to close the main window while the training thread is alive, and restore the OS default SIGINT handler so Ctrl+C terminates the process even while blocked inside Qt's event loop. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…gc to close connect_window_closed bound <Destroy> unconditionally, so the callback fired once per descendant widget as the toplevel tore down. Guard on the event widget being the window itself so it fires exactly once. With that in place, open_sampling_tool defers torch_gc to window close via connect_window_closed instead of running it inline at open time, restoring the original 'free GPU memory after the sample window closes' behaviour. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts: # modules/ui/PySide6TrainUIView.py
…reate_window PySide6TrainUIView constructed PySide6ProfilingWindowView directly instead of via create_window(), so controller.view was never set. Any profiling button (dump stack, start/end profiling) then crashed with AttributeError on self.view = None. Mirrors the existing CtkTrainUIView pattern and the other five window controllers (CaptionUIController, VideoToolUIController, etc.) that all rely on create_window() for the same two-phase init. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_forget_dropdown only called grid_forget(), not destroy(), so the old CTkOptionMenu and its var trace stayed alive every time the training-method dropdown was rebuilt on a model-type change. winfo_exists() stayed True for the stale widgets, so their var.trace_add callback kept firing on every later training-method change, multiplying calls to change_training_method_callback (and the resulting full model-tab rebuild) by the number of prior model-type switches. The PySide6 sibling already used deleteLater() correctly; this regression was CTk-only, introduced when __create_training_method was switched from a direct .destroy() call to the shared _forget_dropdown abstraction. destroy() restores winfo_exists() == False for stale dropdowns, which the existing update_var() guard already relies on. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… review - PySide6OptimizerParamsWindowView: use hide()+deleteLater() instead of setParent(None) when rebuilding dynamic optimizer fields, so var-trace cleanup (destroyed signal) actually fires instead of leaking widgets/traces. - PySide6PathValidator.revalidate(): guard against calling into an already-destroyed QLineEdit via a dependent-var trace during teardown. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…app setup - pyside6_components.py/ctk_components.py: replace button()'s silent **kwargs passthrough with an explicit width param; use setMinimumWidth on Qt to match CTkButton's floor-not-cap width behavior (CTkButton never disables grid propagation, so it grows past `width` to fit its label). - pyside6_validation.py: drop the dead max_undo/DEFAULT_MAX_UNDO plumbing from PySide6FieldValidator/PySide6PathValidator; QLineEdit has native undo/redo, unlike tk.Entry which needed the custom UndoHistory. - QtVar.py: switch docstring to a # comment per project convention. - New modules/util/ui/pyside6_util.py: shared create_application() (SIGINT restore, light palette, base stylesheet) and QtABCMeta (merged from the deleted pyside6_abc.py), applied to all three Qt entry scripts (train_ui_qt.py, convert_model_ui.py, video_tool_ui.py) so they're visually and behaviorally consistent. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Merged
…erogar#1445) into pyside Nerogar/merge's tip is a squash-merge of ctk_abstraction, tree-identical to our ctk_abstraction/pyside_copy ancestors already in pyside's history. -X ours plus manual conflict resolution below keeps pyside's tree unchanged.
|
Hey, so did you switch from custom Tkinter to Qt6? I just updated to the latest version and it opened with a light theme, which is burning my eyes :) Is there a way to switch back to a dark theme? Thanks in advance! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Builds on the controller-view abstraction introduced in the previous PR. That PR made all business logic toolkit-independent and defined UI content in toolkit-neutral base views. This PR adds a PySide6 frontend without touching any of that shared code.
Three pieces are needed to make
build_contentrun under Qt:pyside6_components— mirrors thectk_componentsAPI with Qt widgets.self.components.label(...),self.components.options(...)etc. resolve to Qt implementations when a PySide6 view passes this namespace at construction.QtVar/PySide6UIState— Qt equivalent of tkinter variables andUIState.QtVarwraps a value and fires Qt signals on change;PySide6UIStateexposes the same interface controllers and base views expect.PySide6*Viewclasses — inherit from the Qt window/widget base and the sharedBase*View. ForTrainUI,PySide6TrainUIView(QMainWindow, BaseTrainUIView)reusesTrainUIControllerandBaseTrainUIViewunchanged.The PySide6 UI is launched via
./start-ui.sh. The CTK UI is unaffected and is still started using./run-cmd.sh train_ui_ctk.