initial commit
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
from tkinter import *
|
||||
from tkinter import ttk
|
||||
from tkinter import filedialog
|
||||
from typing import List, Optional, Tuple
|
||||
import re
|
||||
|
||||
|
||||
class Image:
|
||||
def __init__(self, comment_name: str, width: int, height: int) -> None:
|
||||
self.comment_name = comment_name
|
||||
self.name = None
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.data = []
|
||||
|
||||
def add_data(self, raw_data: List[str]) -> None:
|
||||
for v in raw_data:
|
||||
self.data += list(map(lambda v: int(v), f"{int(v, 16):08b}"))
|
||||
|
||||
def get_pixel(self, x: int, y: int) -> bool:
|
||||
return self.data[y * self.width + x] == 1 # TODO better
|
||||
|
||||
|
||||
class File:
|
||||
def __init__(self, path: str) -> None:
|
||||
self.path = path
|
||||
if path is None:
|
||||
self.images = []
|
||||
else:
|
||||
self.images = self.read_file()
|
||||
|
||||
def read_file(self) -> List[Image]:
|
||||
images = []
|
||||
|
||||
current_image = None
|
||||
|
||||
with open(self.path) as f:
|
||||
for line in f:
|
||||
if current_image is not None:
|
||||
header = re.match(
|
||||
r"const unsigned char (\w+) \[\] PROGMEM \= \{",
|
||||
line,
|
||||
)
|
||||
if header:
|
||||
groups = header.groups()
|
||||
current_image.name = groups[0]
|
||||
elif current_image.name is not None:
|
||||
data = re.match(r"((0x\w+,? ?)+)", line.strip())
|
||||
if data:
|
||||
current_image.add_data(
|
||||
data.groups()[0].strip().strip(",").split(", ")
|
||||
)
|
||||
else:
|
||||
images += [current_image]
|
||||
current_image = None
|
||||
comment_header = re.match(r"// '(\w+)', (\d+)x(\d+)px", line)
|
||||
if comment_header:
|
||||
groups = comment_header.groups()
|
||||
current_image = Image(groups[0], int(groups[1]), int(groups[2]))
|
||||
|
||||
return images
|
||||
|
||||
|
||||
class App(ttk.Frame):
|
||||
def __init__(self, parent) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
self.parent = parent
|
||||
self.current_file = None
|
||||
|
||||
self.explorer = self.make_explorer()
|
||||
self.canvas = self.make_canvas()
|
||||
self.menu_file, self.menu_edit = self.make_menus()
|
||||
|
||||
self.grid_rowconfigure(0, weight=1)
|
||||
self.grid_columnconfigure(0, weight=1)
|
||||
|
||||
self.open_file(None)
|
||||
|
||||
def make_menus(self) -> Tuple[Menu, Menu]:
|
||||
menubar = Menu(self.parent)
|
||||
self.parent["menu"] = menubar
|
||||
|
||||
menu_file = Menu(menubar)
|
||||
menubar.add_cascade(menu=menu_file, label="File")
|
||||
|
||||
menu_file.add_command(label="New", command=lambda: self.open_file(""))
|
||||
menu_file.add_command(
|
||||
label="Open...",
|
||||
command=lambda: self.open_file(filedialog.askopenfilename()),
|
||||
)
|
||||
menu_file.add_command(
|
||||
label="Save",
|
||||
command=lambda: self.save_file(self.current_file.path),
|
||||
)
|
||||
menu_file.add_command(
|
||||
label="Save As...",
|
||||
command=lambda: self.save_file(filedialog.asksaveasfilename()),
|
||||
)
|
||||
menu_file.add_command(
|
||||
label="Close",
|
||||
command=lambda: self.open_file(None),
|
||||
)
|
||||
|
||||
menu_edit = Menu(menubar)
|
||||
menubar.add_cascade(menu=menu_edit, label="Edit")
|
||||
menu_edit.add_command(
|
||||
label="New image",
|
||||
command=self.add_image,
|
||||
state="disabled",
|
||||
)
|
||||
menu_edit.add_command(
|
||||
label="Delete image",
|
||||
command=self.delete_image,
|
||||
state="disabled",
|
||||
)
|
||||
|
||||
return menu_file, menu_edit
|
||||
|
||||
def update_menus(self) -> None:
|
||||
for file_index in [2, 3, 4]:
|
||||
self.menu_file.entryconfigure(
|
||||
file_index,
|
||||
state=("normal" if self.current_file is not None else "disabled"),
|
||||
)
|
||||
for edit_index in [0, 1]:
|
||||
self.menu_edit.entryconfigure(
|
||||
edit_index,
|
||||
state=("normal" if self.current_image is not None else "disabled"),
|
||||
)
|
||||
|
||||
def make_explorer(self) -> ttk.Treeview:
|
||||
explorer_container = ttk.Frame(self)
|
||||
explorer_container.grid(column=0, row=0, sticky=(N, S, W))
|
||||
|
||||
explorer = ttk.Treeview(explorer_container, columns=("size"))
|
||||
explorer.heading("#0", text="name")
|
||||
explorer.heading("size", text="size")
|
||||
explorer.column("#0", width=100, anchor="w")
|
||||
explorer.column("size", width=100, anchor="w")
|
||||
explorer.grid(row=0, column=0, sticky=(N, S, W))
|
||||
explorer.bind("<<TreeviewSelect>>", self.update)
|
||||
|
||||
yscrollbar = ttk.Scrollbar(
|
||||
explorer_container, orient="vertical", command=explorer.yview
|
||||
)
|
||||
yscrollbar.grid(row=0, column=1, sticky=(N, S, W))
|
||||
explorer.configure(yscrollcommand=yscrollbar.set)
|
||||
|
||||
explorer_container.grid_rowconfigure(0, weight=1)
|
||||
explorer_container.grid_columnconfigure(0, weight=1)
|
||||
|
||||
return explorer
|
||||
|
||||
def make_canvas(self) -> Canvas:
|
||||
view = ttk.Frame(self, height=500, width=500)
|
||||
view.grid(column=1, row=0, sticky=(N, S, E, W))
|
||||
|
||||
canvas = Canvas(view, width=200, height=200, background="white")
|
||||
canvas.grid(column=0, row=0, sticky=(N, S, E, W))
|
||||
|
||||
return canvas
|
||||
|
||||
def save_file(self, path: Optional[str] = None) -> None:
|
||||
if path == "":
|
||||
path = filedialog.asksaveasfilename()
|
||||
# TODO
|
||||
self.open_file(path)
|
||||
|
||||
def open_file(self, path: Optional[str]) -> None:
|
||||
ids = self.explorer.get_children()
|
||||
if len(ids) > 0:
|
||||
self.explorer.delete(*ids)
|
||||
|
||||
if path is None:
|
||||
self.parent.title(f"Watchy Image Editor")
|
||||
self.current_file = None
|
||||
else:
|
||||
self.parent.title(
|
||||
f"Watchy Image Editor - {'New file' if path == '' else path}"
|
||||
)
|
||||
self.current_file = File(path if path != "" else None)
|
||||
for i, image in enumerate(self.current_file.images):
|
||||
self.explorer.insert(
|
||||
"",
|
||||
"end",
|
||||
iid=str(i),
|
||||
text=image.name,
|
||||
values=[f"{image.width}x{image.height}"],
|
||||
)
|
||||
self.update()
|
||||
|
||||
def add_image(self) -> None:
|
||||
pass # TODO
|
||||
|
||||
def delete_image(self) -> None:
|
||||
pass # TODO
|
||||
|
||||
@property
|
||||
def current_image(self) -> Optional[Image]:
|
||||
if self.explorer.focus() == "":
|
||||
return None
|
||||
else:
|
||||
return self.current_file.images[int(self.explorer.focus())]
|
||||
|
||||
def update_canvas(self) -> None:
|
||||
image = self.current_image
|
||||
scale = 3
|
||||
if image is not None:
|
||||
self.canvas.configure(
|
||||
width=(image.width * scale),
|
||||
height=(image.height * scale),
|
||||
background="white",
|
||||
)
|
||||
self.canvas.create_rectangle(
|
||||
0,
|
||||
0,
|
||||
(image.width * scale),
|
||||
(image.height * scale),
|
||||
fill="white",
|
||||
outline="",
|
||||
)
|
||||
for x in range(image.width):
|
||||
for y in range(image.height):
|
||||
if image.get_pixel(x, y):
|
||||
self.canvas.create_rectangle(
|
||||
x * scale,
|
||||
y * scale,
|
||||
(x + 1) * scale,
|
||||
(y + 1) * scale,
|
||||
fill="black",
|
||||
outline="",
|
||||
)
|
||||
|
||||
def update(self, *args) -> None:
|
||||
self.update_menus()
|
||||
self.update_canvas()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = Tk()
|
||||
root.option_add("*tearOff", FALSE)
|
||||
|
||||
app = App(root)
|
||||
app.pack(fill="both", expand=True)
|
||||
|
||||
app.mainloop()
|
||||
Reference in New Issue
Block a user