Skip to content

Decouple CTK from UI logic; lay groundwork for Qt6#1445

Merged
dxqb merged 34 commits into
Nerogar:mergefrom
dxqb:ctk_abstraction
Jul 1, 2026
Merged

Decouple CTK from UI logic; lay groundwork for Qt6#1445
dxqb merged 34 commits into
Nerogar:mergefrom
dxqb:ctk_abstraction

Conversation

@dxqb

@dxqb dxqb commented May 10, 2026

Copy link
Copy Markdown
Collaborator

All ~25 UI files in modules/ui/ mixed CustomTkinter widget construction with business logic, making it impossible to add a second toolkit without a full rewrite.

This PR applies a Controller / Base View / CTK View split to every window and tab:

Controller — owns all application logic with no UI toolkit dependency. For TrainUI, TrainUIController is what calls start_training(), stop_training(), handles progress callbacks, and opens tool windows.

Base View — toolkit-neutral widget layout. Receives a components namespace at construction and creates the toolkit-neutral layout using self.components.label(...), self.components.options(...) etc.

CTK View — inherits from both ctk.CTk and the base view. Creates CTK frames, and handles everything that cannot be toolkit-neutral.

No behavior changes intended. Method bodies moved verbatim where possible. Labels, tooltips, and option values untouched.

The pattern is similar to #1164 proposed by @HashakGik (but without the Model/Controller abstraction).
However, the git diffs are relative to the original files to enable human review and git tracability, hopefully enabling such a large refactor without introducing bugs.

dxqb and others added 2 commits May 10, 2026 14:11
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dxqb dxqb mentioned this pull request May 10, 2026
2 tasks
@dxqb dxqb requested a review from O-J1 May 10, 2026 12:46
@dxqb

dxqb commented May 10, 2026

Copy link
Copy Markdown
Collaborator Author

All of this code is written by Claude Code, but with the goal to keep it human-reviewable and maintainable. I have reviewed lots of it.

@Koratahiu could you please review this refactor on the parts you have written originally? Optimizer parameters window, Muon Aux window, etc.
@O-J1 could you please do the same? Especially those parts that are less understood by myself, such as input validation. Are you happy with the split between validation.py and ctk_validation.py?
@efhosci could you please review this based on the video tools, statistics and other code you've written? I had to refactor video tools quite a bit to follow the pattern. I tested statistics but not video tools.

@efhosci

efhosci commented May 10, 2026

Copy link
Copy Markdown
Contributor

Tested the video tool and it looks good, only noticed one minor issue where the "..." button is overlapping the end of the text input field for file selection. That probably just needs a change to the column width or padding. Otherwise all the download and extract functions seem to work the same as before. The statistics and image preview/augmentation stuff all seems good too. May go through the files in more detail later.

@dxqb

This comment was marked as resolved.

… 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>
@dxqb

dxqb commented May 11, 2026

Copy link
Copy Markdown
Collaborator Author

Tested the video tool and it looks good, only noticed one minor issue where the "..." button is overlapping the end of the text input field for file selection. That probably just needs a change to the column width or padding. Otherwise all the download and extract functions seem to work the same as before. The statistics and image preview/augmentation stuff all seems good too. May go through the files in more detail later.

fixed by simplifying: there is a path_entry component already that can be used. you don't have to create an entry box and a button that opens file selection dialog. behaviour might have slightly changed, but it should only have improved: for example now there is input validation (a path that doesn't exist is marked red). please re-test anyway.

@efhosci

efhosci commented May 12, 2026

Copy link
Copy Markdown
Contributor

Okay looks good now, thanks! Confirmed input validation works, invalid files are highlighted red. It looks like there are also a couple fields in the "Generate Captions" and "Generate Masks" windows that could be changed to use path_entry in a similar way, I think one of those was what I used as a reference for this originally.

@dxqb

This comment was marked as resolved.

dxqb and others added 11 commits May 15, 2026 11:40
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>
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>
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>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eep ctk_abstraction for UI conflicts

- Apply LoKr and Scaled OFT additions to BaseLoraTabView/LoraTabController
- Apply ETA session-tracking fix to TrainUIController
- Apply local row counter fix to BaseTrainingTabView.__create_text_encoder_frame
- Keep both supported_caption_extensions and json_path_modifier in path_util

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Was sitting on the same row at columns 3-4; now on its own row directly
below the toggle for clearer visual grouping.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Was sitting on the same row at columns 3-4; now on its own row directly
below the toggle for clearer visual grouping.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dxqb dxqb added the preview merged in the preview branch label May 29, 2026
dxqb added a commit to TheForgotten69/OneTrainer that referenced this pull request Jun 3, 2026
dxqb and others added 3 commits June 4, 2026 23:00
dxqb added a commit that referenced this pull request Jun 4, 2026
Comment thread modules/util/ui/validation.py

@O-J1 O-J1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed validation and put an ask re: adding tests?

dxqb and others added 2 commits June 13, 2026 11:01
…erge

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
# Conflicts:
#	modules/ui/BaseConceptWindowView.py
#	modules/ui/CtkConceptWindowView.py
dxqb added a commit that referenced this pull request Jun 14, 2026
@dxqb dxqb force-pushed the ctk_abstraction branch from f466854 to aee3609 Compare June 19, 2026 07:12
dxqb added a commit that referenced this pull request Jun 19, 2026
dxqb added 3 commits June 19, 2026 20:27
# Conflicts:
#	modules/ui/BaseConceptWindowView.py
#	modules/ui/BaseTimestepDistributionWindowView.py
#	modules/ui/BaseTrainingTabView.py
#	modules/ui/CtkConceptWindowView.py
#	modules/ui/CtkTimestepDistributionWindowView.py
#	modules/ui/CtkTrainingTabView.py
@dxqb

dxqb commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator Author

grafik grafik

Will not port dataset tools. It's a tool that isn't used very much, and can still be used using the Ctk UI for those who need it:
grafik

dxqb and others added 10 commits June 26, 2026 20:50
previous_image/next_image called self.view.switch_image, but switch_image
lives on the controller, not the view; arrow keys raised AttributeError.
Also convert the _make_svd_frames docstring to a # comment.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… split

The Base/Ctk/Controller refactor deleted modules/ui/CaptionUI.py,
ConvertModelUI.py and VideoToolUI.py, but the three entry scripts still
imported them, crashing on launch with ModuleNotFoundError. Wire each script
through the new controller + create_window(parent, view_cls) pattern (as
TrainUIController already does internally).

The new views are CTkToplevel, not CTk roots, so launch them under a hidden
CTk root and wait_window on the toplevel to avoid a stray empty "tk" window
and to exit cleanly on close.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The ctk view refactor moved ConceptWindow.__download_dataset into the new
ConceptWindowController.download_dataset from a base predating PR Nerogar#1524, which
silently reverted that PR's fix: the method again called
huggingface_hub.login(token=..., new_session=False) with no empty-token guard.

new_session was removed from login() in huggingface-hub 1.16, so every call
raised TypeError, swallowed by the surrounding except, so snapshot_download
never ran and dataset download was fully broken. Re-apply Nerogar#1524 at the new
location: only login when a token is configured, and drop new_session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Base/Ctk refactor renamed UIState to BaseUIState (UIState.py) plus
CtkUIState (CtkUIState.py) and updated validation.py, but missed the ctk
sibling: ctk_validation.py still imported the now-nonexistent UIState under
TYPE_CHECKING. Point it at CtkUIState, the concrete state actually passed to
these ctk validators (mirroring validation.py -> BaseUIState).

Invisible to ruff and the runtime (used in a string annotation, under
TYPE_CHECKING, with future annotations), so only a type checker would flag it.

Co-Authored-By: Claude Opus 4.8 <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>
…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>
@dxqb

dxqb commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator Author

code-reviewed as best as I can, but with this PR I'll have to rely on testing and AI partially. It wouldn't be possible otherwise

@dxqb dxqb added the merging last steps before merge label Jun 29, 2026
This was referenced Jul 1, 2026
@dxqb dxqb deleted the branch Nerogar:merge July 1, 2026 04:16
@dxqb dxqb closed this Jul 1, 2026
@dxqb dxqb reopened this Jul 1, 2026
@dxqb dxqb changed the base branch from ctk_copy to merge July 1, 2026 04:17
…bstraction

Nerogar/merge received PR Nerogar#1564 as a squash-merge with tree identical
to ctk_copy's tip. ctk_abstraction already contains all of ctk_copy's
real commits, so -X ours is safe here: there is no content on
Nerogar/merge beyond what's already in ctk_abstraction.

# Conflicts:
#	modules/ui/BaseAdditionalEmbeddingsTabView.py
#	modules/ui/BaseCaptionUIView.py
#	modules/ui/BaseCloudTabView.py
#	modules/ui/BaseConceptTabView.py
#	modules/ui/BaseConceptWindowView.py
#	modules/ui/BaseConvertModelUIView.py
#	modules/ui/BaseGenerateCaptionsWindowView.py
#	modules/ui/BaseGenerateMasksWindowView.py
#	modules/ui/BaseLoraTabView.py
#	modules/ui/BaseModelTabView.py
#	modules/ui/BaseOffloadingWindowView.py
#	modules/ui/BaseProfilingWindowView.py
#	modules/ui/BaseSampleFrameView.py
#	modules/ui/BaseSampleParamsWindowView.py
#	modules/ui/BaseSampleWindowView.py
#	modules/ui/BaseSamplingTabView.py
#	modules/ui/BaseSchedulerParamsWindowView.py
#	modules/ui/BaseTimestepDistributionWindowView.py
#	modules/ui/BaseTopBarView.py
#	modules/ui/BaseTrainUIView.py
#	modules/ui/BaseTrainingTabView.py
#	modules/ui/BaseVideoToolUIView.py
#	modules/ui/CaptionUI.py
#	modules/ui/CaptionUIController.py
#	modules/ui/CtkGenerateCaptionsWindowView.py
#	modules/ui/CtkGenerateMasksWindowView.py
#	modules/ui/CtkSchedulerParamsWindowView.py
#	modules/ui/GenerateCaptionsWindow.py
#	modules/ui/GenerateMasksWindow.py
#	modules/ui/SampleWindow.py
#	modules/ui/SampleWindowController.py
#	modules/ui/SchedulerParamsWindow.py
#	modules/ui/VideoToolUI.py
#	modules/ui/VideoToolUIController.py
@dxqb dxqb merged commit 75c4082 into Nerogar:merge Jul 1, 2026
1 check passed
dxqb added a commit to dxqb/OneTrainer that referenced this pull request Jul 1, 2026
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merging last steps before merge preview merged in the preview branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants