From 693c5962248d0cf39f114f4fc4668eba67d43b97 Mon Sep 17 00:00:00 2001 From: Shikiryu Date: Wed, 7 Jun 2023 00:59:45 +0200 Subject: [PATCH] enhance/ui (#28) Fix #24 Reviewed-on: https://git.shikiryu.com/Shikiryu/DrawingTraining/pulls/28 --- main.py | 9 ++++- makefile | 11 +++++- requirements.txt | 3 +- src/element/image.py | 2 +- src/element/toolbar.py | 37 +++++++++--------- src/entity/config.py | 6 +++ src/util.py | 26 +++++++++++++ src/window/app.py | 86 +++++++++++++++++++++++------------------- src/window/image.py | 4 +- src/window/session.py | 73 +++++++++++++++++------------------ 10 files changed, 158 insertions(+), 99 deletions(-) diff --git a/main.py b/main.py index 32f1d1e..f2f9799 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,8 @@ import os import sys -from tkinter import * + +import customtkinter + from src.window.app import App @@ -8,7 +10,10 @@ def main(): if getattr(sys, 'frozen', False): os.chdir(sys._MEIPASS) - root = Tk() + customtkinter.set_appearance_mode("system") + customtkinter.set_default_color_theme("dark-blue") + + root = customtkinter.CTk() root.title("Drawing Training!") root.geometry("300x600") diff --git a/makefile b/makefile index 2c1b4cd..708d95b 100644 --- a/makefile +++ b/makefile @@ -1,5 +1,12 @@ .PHONY: build dist -build: +TOTAL_LOCATION := $(pip show customtkinter | grep Location) +LOCATION=!TOTAL_LOCATION:~0,10! + +build_linux: rm -rf build dist drawingtraining.spec - pyinstaller main.py --onefile -w --hidden-import="PIL._tkinter_finder" -n drawingtraining --add-data "assets:assets" \ No newline at end of file + pyinstaller main.py --onefile -w --hidden-import="PIL._tkinter_finder" -n drawingtraining --add-data "assets:assets" + +build_windows: + rm -rf build dist drawingtraining.spec + pyinstaller main.py --onefile -w --hidden-import="PIL._tkinter_finder" -n drawingtraining --add-data "assets;assets" --add-data "${LOCATION}\customtkinter;customtkinter\" diff --git a/requirements.txt b/requirements.txt index 0721917..b5caefd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ pillow~=9.5.0 pyinstaller -pygame~=2.4.0 \ No newline at end of file +pygame~=2.4.0 +customtkinter~=5.1.3 \ No newline at end of file diff --git a/src/element/image.py b/src/element/image.py index 528f941..d4dad0f 100644 --- a/src/element/image.py +++ b/src/element/image.py @@ -11,7 +11,7 @@ class ImagePlaceholder: self.images = images.copy() self.image_label = Label(self.image_window.window) - self.image_label.bind('', lambda event: self.resize_image(True)) + self.image_label.bind('', lambda event: self.apply_options()) self.image_label.pack(side=TOP, fill=BOTH, expand=1) def display_new_image(self): diff --git a/src/element/toolbar.py b/src/element/toolbar.py index fa270e4..379434e 100644 --- a/src/element/toolbar.py +++ b/src/element/toolbar.py @@ -3,6 +3,7 @@ import os import subprocess import sys from tkinter import * +import customtkinter from src.util import Util @@ -19,32 +20,34 @@ class Toolbar: toolbar = Frame(self.image_window.window, bd=1, relief=RAISED) toolbar.pack(side=BOTTOM, fill=X) - next_button = Button(toolbar, relief=FLAT, compound=LEFT, text="next ➡️", command=self.image_window.next_image) + next_button = customtkinter.CTkButton(toolbar, compound=LEFT, text="next ➡️", + command=self.image_window.next_image) next_button.pack(side=LEFT, padx=0, pady=0) - self.bw_button = Button(toolbar, relief=FLAT, compound=LEFT, text="black&white", - command=self.toggle_black_white) + self.bw_button = customtkinter.CTkButton(toolbar, compound=LEFT, text="black&white", + command=self.toggle_black_white) self.bw_button.pack(side=LEFT, padx=0, pady=0) - self.mirror_button = Button(toolbar, relief=FLAT, compound=LEFT, command=self.toggle_mirror, - text="mirror") + self.mirror_button = customtkinter.CTkButton(toolbar, compound=LEFT, command=self.toggle_mirror, + text="mirror") self.mirror_button.pack(side=LEFT, padx=0, pady=0) - open_folder_button = Button(toolbar, relief=FLAT, compound=LEFT, command=self.open_folder, text="open folder") + open_folder_button = customtkinter.CTkButton(toolbar, compound=LEFT, command=self.open_folder, + text="open folder") open_folder_button.pack(side=LEFT, padx=0, pady=0) - self.always_on_top_button = Button(toolbar, relief=FLAT, compound=LEFT, command=self.toggle_always_on_top, - text="always on top") + self.always_on_top_button = customtkinter.CTkButton(toolbar, compound=LEFT, command=self.toggle_always_on_top, + text="always on top") self.always_on_top_button.pack(side=LEFT, padx=0, pady=0) - self.fullscreen_button = Button(toolbar, relief=FLAT, compound=LEFT, command=self.toggle_fullscreen, - text="fullscreen") + self.fullscreen_button = customtkinter.CTkButton(toolbar, compound=LEFT, command=self.toggle_fullscreen, + text="fullscreen") self.fullscreen_button.pack(side=LEFT, padx=0, pady=0) - timer_button = Button(toolbar, relief=FLAT, compound=LEFT, command=self.toggle_timer, text="timer") + timer_button = customtkinter.CTkButton(toolbar, compound=LEFT, command=self.toggle_timer, text="timer") timer_button.pack(side=LEFT, padx=0, pady=0) - self.timer_label = Label(toolbar, text=self.timer) + self.timer_label = customtkinter.CTkLabel(toolbar, text=str(self.timer), text_color=Util.get_default_button_color()) self.timer_label.pack(side=RIGHT, ipadx=20) def display_new_timer(self): @@ -69,13 +72,13 @@ class Toolbar: def toggle_black_white(self): self.image_window.option["bw"] = not self.image_window.option["bw"] - self.bw_button.config(bg="blue" if self.image_window.option["bw"] else "grey85") + self.bw_button.configure(fg_color=Util.get_default_active_button_color() if self.image_window.option["bw"] else Util.get_default_button_color()) self.image_window.image.apply_options() def toggle_mirror(self): self.image_window.option["mirror"] = not self.image_window.option["mirror"] - self.mirror_button.config(bg="blue" if self.image_window.option["mirror"] else "grey85") + self.mirror_button.configure(fg_color=Util.get_default_active_button_color() if self.image_window.option["mirror"] else Util.get_default_button_color()) self.image_window.image.apply_options() @@ -96,10 +99,10 @@ class Toolbar: def toggle_always_on_top(self): self.image_window.option["always_on_top"] = not self.image_window.option["always_on_top"] if self.image_window.option["always_on_top"]: - self.always_on_top_button.config(bg="blue") + self.always_on_top_button.configure(fg_color=Util.get_default_active_button_color()) self.stay_on_top() else: - self.always_on_top_button.config(bg="grey85") + self.always_on_top_button.configure(fg_color=Util.get_default_active_button_color()) self.image_window.window.after_cancel(self.always_on_top_check) def stay_on_top(self): @@ -109,6 +112,6 @@ class Toolbar: def toggle_fullscreen(self): self.image_window.option["fullscreen"] = not self.image_window.option["fullscreen"] - self.fullscreen_button.config(bg="blue" if self.image_window.option["fullscreen"] else "grey85") + self.fullscreen_button.configure(fg_color=Util.get_default_active_button_color() if self.image_window.option["fullscreen"] else Util.get_default_button_color()) self.image_window.window.attributes("-fullscreen", self.image_window.option["fullscreen"]) diff --git a/src/entity/config.py b/src/entity/config.py index b90ae42..1590cfa 100644 --- a/src/entity/config.py +++ b/src/entity/config.py @@ -47,3 +47,9 @@ class Config(object): if config_var not in Config._CONFIG: raise Exception(f"Please set the {config_var} variable in the config file {Config._CONFIG_FILE}") return Config._CONFIG[config_var] + + @staticmethod + def set_config_var(name, value): + assert Config._CONFIG + Config._CONFIG[name] = value + return Config diff --git a/src/util.py b/src/util.py index 327d844..31ffe60 100644 --- a/src/util.py +++ b/src/util.py @@ -22,3 +22,29 @@ class Util: time += str(seconds) + "s" return time + + @staticmethod + def format_time_to_seconds(time: str) -> int: + seconds = 0 + + hours = time.split("h") + if len(hours) > 1: + seconds += (60 * 60 * int(hours.pop(0))) + time = hours.pop(0) + + minutes = time.split("m") + if len(minutes) > 1: + seconds += (60 * int(minutes.pop(0))) + time = minutes.pop(0) + + seconds += int(time[0:-1]) if len(time) > 0 else 0 + + return seconds + + @staticmethod + def get_default_button_color(): + return "#3a7ebf", "#1f538d" + + @staticmethod + def get_default_active_button_color(): + return "gray29", "gray29" diff --git a/src/window/app.py b/src/window/app.py index 1b435a0..c04c44d 100644 --- a/src/window/app.py +++ b/src/window/app.py @@ -1,10 +1,9 @@ +from pathlib import Path from tkinter import * from tkinter import filedialog -from pathlib import Path - +import customtkinter from src.entity.config import Config from src.util import Util -from functools import partial from src.window.image import ImageWindow from src.window.session import SessionWindow @@ -22,41 +21,49 @@ class App: self.selected_folder = "" self.found_images = [] self.timer = 0 - self.title = Label(root, text="Drawing Training") + self.title = customtkinter.CTkLabel(root, text="Drawing Training") self.title.pack() - self.folder_selector = Button(root, text="Select a folder", command=self.select_folder) + self.folder_selector = customtkinter.CTkButton(root, text="Select a folder", command=self.select_folder) self.folder_selector.pack() - self.folder_name = Label(root, text="Folder : " + self.selected_folder) + self.folder_name = customtkinter.CTkLabel(root, text="Folder : " + self.selected_folder) self.folder_name.pack() - self.images_len = Label(root, text="Found : " + str(len(self.found_images))) + self.images_len = customtkinter.CTkLabel(root, text="Found : " + str(len(self.found_images))) self.images_len.pack() self.button_frame = Frame(root) self.button_frame.pack(fill=X) - timers = [30, 45, 60, 120, 300, 600] - self.buttons = [] - i = 0 - for i in timers: - t = i - new_button = Button(self.button_frame, text=Util.format_seconds(t), - command=partial(self.set_timer_in_seconds, t)) - self.buttons.append(new_button) - self.button_frame.columnconfigure(i, weight=1) - new_button.grid(row=0, column=i, sticky=W + E) - i += 1 + timers = [ + Util.format_seconds(30), + Util.format_seconds(45), + Util.format_seconds(60), + Util.format_seconds(120), + Util.format_seconds(300), + Util.format_seconds(600), + "Custom" + ] + self.buttons = customtkinter.CTkSegmentedButton( + root, values=timers, + command=self.set_timer_in_seconds + ) + self.buttons.pack(fill=X) - new_button = Button(self.button_frame, text="Custom", command=self.custom_session) - self.buttons.append(new_button) - self.button_frame.columnconfigure(i, weight=1) - new_button.grid(row=0, column=i, sticky=W + E) - - self.launch_button = Button(root, text="Let's draw!", command=self.lets_draw, state="disabled") + self.launch_button = customtkinter.CTkButton(root, text="Let's draw!", command=self.lets_draw, state="disabled") self.launch_button.pack(side="bottom") + self.read_config() + + def read_config(self): + try: + self.selected_folder = Config.get_required_config_var("default_folder") + self.folder_name.configure(text="Folder : " + self.selected_folder) + self.find_images_in_folder() + except Exception: + pass + def lets_draw(self): self.image_window = ImageWindow(self) self.image_window.lets_draw(self.found_images.copy(), self.timer) @@ -67,32 +74,33 @@ class App: def select_folder(self): self.selected_folder = filedialog.askdirectory() - self.found_images = list( - p.resolve() for p in Path(self.selected_folder).glob("**/*") if p.suffix in {".jpg", ".gif", ".png"}) - self.folder_name.config(text="Folder : " + self.selected_folder) - self.images_len.config(text="Found : " + str(len(self.found_images))) + self.folder_name.configure(text="Folder : " + self.selected_folder) + Config.set_config_var("default_folder", self.selected_folder).save() + self.find_images_in_folder() self.check_lets_draw() + def find_images_in_folder(self): + if Path(self.selected_folder).exists(): + self.found_images = list( + p.resolve() for p in Path(self.selected_folder).glob("**/*") if p.suffix in {".jpg", ".gif", ".png"} + ) + self.images_len.configure(text="Found : " + str(len(self.found_images))) + def check_lets_draw(self): if self.selected_folder != "" and len(self.found_images) > 0 and (self.timer != 0 or self.custom): - self.launch_button.config(state="normal") + self.launch_button.configure(state="normal") else: - self.launch_button.config(state="disabled") + self.launch_button.configure(state="disabled") def set_timer_in_seconds(self, user_data): self.custom = False - self.timer = user_data - for button in self.buttons: - if button['text'] == Util.format_seconds(self.timer): - button.config(bg="blue") - else: - button.config(bg="gray85") + if user_data == "Custom": + return self.custom_session() + + self.timer = Util.format_time_to_seconds(user_data) self.check_lets_draw() def set_custom(self, list_in_session): self.custom = True self.list_in_session = list_in_session self.check_lets_draw() - for button in self.buttons: - button.config(bg="gray85") - self.buttons[-1].config(bg="blue") diff --git a/src/window/image.py b/src/window/image.py index 2c654d2..8ee7cf2 100644 --- a/src/window/image.py +++ b/src/window/image.py @@ -1,5 +1,7 @@ import random from tkinter import * + +from customtkinter import CTkToplevel from pygame import mixer from src.element.image import ImagePlaceholder from src.element.toolbar import Toolbar @@ -10,7 +12,7 @@ class ImageWindow: self.current_original_image = None self.app = app - self.window = Toplevel(app.root) + self.window = CTkToplevel(app.root) self.window.title("Image") self.window.geometry("1280x1024") self.window.protocol("WM_DELETE_WINDOW", self.on_closing) diff --git a/src/window/session.py b/src/window/session.py index 58298a9..a0c8da1 100644 --- a/src/window/session.py +++ b/src/window/session.py @@ -1,6 +1,5 @@ from functools import partial -from tkinter import * - +from customtkinter import * from src.entity.config import Config from src.entity.session.drawing import DrawingElement from src.util import Util @@ -18,16 +17,16 @@ class SessionWindow: self.add_button = None self.app = app - self.window = Toplevel(app.root) + self.window = CTkToplevel(app.root) self.window.title("Custom session") self.window.geometry("600x600") self.window.protocol("WM_DELETE_WINDOW", self.save_on_closing) - self.left_column = Frame(self.window, width=300, height=600) - self.right_column = Frame(self.window, width=300, height=600) + self.left_column = CTkFrame(self.window, width=300, height=60) + self.right_column = CTkFrame(self.window, width=300, height=600) - self.left_column.grid(row=0, column=0, sticky="ns") - self.right_column.grid(row=0, column=1, sticky="ns") + self.left_column.pack(padx=10, pady=10, side=LEFT, fill=BOTH, expand=True) + self.right_column.pack(padx=10, pady=10, side=LEFT, fill=BOTH, expand=True) self.list_in_session = [] @@ -40,30 +39,32 @@ class SessionWindow: def update_session_list(self): self.reset_list() for i, session in enumerate(self.list_in_session): - bg = "blue" if i == self.selected_element_index else "grey85" - new_session = Button(self.left_column, - text=session.number_of_drawings + " drawings of " + session.timer + "s each", - command=partial(self.edit_element, i), - bg=bg - ) + new_session = CTkButton(self.left_column, + text=session.number_of_drawings + " drawings of " + session.timer + "s each", + command=partial(self.edit_element, i), + fg_color=(Util.get_default_button_color() if i == self.selected_element_index else Util.get_default_active_button_color()) + ) new_session.grid(row=i, column=0) - button_frame = Frame(self.left_column) - self.add_button = Button(button_frame, text="+", command=self.add_element) - self.add_button.pack(side=LEFT, padx=0, pady=0) + buttons = CTkSegmentedButton( + self.left_column, values=["+", "-", "^", "v", "save"], + command=self.button_command + ) + buttons.grid() - self.delete_button = Button(button_frame, text="-", command=self.remove_element) - self.delete_button.pack(side=LEFT, padx=0, pady=0) - - self.up_button = Button(button_frame, text="^", command=self.up_element) - self.up_button.pack(side=LEFT, padx=0, pady=0) - - self.down_button = Button(button_frame, text="v", command=self.down_element) - self.down_button.pack(side=LEFT, padx=0, pady=0) - - save_session_button = Button(button_frame, text="v", command=self.save_session) - save_session_button.pack(side=LEFT, padx=0, pady=0) - button_frame.grid() + def button_command(self, command): + if command == "+": + self.add_element() + elif command == "-": + self.remove_element() + elif command == "^": + self.up_element() + elif command == "v": + self.down_element() + elif command == "save": + self.save_session() + else: + print("unknown command") def reset_element(self): for widget in self.right_column.winfo_children(): @@ -82,34 +83,34 @@ class SessionWindow: else: element = DrawingElement() - number_of_drawings_label = Label(self.right_column, text="Number of drawings") + number_of_drawings_label = CTkLabel(self.right_column, text="Number of drawings") number_of_drawings_label.pack() - self.number_of_drawings_input = Entry(self.right_column) + self.number_of_drawings_input = CTkEntry(self.right_column) self.number_of_drawings_input.insert(INSERT, element.number_of_drawings) self.number_of_drawings_input.bind("", lambda e: self.update_local_timer(e)) self.number_of_drawings_input.pack() self.number_of_drawings_input.focus_set() - timer_label = Label(self.right_column, text="Time per drawing in seconds") + timer_label = CTkLabel(self.right_column, text="Time per drawing in seconds") timer_label.pack() - self.timer_input = Entry(self.right_column) + self.timer_input = CTkEntry(self.right_column) self.timer_input.insert(INSERT, element.timer) self.timer_input.bind("", lambda e: self.update_local_timer(e)) self.timer_input.pack() - total_row_time_label = Label(self.right_column, text="Total row time") + total_row_time_label = CTkLabel(self.right_column, text="Total row time") total_row_time_label.pack() - self.total_row_time_value_label = Label(self.right_column, text="") + self.total_row_time_value_label = CTkLabel(self.right_column, text="") self.total_row_time_value_label.pack() self.update_local_timer() - save_button = Button(self.right_column, text="Save", command=self.save) + save_button = CTkButton(self.right_column, text="Save", command=self.save) save_button.pack() def update_local_timer(self, event=None): - self.total_row_time_value_label.config( + self.total_row_time_value_label.configure( text=Util.format_seconds(int(self.timer_input.get()) * int(self.number_of_drawings_input.get())) )