-
-
Notifications
You must be signed in to change notification settings - Fork 310
implement Qt6 views #1446
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
implement Qt6 views #1446
Changes from all commits
Commits
Show all changes
90 commits
Select commit
Hold shift + click to select a range
fe50e8d
feat: split CTK UI into controller/base-view/ctk-view pattern
a88f369
refactor: abstract CTK views and migrate logic to controllers
f7e4159
copy: add PySide6 view copies mirroring CTK structure
a36aa74
feat: implement PySide6 views replacing CTK placeholder copies
ec2ee0a
refactor: replace manual entry+browse-button pairs with path_entry in…
c5b21f0
Merge branch 'ctk_abstraction' into pyside_copy
e19d149
refactor: mirror VideoToolUI path_entry refactor to PySide6VideoToolU…
624ce5d
refactor: remove _create_browse_dir/file_button from PySide6VideoTool…
65bdda2
copy: sync pyside6_components.py with ctk_components.py (allow_video_…
24b1a5e
refactor: add allow_video_files to pyside6_components path_entry
c3961ba
fix: declare undeclared self attrs in Base*View classes
d039871
refactor: move Windows DPI awareness block to CtkTrainUIView
307eef2
Merge branch 'ctk_abstraction' into pyside_copy
29a62a1
chore: sync PySide6 copies from ctk_abstraction merge
7b067a6
refactor: adapt PySide6 views to ctk_abstraction interface changes
1bb5a56
refactor: remove unused __init__ from BaseConceptTabView
06ca10c
Merge branch 'ctk_abstraction' into pyside_copy
98272c8
Merge branch 'pyside_copy' into pyside
a38020a
refactor: move QSizePolicy import to module level in PySide6TrainingT…
9c05ddd
fix: store str repr in options_kv var so UIState enum lookup works
9f42590
Merge branch 'upstream' into ctk_copy
8dd86c6
fix: apply upstream COFT removal to CtkLoraTabView
19b5724
Merge branch 'ctk_copy' into ctk_abstraction
126bc97
Merge branch 'ctk_abstraction' into pyside_copy
81cbe89
Merge branch 'pyside_copy' into pyside
3984c3f
Merge branch 'upstream' into ctk_copy
dxqb c602458
Sync Ctk* copies with upstream changes merged into Base* files
dxqb 23ad1a2
Merge ctk_copy: LoKr support, Scaled OFT, ETA fix, row counter fix; k…
dxqb 9f27035
Merge branch 'ctk_abstraction' into pyside_copy
dxqb 031c511
Merge pyside_copy: upstream LoKr, Scaled OFT, ETA fix, model/training…
dxqb 02f1032
Make Qt UI the default; rename train_ui scripts to _ctk/_qt
dxqb a3a8d2d
Fix over-wide widgets: extend _pack_form to absorb extra horizontal s…
dxqb 51636c5
Hint ctk fallback in unported Qt6 views
dxqb 01cca79
Move Rolling Backup Count under the Rolling Backup toggle
dxqb 647afc2
Move Rolling Backup Count under the Rolling Backup toggle
dxqb 6658da5
Merge branch 'ctk_abstraction' into pyside_copy
dxqb 8c5a7a3
Merge branch 'pyside_copy' into pyside
dxqb e54623e
Remove Windows DPI-awareness hack from PySide6 view
dxqb 440051c
Force light color scheme on PySide6 UI startup
dxqb 408e7e2
Merge branch 'upstream' into ctk_copy
dxqb a0bd3e4
Sync CtkConceptWindowView and ConceptWindowController with Base copy
dxqb 3d90a9e
Merge ctk_copy: transformers 5.9, torch 2.12, compile thread-safety
dxqb dc4fdea
Merge branch 'ctk_abstraction' into pyside_copy
dxqb d64d50b
Merge branch 'pyside_copy' into pyside
dxqb 51a839e
Merge branch 'upstream' into ctk_copy
dxqb fdea894
Sync CtkConceptWindowView with BaseConceptWindowView after upstream m…
dxqb e70bada
Merge branch 'ctk_copy' into ctk_abstraction
dxqb 2030db2
Merge branch 'ctk_abstraction' into pyside_copy
dxqb 7d3d4d8
Merge branch 'pyside_copy' into pyside
dxqb 640c3de
fix: register PySide6 field validators in the shared validator set
dxqb dd9bdbe
Merge branch 'upstream' into ctk_copy
dxqb 1f73b3a
Sync Ctk* views with Base* after merging upstream
dxqb aee3609
Merge branch 'ctk_copy' into ctk_abstraction
dxqb c074a50
Merge branch 'ctk_abstraction' into pyside_copy
dxqb dbc0a3c
Merge branch 'pyside_copy' into pyside
dxqb adb9860
Fix Qt UI bugs: wide tooltips, broken multiline labels, prompt misali…
dxqb 816c17a
Merge upstream: Disable LoRA rank check (false positive on Flux2 LoHA…
dxqb 68b6a86
Merge ctk_copy: Disable LoRA rank check (false positive on Flux2 LoHA…
dxqb 17dd810
Merge ctk_abstraction: Disable LoRA rank check (false positive on Flu…
dxqb 764cff8
Merge pyside_copy: Disable LoRA rank check (false positive on Flux2 L…
dxqb fdb4a3a
Vertically center top bar widgets to the OneTrainer logo
dxqb 5544b4c
Fix top bar vertical alignment and preset dropdown recreation bug
dxqb 826835e
Fix CaptionUI arrow-key navigation and SVD-frame docstring
dxqb 3f676d7
Fix standalone caption/convert/video UI launch scripts after Ctk view…
dxqb 9706006
Fix HF dataset download in concept window after Ctk view split
dxqb 7377fd7
Fix stale UIState type reference in ctk_validation
dxqb 8f70897
Drop customtkinter dependency from toolkit-agnostic Base views
dxqb d52b8d2
Re-apply aux-optimizer empty-state default fix (#1444) after Ctk view…
dxqb 3c06587
Merge ctk_abstraction: fix scripts, validation, concept window, optim…
dxqb cfdd91e
Merge pyside_copy + fix standalone scripts for PySide6 (video_tool, c…
dxqb 9929250
Merge origin/pyside: top bar alignment, Qt UI bug fixes, pyside6_comp…
dxqb 7cc9f65
Remove var-trace leak in pyside6 entry() and switch()
dxqb b2f05b6
Fix orphaned QScrollArea leak in PySide6TrainingTabView
dxqb 79f8ee6
Route configs_dropdown teardown through _destroy_widget
dxqb 411f739
Stop scan thread and close figure on concept window X-button close
dxqb 66bb2c4
Sync concept filter widgets on Clear
dxqb 5ab7a99
Warn on reserved '#' config name instead of silent discard
dxqb 842ff93
Guard standalone sample button against concurrent runs
dxqb 0faf838
Block window close during training and fix Ctrl+C in PySide6 UI
dxqb 0f361c9
Fix sample-window lifecycle: scope <Destroy> binding and defer torch_…
dxqb 57d53bf
Remove PLAN.md from .gitignore
dxqb cffd19e
Merge branch 'ctk_abstraction' into pyside_copy
dxqb 18a06b0
Re-copy PySide6 view after ctk_abstraction merge (sample-window fix)
dxqb a6aa601
Merge branch 'pyside_copy' into pyside
dxqb e6d73af
Fix profiling window crash: route through ProfilingWindowController.c…
dxqb 59758c7
Fix CTk training-method dropdown leak via _forget_dropdown
dxqb 0e084b0
Fix PySide6 widget destruction and validator races from PR #1446 review
dxqb ab3d04b
Match ctk button-width semantics, drop dead Qt undo params, share Qt …
dxqb 57215ae
Merge Nerogar/merge (squashed ctk_copy #1564 + ctk_abstraction #1445)…
dxqb cf83a5e
Bump PySide6 to 6.11.1
dxqb File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,4 +38,4 @@ pixi.toml | |
| train.bat | ||
| debug_report.log | ||
| config_diff.txt | ||
| PLAN.md | ||
| CLAUDE.md | ||
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
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,228 +1,12 @@ | ||
| from tkinter import filedialog | ||
|
|
||
| from modules.ui.BaseCaptionUIView import BaseCaptionUIView | ||
| from modules.ui.CaptionUIController import CaptionUIController | ||
| from modules.ui.CtkGenerateCaptionsWindowView import CtkGenerateCaptionsWindowView | ||
| from modules.ui.CtkGenerateMasksWindowView import CtkGenerateMasksWindowView | ||
| from modules.util.ui import ctk_components | ||
| from modules.util.ui.CtkUIState import CtkUIState | ||
| from modules.util.ui.ui_utils import bind_mousewheel, set_window_icon | ||
|
|
||
| import customtkinter as ctk | ||
| from customtkinter import ScalingTracker, ThemeManager | ||
| from PIL import Image | ||
|
|
||
|
|
||
| class CtkCaptionUIView(BaseCaptionUIView, ctk.CTkToplevel): | ||
| def __init__(self, parent, controller: CaptionUIController, *args, **kwargs): | ||
| ctk.CTkToplevel.__init__(self, parent, *args, **kwargs) | ||
| BaseCaptionUIView.__init__(self, ctk_components) | ||
| self.protocol("WM_DELETE_WINDOW", controller.on_close) | ||
|
|
||
| self.controller = controller | ||
| controller.view = self | ||
| self.config_ui_state = CtkUIState(self, controller.config_ui_data) | ||
| self.enable_mask_editing_var = ctk.BooleanVar() | ||
| self.mask_editing_alpha = None | ||
| self.prompt_var = None | ||
| self.prompt_component = None | ||
| self.image = None | ||
| self.image_label = None | ||
| self.file_list = None | ||
| self.image_labels = [] | ||
|
|
||
| self.title("OneTrainer") | ||
| self.geometry("1280x980") | ||
| self.resizable(False, False) | ||
|
|
||
| self.grid_rowconfigure(0, weight=0) | ||
| self.grid_rowconfigure(1, weight=1) | ||
| self.grid_columnconfigure(0, weight=1) | ||
|
|
||
| top_frame = ctk.CTkFrame(self) | ||
| top_frame.grid(row=0, column=0, sticky="nsew") | ||
| self.build_top_bar(top_frame, controller, self.config_ui_state) | ||
|
|
||
| self.bottom_frame = ctk.CTkFrame(self) | ||
| self.bottom_frame.grid(row=1, column=0, sticky="nsew") | ||
| self.bottom_frame.grid_rowconfigure(0, weight=1) | ||
| self.bottom_frame.grid_columnconfigure(0, weight=0) | ||
| self.bottom_frame.grid_columnconfigure(1, weight=1) | ||
|
|
||
| self.file_list_column(self.bottom_frame) | ||
| self.content_column(self.bottom_frame) | ||
| self.controller.load_directory() | ||
|
|
||
| self.wait_visibility() | ||
| self.focus_set() | ||
| self.after(200, lambda: set_window_icon(self)) | ||
|
|
||
| def file_list_column(self, master): | ||
| if self.file_list is not None: | ||
| self.image_labels = [] | ||
| self.file_list.destroy() | ||
|
|
||
| self.file_list = ctk.CTkScrollableFrame(master, width=300) | ||
| self.file_list.grid(row=0, column=0, sticky="nsew") | ||
|
|
||
| for i, filename in enumerate(self.controller.image_rel_paths): | ||
| def __create_switch_image(index): | ||
| def __switch_image(event): | ||
| self.controller.switch_image(index) | ||
|
|
||
| return __switch_image | ||
|
|
||
| label = ctk.CTkLabel(self.file_list, text=filename) | ||
| label.bind("<Button-1>", __create_switch_image(i)) | ||
|
|
||
| self.image_labels.append(label) | ||
| label.grid(row=i, column=0, padx=5, sticky="nsw") | ||
|
|
||
| def content_column(self, master): | ||
| image = Image.new("RGBA", (512, 512), (0, 0, 0, 0)) | ||
|
|
||
| right_frame = ctk.CTkFrame(master, fg_color="transparent") | ||
| right_frame.grid(row=0, column=1, sticky="nsew") | ||
|
|
||
| right_frame.grid_columnconfigure(4, weight=1) | ||
| right_frame.grid_rowconfigure(1, weight=1) | ||
|
|
||
| self.build_mask_buttons(right_frame) | ||
|
|
||
| # checkbox to enable mask editing | ||
| self.enable_mask_editing_var = ctk.BooleanVar() | ||
| self.enable_mask_editing_var.set(False) | ||
| enable_mask_editing_checkbox = ctk.CTkCheckBox( | ||
| right_frame, text="Enable Mask Editing", variable=self.enable_mask_editing_var, width=50) | ||
| enable_mask_editing_checkbox.grid(row=0, column=2, padx=25, pady=5, sticky="w") | ||
|
|
||
| # mask alpha textbox | ||
| self.mask_editing_alpha = ctk.CTkEntry(master=right_frame, width=40, placeholder_text="1.0") | ||
| self.mask_editing_alpha.insert(0, "1.0") | ||
| self.mask_editing_alpha.grid(row=0, column=3, sticky="e", padx=5, pady=5) | ||
| self.bind_key_events(self.mask_editing_alpha) | ||
|
|
||
| mask_editing_alpha_label = ctk.CTkLabel(right_frame, text="Brush Alpha", width=75) | ||
| mask_editing_alpha_label.grid(row=0, column=4, padx=0, pady=5, sticky="w") | ||
|
|
||
| # image | ||
| self.image = ctk.CTkImage( | ||
| light_image=image, | ||
| size=(self.controller.image_size, self.controller.image_size) | ||
| ) | ||
| self.image_label = ctk.CTkLabel( | ||
| master=right_frame, text="", image=self.image, | ||
| height=self.controller.image_size, width=self.controller.image_size | ||
| ) | ||
| self.image_label.grid(row=1, column=0, columnspan=5, sticky="nsew") | ||
|
|
||
| self.image_label.bind("<Motion>", self.edit_mask) | ||
| self.image_label.bind("<Button-1>", self.edit_mask) | ||
| self.image_label.bind("<Button-3>", self.edit_mask) | ||
| bind_mousewheel(self.image_label, {self.image_label.children["!label"]}, self.draw_mask_radius) | ||
|
|
||
| # prompt | ||
| self.prompt_var = ctk.StringVar() | ||
| self.prompt_component = ctk.CTkEntry(right_frame, textvariable=self.prompt_var) | ||
| self.prompt_component.grid(row=2, column=0, columnspan=5, pady=5, sticky="new") | ||
| self.bind_key_events(self.prompt_component) | ||
| self.prompt_component.focus_set() | ||
|
|
||
| def bind_key_events(self, component): | ||
| component.bind("<Down>", lambda e: self.controller.next_image()) | ||
| component.bind("<Up>", lambda e: self.controller.previous_image()) | ||
| component.bind("<Return>", self.save) | ||
| component.bind("<Control-m>", self.toggle_mask) | ||
| component.bind("<Control-d>", self.draw_mask_editing_mode) | ||
| component.bind("<Control-f>", self.fill_mask_editing_mode) | ||
|
|
||
| def refresh_file_list(self): | ||
| self.file_list_column(self.bottom_frame) | ||
|
|
||
| def focus_prompt(self): | ||
| self.prompt_component.focus_set() | ||
|
|
||
| def on_image_switched(self, old_index, new_index, prompt): | ||
| if len(self.image_labels) > 0 and old_index < len(self.image_labels): | ||
| self.image_labels[old_index].configure( | ||
| text_color=ThemeManager.theme["CTkLabel"]["text_color"]) | ||
| self.image_labels[new_index].configure(text_color="#FF0000") | ||
| self.refresh_image() | ||
| self.prompt_var.set(prompt) | ||
|
|
||
| def on_image_cleared(self): | ||
| image = Image.new("RGB", (512, 512), (0, 0, 0)) | ||
| self.image.configure(light_image=image) | ||
|
|
||
| def refresh_image(self): | ||
| pil_image, size = self.controller.get_display_image() | ||
| self.image.configure(light_image=pil_image, size=size) | ||
|
|
||
| def draw_mask_radius(self, delta, raw_event): | ||
| self.controller.update_mask_draw_radius(delta) | ||
|
|
||
| def edit_mask(self, event): | ||
| if not self.enable_mask_editing_var.get(): | ||
| return | ||
|
|
||
| if event.widget != self.image_label.children["!label"]: | ||
| return | ||
|
|
||
| display_scaling = ScalingTracker.get_window_scaling(self) | ||
|
|
||
| event_x = event.x / display_scaling | ||
| event_y = event.y / display_scaling | ||
|
|
||
| is_right = False | ||
| is_left = False | ||
| if event.state & 0x0100 or event.num == 1: # left mouse button | ||
| is_left = True | ||
| elif event.state & 0x0400 or event.num == 3: # right mouse button | ||
| is_right = True | ||
|
|
||
| try: | ||
| alpha = float(self.mask_editing_alpha.get()) | ||
| except Exception: | ||
| alpha = 1.0 | ||
|
|
||
| self.controller.handle_edit_mask(event_x, event_y, is_left, is_right, alpha) | ||
|
|
||
| def save(self, event): | ||
| self.controller.save(self.prompt_var.get()) | ||
|
|
||
| def draw_mask_editing_mode(self, *args): | ||
| self.controller.set_mask_editing_mode('draw') | ||
|
|
||
| if args: | ||
| # disable default event | ||
| return "break" | ||
| return None | ||
|
|
||
| def fill_mask_editing_mode(self, *args): | ||
| self.controller.set_mask_editing_mode('fill') | ||
|
|
||
| def toggle_mask(self, *args): | ||
| self.controller.toggle_mask() | ||
| self.refresh_image() | ||
|
|
||
| def open_directory(self): | ||
| new_dir = filedialog.askdirectory() | ||
|
|
||
| if new_dir: | ||
| self.controller.dir = new_dir | ||
| self.controller.load_directory(include_subdirectories=self.controller.config_ui_data["include_subdirectories"]) | ||
|
|
||
| def open_mask_window(self): | ||
| self.wait_window(self.controller.open_mask_window(self, CtkGenerateMasksWindowView)) | ||
| self.controller.switch_image(self.controller.current_image_index) | ||
|
|
||
| def open_caption_window(self): | ||
| self.wait_window(self.controller.open_caption_window(self, CtkGenerateCaptionsWindowView)) | ||
| self.controller.switch_image(self.controller.current_image_index) | ||
|
|
||
| def open_in_explorer(self): | ||
| self.controller.open_in_explorer() | ||
|
|
||
| def destroy(self): | ||
| self.controller._release_models() | ||
| super().destroy() | ||
| from PySide6.QtWidgets import QDialog, QLabel, QPushButton, QVBoxLayout | ||
|
|
||
|
|
||
| class PySide6CaptionUIView(QDialog): | ||
| def __init__(self, parent, controller): | ||
| super().__init__(parent) | ||
| self.setWindowTitle("Dataset Tool") | ||
| lo = QVBoxLayout(self) | ||
| lo.addWidget(QLabel("The dataset tool has not been ported to Qt6 yet.\nYou can still use it by launching the CustomTkinter UI: scripts/train_ui_ctk.py")) | ||
| ok = QPushButton("OK") | ||
| ok.clicked.connect(self.accept) | ||
| lo.addWidget(ok) |
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.