enhance/ui (#28)

Fix #24

Reviewed-on: #28
This commit is contained in:
Shikiryu 2023-06-07 00:59:45 +02:00
parent a149ab71bb
commit 693c596224
10 changed files with 158 additions and 99 deletions

View File

@ -1,6 +1,8 @@
import os import os
import sys import sys
from tkinter import *
import customtkinter
from src.window.app import App from src.window.app import App
@ -8,7 +10,10 @@ def main():
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
os.chdir(sys._MEIPASS) 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.title("Drawing Training!")
root.geometry("300x600") root.geometry("300x600")

View File

@ -1,5 +1,12 @@
.PHONY: build dist .PHONY: build dist
build: TOTAL_LOCATION := $(pip show customtkinter | grep Location)
LOCATION=!TOTAL_LOCATION:~0,10!
build_linux:
rm -rf build dist drawingtraining.spec rm -rf build dist drawingtraining.spec
pyinstaller main.py --onefile -w --hidden-import="PIL._tkinter_finder" -n drawingtraining --add-data "assets:assets" 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\"

View File

@ -1,3 +1,4 @@
pillow~=9.5.0 pillow~=9.5.0
pyinstaller pyinstaller
pygame~=2.4.0 pygame~=2.4.0
customtkinter~=5.1.3

View File

@ -11,7 +11,7 @@ class ImagePlaceholder:
self.images = images.copy() self.images = images.copy()
self.image_label = Label(self.image_window.window) self.image_label = Label(self.image_window.window)
self.image_label.bind('<Configure>', lambda event: self.resize_image(True)) self.image_label.bind('<Configure>', lambda event: self.apply_options())
self.image_label.pack(side=TOP, fill=BOTH, expand=1) self.image_label.pack(side=TOP, fill=BOTH, expand=1)
def display_new_image(self): def display_new_image(self):

View File

@ -3,6 +3,7 @@ import os
import subprocess import subprocess
import sys import sys
from tkinter import * from tkinter import *
import customtkinter
from src.util import Util from src.util import Util
@ -19,32 +20,34 @@ class Toolbar:
toolbar = Frame(self.image_window.window, bd=1, relief=RAISED) toolbar = Frame(self.image_window.window, bd=1, relief=RAISED)
toolbar.pack(side=BOTTOM, fill=X) 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) next_button.pack(side=LEFT, padx=0, pady=0)
self.bw_button = Button(toolbar, relief=FLAT, compound=LEFT, text="black&white", self.bw_button = customtkinter.CTkButton(toolbar, compound=LEFT, text="black&white",
command=self.toggle_black_white) command=self.toggle_black_white)
self.bw_button.pack(side=LEFT, padx=0, pady=0) self.bw_button.pack(side=LEFT, padx=0, pady=0)
self.mirror_button = Button(toolbar, relief=FLAT, compound=LEFT, command=self.toggle_mirror, self.mirror_button = customtkinter.CTkButton(toolbar, compound=LEFT, command=self.toggle_mirror,
text="mirror") text="mirror")
self.mirror_button.pack(side=LEFT, padx=0, pady=0) 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) 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, self.always_on_top_button = customtkinter.CTkButton(toolbar, compound=LEFT, command=self.toggle_always_on_top,
text="always on top") text="always on top")
self.always_on_top_button.pack(side=LEFT, padx=0, pady=0) 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, self.fullscreen_button = customtkinter.CTkButton(toolbar, compound=LEFT, command=self.toggle_fullscreen,
text="fullscreen") text="fullscreen")
self.fullscreen_button.pack(side=LEFT, padx=0, pady=0) 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) 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) self.timer_label.pack(side=RIGHT, ipadx=20)
def display_new_timer(self): def display_new_timer(self):
@ -69,13 +72,13 @@ class Toolbar:
def toggle_black_white(self): def toggle_black_white(self):
self.image_window.option["bw"] = not self.image_window.option["bw"] 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() self.image_window.image.apply_options()
def toggle_mirror(self): def toggle_mirror(self):
self.image_window.option["mirror"] = not self.image_window.option["mirror"] 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() self.image_window.image.apply_options()
@ -96,10 +99,10 @@ class Toolbar:
def toggle_always_on_top(self): def toggle_always_on_top(self):
self.image_window.option["always_on_top"] = not self.image_window.option["always_on_top"] self.image_window.option["always_on_top"] = not self.image_window.option["always_on_top"]
if 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() self.stay_on_top()
else: 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) self.image_window.window.after_cancel(self.always_on_top_check)
def stay_on_top(self): def stay_on_top(self):
@ -109,6 +112,6 @@ class Toolbar:
def toggle_fullscreen(self): def toggle_fullscreen(self):
self.image_window.option["fullscreen"] = not self.image_window.option["fullscreen"] 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"]) self.image_window.window.attributes("-fullscreen", self.image_window.option["fullscreen"])

View File

@ -47,3 +47,9 @@ class Config(object):
if config_var not in Config._CONFIG: if config_var not in Config._CONFIG:
raise Exception(f"Please set the {config_var} variable in the config file {Config._CONFIG_FILE}") raise Exception(f"Please set the {config_var} variable in the config file {Config._CONFIG_FILE}")
return Config._CONFIG[config_var] return Config._CONFIG[config_var]
@staticmethod
def set_config_var(name, value):
assert Config._CONFIG
Config._CONFIG[name] = value
return Config

View File

@ -22,3 +22,29 @@ class Util:
time += str(seconds) + "s" time += str(seconds) + "s"
return time 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"

View File

@ -1,10 +1,9 @@
from pathlib import Path
from tkinter import * from tkinter import *
from tkinter import filedialog from tkinter import filedialog
from pathlib import Path import customtkinter
from src.entity.config import Config from src.entity.config import Config
from src.util import Util from src.util import Util
from functools import partial
from src.window.image import ImageWindow from src.window.image import ImageWindow
from src.window.session import SessionWindow from src.window.session import SessionWindow
@ -22,41 +21,49 @@ class App:
self.selected_folder = "" self.selected_folder = ""
self.found_images = [] self.found_images = []
self.timer = 0 self.timer = 0
self.title = Label(root, text="Drawing Training") self.title = customtkinter.CTkLabel(root, text="Drawing Training")
self.title.pack() 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_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.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.images_len.pack()
self.button_frame = Frame(root) self.button_frame = Frame(root)
self.button_frame.pack(fill=X) self.button_frame.pack(fill=X)
timers = [30, 45, 60, 120, 300, 600] timers = [
self.buttons = [] Util.format_seconds(30),
i = 0 Util.format_seconds(45),
for i in timers: Util.format_seconds(60),
t = i Util.format_seconds(120),
new_button = Button(self.button_frame, text=Util.format_seconds(t), Util.format_seconds(300),
command=partial(self.set_timer_in_seconds, t)) Util.format_seconds(600),
self.buttons.append(new_button) "Custom"
self.button_frame.columnconfigure(i, weight=1) ]
new_button.grid(row=0, column=i, sticky=W + E) self.buttons = customtkinter.CTkSegmentedButton(
i += 1 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.launch_button = customtkinter.CTkButton(root, text="Let's draw!", command=self.lets_draw, state="disabled")
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.pack(side="bottom") 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): def lets_draw(self):
self.image_window = ImageWindow(self) self.image_window = ImageWindow(self)
self.image_window.lets_draw(self.found_images.copy(), self.timer) self.image_window.lets_draw(self.found_images.copy(), self.timer)
@ -67,32 +74,33 @@ class App:
def select_folder(self): def select_folder(self):
self.selected_folder = filedialog.askdirectory() self.selected_folder = filedialog.askdirectory()
self.found_images = list( self.folder_name.configure(text="Folder : " + self.selected_folder)
p.resolve() for p in Path(self.selected_folder).glob("**/*") if p.suffix in {".jpg", ".gif", ".png"}) Config.set_config_var("default_folder", self.selected_folder).save()
self.folder_name.config(text="Folder : " + self.selected_folder) self.find_images_in_folder()
self.images_len.config(text="Found : " + str(len(self.found_images)))
self.check_lets_draw() 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): def check_lets_draw(self):
if self.selected_folder != "" and len(self.found_images) > 0 and (self.timer != 0 or self.custom): 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: else:
self.launch_button.config(state="disabled") self.launch_button.configure(state="disabled")
def set_timer_in_seconds(self, user_data): def set_timer_in_seconds(self, user_data):
self.custom = False self.custom = False
self.timer = user_data if user_data == "Custom":
for button in self.buttons: return self.custom_session()
if button['text'] == Util.format_seconds(self.timer):
button.config(bg="blue") self.timer = Util.format_time_to_seconds(user_data)
else:
button.config(bg="gray85")
self.check_lets_draw() self.check_lets_draw()
def set_custom(self, list_in_session): def set_custom(self, list_in_session):
self.custom = True self.custom = True
self.list_in_session = list_in_session self.list_in_session = list_in_session
self.check_lets_draw() self.check_lets_draw()
for button in self.buttons:
button.config(bg="gray85")
self.buttons[-1].config(bg="blue")

View File

@ -1,5 +1,7 @@
import random import random
from tkinter import * from tkinter import *
from customtkinter import CTkToplevel
from pygame import mixer from pygame import mixer
from src.element.image import ImagePlaceholder from src.element.image import ImagePlaceholder
from src.element.toolbar import Toolbar from src.element.toolbar import Toolbar
@ -10,7 +12,7 @@ class ImageWindow:
self.current_original_image = None self.current_original_image = None
self.app = app self.app = app
self.window = Toplevel(app.root) self.window = CTkToplevel(app.root)
self.window.title("Image") self.window.title("Image")
self.window.geometry("1280x1024") self.window.geometry("1280x1024")
self.window.protocol("WM_DELETE_WINDOW", self.on_closing) self.window.protocol("WM_DELETE_WINDOW", self.on_closing)

View File

@ -1,6 +1,5 @@
from functools import partial from functools import partial
from tkinter import * from customtkinter import *
from src.entity.config import Config from src.entity.config import Config
from src.entity.session.drawing import DrawingElement from src.entity.session.drawing import DrawingElement
from src.util import Util from src.util import Util
@ -18,16 +17,16 @@ class SessionWindow:
self.add_button = None self.add_button = None
self.app = app self.app = app
self.window = Toplevel(app.root) self.window = CTkToplevel(app.root)
self.window.title("Custom session") self.window.title("Custom session")
self.window.geometry("600x600") self.window.geometry("600x600")
self.window.protocol("WM_DELETE_WINDOW", self.save_on_closing) self.window.protocol("WM_DELETE_WINDOW", self.save_on_closing)
self.left_column = Frame(self.window, width=300, height=600) self.left_column = CTkFrame(self.window, width=300, height=60)
self.right_column = Frame(self.window, width=300, height=600) self.right_column = CTkFrame(self.window, width=300, height=600)
self.left_column.grid(row=0, column=0, sticky="ns") self.left_column.pack(padx=10, pady=10, side=LEFT, fill=BOTH, expand=True)
self.right_column.grid(row=0, column=1, sticky="ns") self.right_column.pack(padx=10, pady=10, side=LEFT, fill=BOTH, expand=True)
self.list_in_session = [] self.list_in_session = []
@ -40,30 +39,32 @@ class SessionWindow:
def update_session_list(self): def update_session_list(self):
self.reset_list() self.reset_list()
for i, session in enumerate(self.list_in_session): for i, session in enumerate(self.list_in_session):
bg = "blue" if i == self.selected_element_index else "grey85" new_session = CTkButton(self.left_column,
new_session = Button(self.left_column, text=session.number_of_drawings + " drawings of " + session.timer + "s each",
text=session.number_of_drawings + " drawings of " + session.timer + "s each", command=partial(self.edit_element, i),
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())
bg=bg )
)
new_session.grid(row=i, column=0) new_session.grid(row=i, column=0)
button_frame = Frame(self.left_column) buttons = CTkSegmentedButton(
self.add_button = Button(button_frame, text="+", command=self.add_element) self.left_column, values=["+", "-", "^", "v", "save"],
self.add_button.pack(side=LEFT, padx=0, pady=0) command=self.button_command
)
buttons.grid()
self.delete_button = Button(button_frame, text="-", command=self.remove_element) def button_command(self, command):
self.delete_button.pack(side=LEFT, padx=0, pady=0) if command == "+":
self.add_element()
self.up_button = Button(button_frame, text="^", command=self.up_element) elif command == "-":
self.up_button.pack(side=LEFT, padx=0, pady=0) self.remove_element()
elif command == "^":
self.down_button = Button(button_frame, text="v", command=self.down_element) self.up_element()
self.down_button.pack(side=LEFT, padx=0, pady=0) elif command == "v":
self.down_element()
save_session_button = Button(button_frame, text="v", command=self.save_session) elif command == "save":
save_session_button.pack(side=LEFT, padx=0, pady=0) self.save_session()
button_frame.grid() else:
print("unknown command")
def reset_element(self): def reset_element(self):
for widget in self.right_column.winfo_children(): for widget in self.right_column.winfo_children():
@ -82,34 +83,34 @@ class SessionWindow:
else: else:
element = DrawingElement() 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() 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.insert(INSERT, element.number_of_drawings)
self.number_of_drawings_input.bind("<KeyRelease>", lambda e: self.update_local_timer(e)) self.number_of_drawings_input.bind("<KeyRelease>", lambda e: self.update_local_timer(e))
self.number_of_drawings_input.pack() self.number_of_drawings_input.pack()
self.number_of_drawings_input.focus_set() 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() 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.insert(INSERT, element.timer)
self.timer_input.bind("<KeyRelease>", lambda e: self.update_local_timer(e)) self.timer_input.bind("<KeyRelease>", lambda e: self.update_local_timer(e))
self.timer_input.pack() 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() 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.total_row_time_value_label.pack()
self.update_local_timer() 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() save_button.pack()
def update_local_timer(self, event=None): 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())) text=Util.format_seconds(int(self.timer_input.get()) * int(self.number_of_drawings_input.get()))
) )