Compare commits

...

10 Commits

Author SHA1 Message Date
1a59ec3ec2 πŸ› Debug linux build 2024-09-13 17:53:55 +02:00
5941da9ae5 ✨ Add cheated pause
For #36
2024-06-17 14:16:58 +02:00
a8c0979ef1 ✨ Add not cheated pause
For #36
2024-06-14 17:35:28 +02:00
Shikiryu
328b7a0e49 πŸš€ Modify windows makefile 2024-06-10 21:49:04 +02:00
Shikiryu
ee8968cbab πŸ’„ Add progressbar as timer
+ remove one bug and updates dependencies
2024-05-17 23:39:29 +02:00
Shikiryu
33d6e9d4df πŸ’„ Add better displayed tooltip 2024-05-16 00:19:49 +02:00
3b17dff7be 🚧 Add icons in toolbar
For #17
2024-05-15 23:54:07 +02:00
d8b6e7bd8c πŸ› Transform Radiobutton to button to allow re-click
Fix #29
2024-05-15 17:25:18 +02:00
8087d326ef πŸ“ Add a README 2024-05-15 09:40:16 +02:00
7702a0bea3 πŸ’„ Add an app icon
Fix #15
2024-05-15 09:30:57 +02:00
19 changed files with 160 additions and 42 deletions

19
README.md Normal file
View File

@ -0,0 +1,19 @@
# Drawing Training
Open Source alternative to [GestureDrawing!](https://cubebrush.co/advanches/products/d9q6yq/gesturedrawing)
## Authors
- [@shikiryu](https://git.shikiryu.com/Shikiryu)
## Installation
Install my-project with python
```bash
sudo apt install cmake build-essential tcl-dev tk-dev python3-tk
pip install -r requirements.txt
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

BIN
assets/icons/copy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

BIN
assets/icons/flip.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

BIN
assets/icons/folder.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

BIN
assets/icons/logo-dt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
assets/icons/maximize.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 245 B

BIN
assets/icons/next.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 B

BIN
assets/icons/pause.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

BIN
assets/icons/sand-clock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 B

BIN
assets/icons/timer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 B

View File

@ -1,5 +1,6 @@
import os import os
import sys import sys
from tkinter import PhotoImage
import customtkinter import customtkinter
@ -15,7 +16,9 @@ def main():
root = customtkinter.CTk() root = customtkinter.CTk()
root.title("Drawing Training!") root.title("Drawing Training!")
root.geometry("300x600") root.geometry("300x300")
icon = PhotoImage(file="assets/icons/logo-dt.png")
root.iconphoto(True, icon)
App(root) App(root)

View File

@ -1,12 +1,20 @@
.PHONY: build dist .PHONY: build dist
TOTAL_LOCATION := $(pip show customtkinter | grep Location) .ONESHELL:
LOCATION=!TOTAL_LOCATION:~0,10! VENV_DIR=.venv
ACTIVATE_VENV:=. $(VENV_DIR)/bin/activate
LOCATION:= $(shell pip show customtkinter | grep Location | cut -c 11-)
install:
python3 -m venv "$(VENV_DIR)"
$(ACTIVATE_VENV)
pip install --upgrade --requirement requirements.txt
build_linux: build_linux:
rm -rf build dist drawingtraining.spec rm -rf build dist drawingtraining.spec
$(ACTIVATE_VENV)
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: build_windows:
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" --add-data "${LOCATION}\customtkinter;customtkinter\" pyinstaller main.py --onefile -w --hidden-import="PIL._tkinter_finder" -n drawingtraining --add-data "assets;assets" --add-data "$(LOCATION)\customtkinter;customtkinter" --icon="assets/icons/logo-dt.png"

View File

@ -1,4 +1,6 @@
pillow~=9.5.0 pillow~=10.0.1
pyinstaller pyinstaller==5.13.1
pygame~=2.4.0 pygame~=2.4.0
customtkinter~=5.1.3 customtkinter~=5.1.3
configparser~=5.3.0
CTkToolTip~=0.4

View File

@ -1,5 +1,7 @@
import copy import copy
from tkinter import * from tkinter import *
import PIL
from PIL import ImageTk, Image, ImageOps from PIL import ImageTk, Image, ImageOps
@ -8,6 +10,7 @@ class ImagePlaceholder:
self.is_break = False self.is_break = False
self.current_image = None self.current_image = None
self.current_original_image = None self.current_original_image = None
self.paused_original_image = None
self.image_window = image_window self.image_window = image_window
self.images = images.copy() self.images = images.copy()
@ -16,6 +19,9 @@ class ImagePlaceholder:
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):
if len(self.images) == 0:
self.image_window.on_closing()
return
image_path = self.images.pop(0) image_path = self.images.pop(0)
if image_path == "break": if image_path == "break":
self.is_break = True self.is_break = True
@ -35,7 +41,7 @@ class ImagePlaceholder:
if w < 21 or h < 21: if w < 21 or h < 21:
w = 1280 w = 1280
h = 1024 h = 1024
self.current_image.thumbnail((w - 20, h - 20), Image.ANTIALIAS) self.current_image.thumbnail((w - 20, h - 20), PIL.Image.Resampling.LANCZOS)
if reload: if reload:
self.load_widget() self.load_widget()
@ -56,3 +62,16 @@ class ImagePlaceholder:
self.image_label.configure(bg="#e8d4bc" if self.is_break else "#FFFFFF") self.image_label.configure(bg="#e8d4bc" if self.is_break else "#FFFFFF")
self.image_label.configure(image=image_to_display) self.image_label.configure(image=image_to_display)
self.image_label.image = image_to_display self.image_label.image = image_to_display
def pause(self, pause=True, cheat=False):
if pause:
self.paused_original_image = self.current_original_image
if not cheat:
image_path = 'assets/images/break.jpg'
self.current_original_image = Image.open(image_path)
else:
self.current_original_image = self.paused_original_image
self.paused_original_image = None
self.current_image = copy.deepcopy(self.current_original_image)
self.apply_options()

View File

@ -1,15 +1,21 @@
import copy
import os import os
import subprocess import subprocess
import sys import sys
from tkinter import * from tkinter import *
from tkinter.ttk import Progressbar, Style
from tkinter.messagebox import *
import customtkinter import customtkinter
from CTkToolTip import *
from PIL import ImageTk, Image
from src.util import Util from src.util import Util
class Toolbar: class Toolbar:
def __init__(self, image_window, timer): def __init__(self, image_window, timer):
self.timers = [] self.timers = []
self.is_paused = False
self.current_image = None self.current_image = None
self.current_original_image = None self.current_original_image = None
self.image_window = image_window self.image_window = image_window
@ -20,43 +26,70 @@ 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 = customtkinter.CTkButton(toolbar, compound=LEFT, text="next ➑️", next_button = customtkinter.CTkButton(toolbar, image=ImageTk.PhotoImage(Image.open("assets/icons/next.png")),
command=self.image_window.next_image) text="", command=self.image_window.next_image)
next_button.pack(side=LEFT, padx=0, pady=0) next_button.pack(side=LEFT, padx=0, pady=0)
CTkToolTip(next_button, delay=0.1, message="Next", y_offset=-40, x_offset=-40)
self.bw_button = customtkinter.CTkButton(toolbar, compound=LEFT, text="black&white", pause_button = customtkinter.CTkButton(toolbar, image=ImageTk.PhotoImage(Image.open("assets/icons/pause.png")),
command=self.toggle_black_white) text="", command=self.pause)
pause_button.pack(side=LEFT, padx=0, pady=0)
CTkToolTip(pause_button, delay=0.1, message="Next", y_offset=-40, x_offset=-40)
self.bw_button = customtkinter.CTkButton(toolbar, image=ImageTk.PhotoImage(Image.open("assets/icons/black-and"
"-white.png")),
text="", 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)
CTkToolTip(self.bw_button, delay=0.1, message="Black and white image", y_offset=-40, x_offset=-40)
self.mirror_button = customtkinter.CTkButton(toolbar, compound=LEFT, command=self.toggle_mirror, self.mirror_button = customtkinter.CTkButton(toolbar, command=self.toggle_mirror, text="",
text="mirror") image=ImageTk.PhotoImage(Image.open("assets/icons/flip.png")))
self.mirror_button.pack(side=LEFT, padx=0, pady=0) self.mirror_button.pack(side=LEFT, padx=0, pady=0)
CTkToolTip(self.mirror_button, delay=0.1, message="Mirrored image", y_offset=-40, x_offset=-40)
open_folder_button = customtkinter.CTkButton(toolbar, compound=LEFT, command=self.open_folder, open_folder_button = customtkinter.CTkButton(toolbar, command=self.open_folder, text="",
text="open folder") image=ImageTk.PhotoImage(Image.open("assets/icons/folder.png")))
open_folder_button.pack(side=LEFT, padx=0, pady=0) open_folder_button.pack(side=LEFT, padx=0, pady=0)
CTkToolTip(open_folder_button, delay=0.1, message="Open folder", y_offset=-40, x_offset=-40)
self.always_on_top_button = customtkinter.CTkButton(toolbar, compound=LEFT, command=self.toggle_always_on_top, self.always_on_top_button = customtkinter.CTkButton(toolbar, command=self.toggle_always_on_top, text="",
text="always on top") image=ImageTk.PhotoImage(
Image.open("assets/icons/copy.png")))
self.always_on_top_button.pack(side=LEFT, padx=0, pady=0) self.always_on_top_button.pack(side=LEFT, padx=0, pady=0)
CTkToolTip(self.always_on_top_button, delay=0.1, message="Always on top", y_offset=-40, x_offset=-40)
self.fullscreen_button = customtkinter.CTkButton(toolbar, compound=LEFT, command=self.toggle_fullscreen, self.fullscreen_button = customtkinter.CTkButton(toolbar, command=self.toggle_fullscreen, text="",
text="fullscreen") image=ImageTk.PhotoImage(
Image.open("assets/icons/maximize.png")))
self.fullscreen_button.pack(side=LEFT, padx=0, pady=0) self.fullscreen_button.pack(side=LEFT, padx=0, pady=0)
CTkToolTip(self.fullscreen_button, delay=0.1, message="Fullscreen", y_offset=-40, x_offset=-40)
timer_button = customtkinter.CTkButton(toolbar, compound=LEFT, command=self.toggle_timer, text="timer") timer_button = customtkinter.CTkButton(toolbar, command=self.toggle_timer, text="",
image=ImageTk.PhotoImage(Image.open("assets/icons/sand-clock.png")))
timer_button.pack(side=LEFT, padx=0, pady=0) timer_button.pack(side=LEFT, padx=0, pady=0)
CTkToolTip(timer_button, delay=0.1, message="Toggle timer", y_offset=-40, x_offset=-40)
self.timer_label = customtkinter.CTkLabel(toolbar, text=str(self.timer), text_color=Util.get_default_button_color()) 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)
style = Style()
style.theme_use('default')
style.configure("black.Horizontal.TProgressbar", background='black')
self.progressbar = Progressbar(toolbar, length=280)
self.progressbar.pack(side=RIGHT, ipadx=20)
def display_new_timer(self): def display_new_timer(self):
if len(self.timers) == 0:
return
self.timer = self.timers.pop(0) self.timer = self.timers.pop(0)
self.timer_label.configure(text=Util.format_seconds(self.timer)) self.timer_label.configure(text=Util.format_seconds(self.timer))
self.timer_check = self.image_window.window.after(1000, self.update_timer, self.timer) self.timer_check = self.image_window.window.after(1000, self.update_timer, self.timer)
def update_timer(self, current): def update_timer(self, current):
current -= 1 current -= 1
self.progressbar['value'] = 100 - (current * 100 / self.timer)
if current == 10: if current == 10:
self.image_window.play_countdown() self.image_window.play_countdown()
if current > 0: if current > 0:
@ -72,13 +105,15 @@ 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.configure(fg_color=Util.get_default_active_button_color() if self.image_window.option["bw"] else Util.get_default_button_color()) 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.configure(fg_color=Util.get_default_active_button_color() if self.image_window.option["mirror"] else Util.get_default_button_color()) 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()
@ -86,8 +121,11 @@ class Toolbar:
try: try:
self.timer_label.pack_info() self.timer_label.pack_info()
self.timer_label.pack_forget() self.timer_label.pack_forget()
self.progressbar.pack_info()
self.progressbar.pack_forget()
except TclError: except TclError:
self.timer_label.pack(side=RIGHT, ipadx=20) self.timer_label.pack(side=RIGHT, ipadx=20)
self.progressbar.pack(side=RIGHT, ipadx=20)
def open_folder(self): def open_folder(self):
if sys.platform == "win32": if sys.platform == "win32":
@ -112,6 +150,27 @@ 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.configure(fg_color=Util.get_default_active_button_color() if self.image_window.option["fullscreen"] else Util.get_default_button_color()) 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"])
def pause(self):
cheating = False
if not self.is_paused:
cheating = askyesno("Pause", "Laisser l'image affichΓ©e ?")
self.is_paused = True
else:
self.is_paused = False
# timer is paused whatever happened
if self.timer_check is not None:
self.image_window.window.after_cancel(self.timer_check)
self.timer_check = None
else:
self.timer_check = self.image_window.window.after(
1000, self.update_timer, int((100-self.progressbar['value'])*self.timer/100)
)
self.image_window.image.pause(pause=self.is_paused, cheat=cheating)

View File

@ -47,4 +47,4 @@ class Util:
@staticmethod @staticmethod
def get_default_active_button_color(): def get_default_active_button_color():
return "gray29", "gray29" return "green", "green"

View File

@ -1,3 +1,4 @@
import tkinter
from pathlib import Path from pathlib import Path
from tkinter import * from tkinter import *
from tkinter import filedialog from tkinter import filedialog
@ -33,8 +34,8 @@ class App:
self.images_len = customtkinter.CTkLabel(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, bg=root.cget("bg"))
self.button_frame.pack(fill=X) self.button_frame.pack(fill=X, expand=True)
timers = [ timers = [
Util.format_seconds(30), Util.format_seconds(30),
@ -45,14 +46,15 @@ class App:
Util.format_seconds(600), Util.format_seconds(600),
"Custom" "Custom"
] ]
self.buttons = customtkinter.CTkSegmentedButton(
root, values=timers, self.buttons = []
command=self.set_timer_in_seconds for i, timer in enumerate(timers):
) new_button = customtkinter.CTkButton(self.button_frame, width=10, text=timer, command=lambda t=timer: self.set_timer_in_seconds(t))
self.buttons.pack(fill=X) new_button.pack(side=tkinter.LEFT, fill=X, expand=True)
self.buttons.append(new_button)
self.launch_button = customtkinter.CTkButton(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.launch_button.pack(side=tkinter.BOTTOM)
self.read_config() self.read_config()
@ -73,11 +75,13 @@ class App:
self.session_window.open() self.session_window.open()
def select_folder(self): def select_folder(self):
self.selected_folder = filedialog.askdirectory() new_directory = filedialog.askdirectory()
self.folder_name.configure(text="Folder : " + self.selected_folder) if new_directory:
Config.set_config_var("default_folder", self.selected_folder).save() self.selected_folder = new_directory
self.find_images_in_folder() self.folder_name.configure(text="Folder : " + self.selected_folder)
self.check_lets_draw() 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): def find_images_in_folder(self):
if Path(self.selected_folder).exists(): if Path(self.selected_folder).exists():
@ -93,6 +97,10 @@ class App:
self.launch_button.configure(state="disabled") self.launch_button.configure(state="disabled")
def set_timer_in_seconds(self, user_data): def set_timer_in_seconds(self, user_data):
# resetting buttons states
for button in self.buttons:
button.configure(fg_color=(Util.get_default_button_color() if user_data != button.cget("text") else Util.get_default_active_button_color()))
self.custom = False self.custom = False
if user_data == "Custom": if user_data == "Custom":
return self.custom_session() return self.custom_session()

View File

@ -45,14 +45,14 @@ class SessionWindow:
text=session.number_of_drawings + " drawings of " + Util.format_seconds(int(session.timer)) + " each", text=session.number_of_drawings + " drawings of " + Util.format_seconds(int(session.timer)) + " each",
command=partial(self.edit_element, i), command=partial(self.edit_element, i),
fg_color=( fg_color=(
Util.get_default_button_color() if i == self.selected_element_index else Util.get_default_active_button_color()) Util.get_default_active_button_color() if i == self.selected_element_index else Util.get_default_button_color())
) )
elif isinstance(session, BreakElement): elif isinstance(session, BreakElement):
new_session = CTkButton(self.left_column, new_session = CTkButton(self.left_column,
text="Break of " + Util.format_seconds(int(session.timer)) + " each", text="Break of " + Util.format_seconds(int(session.timer)) + " each",
command=partial(self.edit_element, i), command=partial(self.edit_element, i),
fg_color=( fg_color=(
Util.get_default_button_color() if i == self.selected_element_index else Util.get_default_active_button_color()) Util.get_default_active_button_color() if i == self.selected_element_index else Util.get_default_button_color())
) )
else: else:
raise Exception("unknown type.") raise Exception("unknown type.")