Skip to content

Commit 1ae710c

Browse files
authored
Merge pull request #11 from Matiiss/image-tool
Smudge tool for the image editor
2 parents 968a652 + bdeb46a commit 1ae710c

File tree

4 files changed

+609
-87
lines changed

4 files changed

+609
-87
lines changed

pyproject.toml

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,17 @@
33
# Don't forget to change the name, description, and authors to match your project!
44
name = "code-jam-template"
55
description = "Add your description here"
6-
authors = [
7-
{ name = "Your Name" }
8-
]
6+
authors = [{ name = "Your Name" }]
97
version = "0.1.0"
108
readme = "README.md"
119
requires-python = ">=3.12"
12-
dependencies = [
13-
"nicegui>=2.22.2",
14-
]
10+
dependencies = ["nicegui>=2.22.2"]
1511

1612
[dependency-groups]
1713
# This `dev` group contains all the development requirements for our linting toolchain.
1814
# Don't forget to pin your dependencies!
1915
# This list will have to be migrated if you wish to use another dependency manager.
20-
dev = [
21-
"pre-commit~=4.2.0",
22-
"ruff~=0.12.2",
23-
]
16+
dev = ["pre-commit~=4.2.0", "ruff~=0.12.2"]
2417

2518
[tool.ruff]
2619
# Increase the line length. This breaks PEP8 but it is way easier to work with.
@@ -41,25 +34,31 @@ src = ["src"]
4134
select = ["ALL"]
4235
# Ignore some of the most obnoxious linting errors.
4336
ignore = [
44-
# Missing docstrings.
45-
"D100",
46-
"D104",
47-
"D105",
48-
"D106",
49-
"D107",
50-
# Docstring whitespace.
51-
"D203",
52-
"D213",
53-
# Docstring punctuation.
54-
"D415",
55-
# Docstring quotes.
56-
"D301",
57-
# Builtins.
58-
"A",
59-
# Print statements.
60-
"T20",
61-
# TODOs.
62-
"TD002",
63-
"TD003",
64-
"FIX",
37+
# Missing docstrings.
38+
"D100",
39+
"D104",
40+
"D105",
41+
"D106",
42+
"D107",
43+
# Docstring whitespace.
44+
"D203",
45+
"D213",
46+
"D413",
47+
# Docstring punctuation.
48+
"D415",
49+
# Docstring quotes.
50+
"D301",
51+
# lowercase issues.
52+
"N802",
53+
"N803",
54+
# clas scope should not be mixedCase
55+
"N815",
56+
# Builtins.
57+
"A",
58+
# Print statements.
59+
"T20",
60+
# TODOs.
61+
"TD002",
62+
"TD003",
63+
"FIX",
6564
]

src/editor/main.py

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import random
55

66
from nicegui import app, ui
7-
from nicegui.events import UploadEventArguments
7+
from nicegui.events import UploadEventArguments, ValueChangeEventArguments
88

99
app.add_static_files("/scripts", pathlib.Path(__file__).parent / "scripts")
1010

@@ -50,6 +50,42 @@ def reset_confirmation(*, mode_value: bool = False) -> None:
5050
dialog.open()
5151

5252

53+
# I really don't want to do this but I don't know how else to achieve it
54+
global_vars = {
55+
"type_programatically_changed": False,
56+
}
57+
58+
59+
def revert_type() -> None:
60+
"""Revert the type change when cancel is clicked."""
61+
global_vars["type_programatically_changed"] = True
62+
type_toggle.set_visibility(False)
63+
type_toggle.value = "smooth" if type_toggle.value == "pixel" else "pixel"
64+
type_toggle.update()
65+
type_toggle.set_visibility(True)
66+
global_vars["type_programatically_changed"] = False
67+
68+
69+
def change_type(*, mode_value: bool = False) -> None:
70+
"""Prompt user to reset canvas."""
71+
if global_vars["type_programatically_changed"]:
72+
return
73+
with ui.dialog() as dialog, ui.card():
74+
ui.label("Are you sure you want to change the drawing mode? This will clear the canvas.")
75+
with ui.row().style("display: flex; justify-content: space-between; width: 100%;"):
76+
ui.button(
77+
"Cancel",
78+
on_click=lambda: (
79+
dialog.close(),
80+
revert_type(),
81+
),
82+
)
83+
ui.button("Change", on_click=lambda: (do_reset(mode_value=mode_value), dialog.close())).props(
84+
"color='red'",
85+
)
86+
dialog.open()
87+
88+
5389
def reset() -> None:
5490
"""Reset canvas."""
5591
ui.run_javascript("""
@@ -91,6 +127,16 @@ def upload_image(e: UploadEventArguments) -> None:
91127
""")
92128

93129

130+
def switch_action(e: ValueChangeEventArguments) -> None:
131+
"""Fire switch action event."""
132+
ui.run_javascript(f"""
133+
const event = new Event('change');
134+
const actionSelect = document.querySelector("#action-select");
135+
actionSelect.setAttribute("value", "{e.value}");
136+
actionSelect.dispatchEvent(event);
137+
""")
138+
139+
94140
ui.element("img").props("id='file-upload'").style("display: none;")
95141

96142
with ui.row().style("display: flex; width: 100%;"):
@@ -102,15 +148,20 @@ def upload_image(e: UploadEventArguments) -> None:
102148
ui.button("Download").props("id='download-button'")
103149
ui.upload(
104150
label="Upload file",
151+
# The following event is fired in case the image upload is above the canvas.
152+
# This would change the getBoundingClientRect() of the canvas.
153+
on_begin_upload=lambda: ui.run_javascript("""
154+
const event = new Event('resize');
155+
window.dispatchEvent(event);
156+
"""),
157+
auto_upload=True,
105158
on_upload=upload_image,
106159
on_rejected=lambda _: ui.notify("There was an issue with the upload."),
107-
).classes(
108-
"max-w-full",
109160
).props("accept='image/*' id='file-input'")
110-
ui.toggle(
161+
type_toggle = ui.toggle(
111162
{"smooth": "✍️", "pixel": "👾"},
112163
value="smooth",
113-
on_change=lambda e: reset_confirmation(mode_value=e.value),
164+
on_change=lambda e: change_type(mode_value=e.value),
114165
).props("id='type-select'")
115166

116167
ui.element("canvas").props("id='image-canvas'").style(
@@ -119,7 +170,17 @@ def upload_image(e: UploadEventArguments) -> None:
119170

120171
# Canvas controls
121172
with ui.column().style("flex-grow: 1; flex-basis: 0;"):
122-
ui.toggle({"pen": "🖊️", "eraser": "🧽"}, value="pen", on_change=lambda _: reset_confirmation()).props(
173+
action_options = {
174+
"pen": "🖊️",
175+
"eraser": "🧽",
176+
"smudge": "💨",
177+
}
178+
179+
action_toggle = ui.toggle(
180+
action_options,
181+
value="pen",
182+
on_change=switch_action,
183+
).props(
123184
"id='action-select'",
124185
)
125186
ui.separator().classes("w-full")
@@ -145,7 +206,12 @@ def upload_image(e: UploadEventArguments) -> None:
145206
width_input.bind_value(width_slider)
146207

147208
ui.add_body_html("""
148-
<script type="py" src="/scripts/editor.py" defer></script>
209+
<py-config>
210+
[[fetch]]
211+
from = "/scripts/"
212+
files = ["canvas_ctx.py", "editor.py"]
213+
</py-config>
214+
<script type="py" src="/scripts/editor.py"></script>
149215
""")
150216

151217
ui.run()

0 commit comments

Comments
 (0)