-
-
Notifications
You must be signed in to change notification settings - Fork 295
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
Open
dxqb
wants to merge
7
commits into
Nerogar:pyside_copy
Choose a base branch
from
dxqb:pyside
base: pyside_copy
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
implement Qt6 views #1446
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
a36aa74
feat: implement PySide6 views replacing CTK placeholder copies
624ce5d
refactor: remove _create_browse_dir/file_button from PySide6VideoTool…
24b1a5e
refactor: add allow_video_files to pyside6_components path_entry
7b067a6
refactor: adapt PySide6 views to ctk_abstraction interface changes
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
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,5 @@ pixi.toml | |
| train.bat | ||
| debug_report.log | ||
| config_diff.txt | ||
| CLAUDE.md | ||
| PLAN.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
| 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.")) | ||
| ok = QPushButton("OK") | ||
| ok.clicked.connect(self.accept) | ||
| lo.addWidget(ok) |
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,44 +1,41 @@ | ||
|
|
||
|
|
||
| from modules.ui.BaseCloudTabView import BaseCloudTabView | ||
| from modules.ui.CloudTabController import CloudTabController | ||
| from modules.util.ui import ctk_components | ||
| from modules.util.ui import pyside6_components | ||
| from modules.util.ui.pyside6_abc import QtABCMeta | ||
|
|
||
| import customtkinter as ctk | ||
| from PySide6.QtWidgets import QWidget | ||
|
|
||
|
|
||
| class CtkCloudTabView(BaseCloudTabView): | ||
| def __init__(self, master, controller: CloudTabController, ui_state): | ||
| BaseCloudTabView.__init__(self, ctk_components, controller) | ||
| self.master = master | ||
| self.ui_state = ui_state | ||
| class PySide6CloudTabView(BaseCloudTabView, QWidget, metaclass=QtABCMeta): | ||
|
|
||
| self.frame = ctk.CTkScrollableFrame(master, fg_color="transparent") | ||
| self.frame.grid_columnconfigure(0, weight=0) | ||
| self.frame.grid_columnconfigure(1, weight=1) | ||
| self.frame.grid_columnconfigure(2, weight=0) | ||
| self.frame.grid_columnconfigure(3, weight=1) | ||
| self.frame.grid_columnconfigure(4, weight=0) | ||
| self.frame.grid_columnconfigure(5, weight=1) | ||
| def __init__(self, master, controller: CloudTabController, ui_state): | ||
| QWidget.__init__(self, master) | ||
| BaseCloudTabView.__init__(self, pyside6_components, controller) | ||
|
|
||
| self.build_content(self.frame, controller, ui_state) | ||
| self.ui_state = ui_state | ||
|
|
||
| self.frame.pack(fill="both", expand=1) | ||
| scroll, frame = pyside6_components.scrollable_frame(self) | ||
| pyside6_components._layout(self).addWidget(scroll, 0, 0) | ||
| lo = pyside6_components._layout(frame) | ||
| lo.setColumnStretch(1, 1) | ||
| lo.setColumnStretch(3, 1) | ||
| lo.setColumnStretch(5, 1) | ||
| self.frame = frame | ||
|
|
||
| self.build_content(frame, controller, ui_state) | ||
|
|
||
| def _on_set_gpu_types(self): | ||
| self.gpu_types_menu.configure(values=self.controller.get_gpu_types()) | ||
| self.gpu_types_menu.clear() | ||
| self.gpu_types_menu.addItems(self.controller.get_gpu_types()) | ||
|
|
||
| def _make_reattach_frame(self, frame): | ||
| reattach_frame = ctk.CTkFrame(frame, fg_color="transparent") | ||
| reattach_frame.grid(row=9, column=3, padx=0, pady=0, sticky="new") | ||
| reattach_frame.grid_columnconfigure(0, weight=1) | ||
| reattach_frame.grid_columnconfigure(1, weight=1) | ||
| reattach_frame = QWidget(frame) | ||
| pyside6_components._layout(frame).addWidget(reattach_frame, 9, 3) | ||
| pyside6_components._layout(reattach_frame).setColumnStretch(0, 1) | ||
| return reattach_frame | ||
|
|
||
| def _make_create_frame(self, frame): | ||
| create_frame = ctk.CTkFrame(frame, fg_color="transparent") | ||
| create_frame.grid(row=1, column=5, padx=0, pady=0, sticky="new") | ||
| create_frame.grid_columnconfigure(0, weight=0) | ||
| create_frame.grid_columnconfigure(1, weight=1) | ||
| create_frame = QWidget(frame) | ||
| pyside6_components._layout(frame).addWidget(create_frame, 1, 5) | ||
| pyside6_components._layout(create_frame).setColumnStretch(1, 1) | ||
| return create_frame |
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI, you should definitely push your CLAUDE.md and treat it as a development guide for the project :)
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mine is not good enough for general use. I used the first draft claude made because I was still learning to use claude, and then worked with additional PLAN files
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe then a good opportunity would be to describe in CLAUDE.md your programming rules for the repo and the entrypoints of pyside UI that this MR introduce? and then each new MR would build on top of it to describe different branch of the codebase wdyt?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still not experienced enough with Claude Code to do that, but PRs welcome.
I also have second thoughts about encouraging AI PRs. It has obviously become a good tool for a large refactoring like this one, that wouldn't have been possible without AI.
But all code using AI code must be reviewed before submitting it as a PR. That can't become the PR reviewer's job. So you must still already know the codebase to contribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair point. Wdyt of adding a PR template with a checklist contributors have to tick before opening, something like "I've read and understand all changes in this diff" and "I've tested locally"? It won't stop people from lying, but vibe coded mess is easy to spot anyway.
I can include that in the same PR as the CLAUDE.md if it sounds useful.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no strong opinions on this from me. It would be nice to know what stage the code is, like
but I'm not sure how to enforce that. if the templates get too much, nobody follows them
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll have a dab along with the Claude.md/agents.md and potentially some skills useful - thanks for the feedback