11 Commits

Author SHA1 Message Date
7a8ca677ea ♻️ Refactor generators and add an about window 2024-09-13 17:51:58 +02:00
Shikiryu
5112439712 🚧 Add Quickposes model download 2024-09-13 00:59:04 +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
21 changed files with 254 additions and 41 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 sys
from tkinter import PhotoImage
import customtkinter
@@ -15,7 +16,9 @@ def main():
root = customtkinter.CTk()
root.title("Drawing Training!")
root.geometry("300x600")
root.geometry("300x300")
icon = PhotoImage(file="assets/icons/logo-dt.png")
root.iconphoto(True, icon)
App(root)

View File

@@ -1,7 +1,14 @@
.PHONY: build dist
TOTAL_LOCATION := $(pip show customtkinter | grep Location)
LOCATION=!TOTAL_LOCATION:~0,10!
.ONESHELL:
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:
rm -rf build dist drawingtraining.spec
@@ -9,4 +16,4 @@ build_linux:
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\"
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,8 @@
pillow~=9.5.0
pyinstaller
pillow~=10.0.1
pyinstaller==5.13.1
pygame~=2.4.0
customtkinter~=5.1.3
configparser~=5.3.0
CTkToolTip~=0.4
requests~=2.32.3
CTkMenuBar~=0.8

View File

@@ -1,5 +1,7 @@
import copy
from tkinter import *
import PIL
from PIL import ImageTk, Image, ImageOps
@@ -8,6 +10,7 @@ class ImagePlaceholder:
self.is_break = False
self.current_image = None
self.current_original_image = None
self.paused_original_image = None
self.image_window = image_window
self.images = images.copy()
@@ -16,6 +19,9 @@ class ImagePlaceholder:
self.image_label.pack(side=TOP, fill=BOTH, expand=1)
def display_new_image(self):
if len(self.images) == 0:
self.image_window.on_closing()
return
image_path = self.images.pop(0)
if image_path == "break":
self.is_break = True
@@ -35,7 +41,7 @@ class ImagePlaceholder:
if w < 21 or h < 21:
w = 1280
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:
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(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 subprocess
import sys
from tkinter import *
from tkinter.ttk import Progressbar, Style
from tkinter.messagebox import *
import customtkinter
from CTkToolTip import *
from PIL import ImageTk, Image
from src.util import Util
class Toolbar:
def __init__(self, image_window, timer):
self.timers = []
self.is_paused = False
self.current_image = None
self.current_original_image = None
self.image_window = image_window
@@ -20,43 +26,70 @@ class Toolbar:
toolbar = Frame(self.image_window.window, bd=1, relief=RAISED)
toolbar.pack(side=BOTTOM, fill=X)
next_button = customtkinter.CTkButton(toolbar, compound=LEFT, text="next ➡️",
command=self.image_window.next_image)
next_button = customtkinter.CTkButton(toolbar, image=ImageTk.PhotoImage(Image.open("assets/icons/next.png")),
text="", command=self.image_window.next_image)
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",
command=self.toggle_black_white)
pause_button = customtkinter.CTkButton(toolbar, image=ImageTk.PhotoImage(Image.open("assets/icons/pause.png")),
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)
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,
text="mirror")
self.mirror_button = customtkinter.CTkButton(toolbar, command=self.toggle_mirror, text="",
image=ImageTk.PhotoImage(Image.open("assets/icons/flip.png")))
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,
text="open folder")
open_folder_button = customtkinter.CTkButton(toolbar, command=self.open_folder, text="",
image=ImageTk.PhotoImage(Image.open("assets/icons/folder.png")))
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,
text="always on top")
self.always_on_top_button = customtkinter.CTkButton(toolbar, command=self.toggle_always_on_top, text="",
image=ImageTk.PhotoImage(
Image.open("assets/icons/copy.png")))
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,
text="fullscreen")
self.fullscreen_button = customtkinter.CTkButton(toolbar, command=self.toggle_fullscreen, text="",
image=ImageTk.PhotoImage(
Image.open("assets/icons/maximize.png")))
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)
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)
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):
if len(self.timers) == 0:
return
self.timer = self.timers.pop(0)
self.timer_label.configure(text=Util.format_seconds(self.timer))
self.timer_check = self.image_window.window.after(1000, self.update_timer, self.timer)
def update_timer(self, current):
current -= 1
self.progressbar['value'] = 100 - (current * 100 / self.timer)
if current == 10:
self.image_window.play_countdown()
if current > 0:
@@ -72,13 +105,15 @@ class Toolbar:
def toggle_black_white(self):
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()
def toggle_mirror(self):
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()
@@ -86,8 +121,11 @@ class Toolbar:
try:
self.timer_label.pack_info()
self.timer_label.pack_forget()
self.progressbar.pack_info()
self.progressbar.pack_forget()
except TclError:
self.timer_label.pack(side=RIGHT, ipadx=20)
self.progressbar.pack(side=RIGHT, ipadx=20)
def open_folder(self):
if sys.platform == "win32":
@@ -112,6 +150,27 @@ class Toolbar:
def toggle_fullscreen(self):
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"])
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)

41
src/entity/generator.py Normal file
View File

@@ -0,0 +1,41 @@
from datetime import datetime
import os
import io
import tempfile
import requests
from PIL import Image
class Generator(object):
def __init__(self, generator=None, folder=None):
self.selected_folder = folder
if generator == 'poses':
self.generate_poses()
def generate_poses(self):
dt = datetime.now()
ts = int(datetime.timestamp(dt))
url = 'https://quickposes.com/ajax/getPosesForSlider/Chiaroscuro?_=' + str(ts)
resp_json = requests.get(url=url)
if resp_json.status_code == 200:
data = resp_json.json()
c = 0
for src in data:
if os.path.exists(os.path.join(self.selected_folder, src['src'])):
continue
if c >= 3:
break
buffer = tempfile.SpooledTemporaryFile(max_size=1e9)
r = requests.get('https://quickposes.com/assets/poses/' + src['src'], stream=True)
if r.status_code == 200:
downloaded = 0
for chunk in r.iter_content(chunk_size=1024):
downloaded += len(chunk)
buffer.write(chunk)
buffer.seek(0)
i = Image.open(io.BytesIO(buffer.read()))
i.save(os.path.join(self.selected_folder, src['src']), quality=85)
buffer.close()
c += 1

View File

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

30
src/window/about.py Normal file
View File

@@ -0,0 +1,30 @@
import webbrowser
import customtkinter
from customtkinter import CTkToplevel
class AboutWindow:
def __init__(self, app):
self.app = app
self.window = CTkToplevel(app.root)
self.window.title("À propos")
self.window.geometry("600x300")
self.window.protocol("WM_DELETE_WINDOW", self.on_closing)
link_gesture_drawing = customtkinter.CTkLabel(self.window, text="Open Source alternative to GestureDrawing!")
link_gesture_drawing.pack()
link_gesture_drawing.bind("<Button-1>",
lambda e: self.open_link("https://cubebrush.co/advanches/products/d9q6yq/gesturedrawing"))
link_shikiryu = customtkinter.CTkLabel(self.window, text="by Shikiryu")
link_shikiryu.pack()
link_shikiryu.bind("<Button-1>", lambda e: self.open_link("https://shikiryu.com"))
@staticmethod
def open_link(url):
webbrowser.open_new(url)
def on_closing(self):
self.window.destroy()

View File

@@ -1,11 +1,15 @@
import tkinter
from pathlib import Path
from tkinter import *
from tkinter import filedialog
import customtkinter
from src.entity.config import Config
from src.entity.generator import Generator
from src.util import Util
from src.window.about import AboutWindow
from src.window.image import ImageWindow
from src.window.session import SessionWindow
from CTkMenuBar import *
class App:
@@ -21,6 +25,17 @@ class App:
self.selected_folder = ""
self.found_images = []
self.timer = 0
menu = CTkMenuBar(master=root)
generate_button = menu.add_cascade("Generate")
help_button = menu.add_cascade("Help")
dropdown = CustomDropdownMenu(widget=generate_button)
dropdown.add_option(option="Poses", command=lambda:self.download_poses('poses'))
about = CustomDropdownMenu(widget=help_button)
about.add_option(option="À propos", command=self.show_help)
self.title = customtkinter.CTkLabel(root, text="Drawing Training")
self.title.pack()
@@ -33,8 +48,8 @@ class App:
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)
self.button_frame = Frame(root, bg=root.cget("bg"))
self.button_frame.pack(fill=X, expand=True)
timers = [
Util.format_seconds(30),
@@ -45,14 +60,16 @@ class App:
Util.format_seconds(600),
"Custom"
]
self.buttons = customtkinter.CTkSegmentedButton(
root, values=timers,
command=self.set_timer_in_seconds
)
self.buttons.pack(fill=X)
self.buttons = []
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))
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.pack(side="bottom")
self.launch_button.pack(side=tkinter.BOTTOM)
self.read_config()
@@ -64,6 +81,9 @@ class App:
except Exception:
pass
def show_help(self):
AboutWindow(self)
def lets_draw(self):
self.image_window = ImageWindow(self)
self.image_window.lets_draw(self.found_images.copy(), self.timer)
@@ -73,11 +93,17 @@ class App:
self.session_window.open()
def select_folder(self):
self.selected_folder = filedialog.askdirectory()
self.folder_name.configure(text="Folder : " + self.selected_folder)
Config.set_config_var("default_folder", self.selected_folder).save()
new_directory = filedialog.askdirectory()
if new_directory:
self.selected_folder = new_directory
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 download_poses(self, generator=None):
Generator(generator=generator, folder=self.selected_folder)
self.find_images_in_folder()
self.check_lets_draw()
def find_images_in_folder(self):
if Path(self.selected_folder).exists():
@@ -93,6 +119,11 @@ class App:
self.launch_button.configure(state="disabled")
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
if user_data == "Custom":
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",
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())
Util.get_default_active_button_color() if i == self.selected_element_index else Util.get_default_button_color())
)
elif isinstance(session, BreakElement):
new_session = CTkButton(self.left_column,
text="Break of " + Util.format_seconds(int(session.timer)) + " 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())
Util.get_default_active_button_color() if i == self.selected_element_index else Util.get_default_button_color())
)
else:
raise Exception("unknown type.")