commit f05f9c7fd4d30db536418ccfe55e95d14d846452 Author: Klemek Date: Wed Aug 25 01:37:33 2021 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..087e56f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__ +.vscode +.idea \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..df31134 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# Klemek's Watchy + +Soon some watchfaces ... + +Meanwhile I'm making an editor for images (WIP) : + +![preview](./watchy-image-editor/preview.png) \ No newline at end of file diff --git a/watchy-image-editor/main.py b/watchy-image-editor/main.py new file mode 100644 index 0000000..5d8118a --- /dev/null +++ b/watchy-image-editor/main.py @@ -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("<>", 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() diff --git a/watchy-image-editor/preview.png b/watchy-image-editor/preview.png new file mode 100644 index 0000000..35574bf Binary files /dev/null and b/watchy-image-editor/preview.png differ