]> git.mar77i.info Git - zenbook_gui/commitdiff
MVP bookpaint. collapse some unnecessary abstractions
authormar77i <mar77i@protonmail.ch>
Tue, 28 Jan 2025 16:23:23 +0000 (17:23 +0100)
committermar77i <mar77i@protonmail.ch>
Tue, 28 Jan 2025 16:23:23 +0000 (17:23 +0100)
bookpaint.py [new file with mode: 0755]
bookpaint/__init__.py [new file with mode: 0644]
bookpaint/bookpaint.py [new file with mode: 0644]
bookpaint/draw_ui.py [new file with mode: 0644]
ui/ui.py

diff --git a/bookpaint.py b/bookpaint.py
new file mode 100755 (executable)
index 0000000..bfdd01a
--- /dev/null
@@ -0,0 +1,15 @@
+#!/usr/bin/env python3
+
+import sys
+from contextlib import redirect_stdout
+from io import StringIO
+from pathlib import Path
+
+sys.path.append(str(Path(__file__).parent))
+
+from bookpaint.bookpaint import BookPaint
+
+with redirect_stdout(StringIO()):
+    import pygame  # type: ignore
+
+BookPaint().run()
diff --git a/bookpaint/__init__.py b/bookpaint/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/bookpaint/bookpaint.py b/bookpaint/bookpaint.py
new file mode 100644 (file)
index 0000000..50162f5
--- /dev/null
@@ -0,0 +1,70 @@
+import pygame
+
+from ui.ui import Button, DrawImage, FPSWidget, Modal, Root
+
+
+class BookPaintMenu(Modal):
+    def __init__(self, root):
+        super().__init__(root)
+        size = self.surf.get_size()
+        root.children.extend(
+            (
+                Button(
+                    root,
+                    pygame.Rect((size[0] - 128, 0), (128, 96)),
+                    "×",
+                    root.quit,
+                ),
+                Button(
+                    root,
+                    pygame.Rect((size[0] - 256, 0), (128, 96)),
+                    "_",
+                    root.iconify,
+                ),
+                FPSWidget(root),
+            ),
+        )
+
+    def key_escape(self):
+        self.deactivate()
+
+    KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: key_escape}}
+
+    def draw(self):
+        self.root.surf.fill(0x333333)
+        super().draw()
+
+
+class BookPaint(Root):
+    BACKGROUND_COLOR = "black"
+
+    def __init__(self):
+        pygame.init()
+        super().__init__(
+            pygame.display.set_mode(flags=pygame.FULLSCREEN),
+            pygame.font.Font(None, size=96),
+        )
+        self.children.extend(
+            (
+                DrawImage(
+                    self,
+                    self.surf.get_rect(),
+                    self.BACKGROUND_COLOR,
+                ),
+                FPSWidget(
+                    self,
+                ),
+            )
+        )
+
+    def key_escape(self):
+        if isinstance(self.children[0], DrawImage):
+            BookPaintMenu(self)
+
+    def quit(self):
+        self.running = False
+
+    def iconify(self):
+        pygame.display.iconify()
+
+    KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: key_escape}}
diff --git a/bookpaint/draw_ui.py b/bookpaint/draw_ui.py
new file mode 100644 (file)
index 0000000..b73c259
--- /dev/null
@@ -0,0 +1,42 @@
+import pygame
+
+from ui.ui import Child
+
+
+class DrawImage(Child):
+    def __init__(self, root, rect, background_color=None):
+        super().__init__(root)
+        self.pos = rect.topleft
+        self.surface = pygame.Surface(rect.size, 0, 24)
+        if background_color is not None:
+            self.surface.fill(background_color)
+        self.last_pos = None
+        self.color = "white"
+        self.line = pygame.draw.line
+
+    def draw_line(self, pos):
+        if self.last_pos is not None:
+            self.line(self.surface, self.color, self.last_pos, pos)
+        else:
+            self.surface.set_at(pos, self.color)
+
+    def handle_mousebuttondown(self, ev):
+        self.draw_line(ev.pos)
+        self.last_pos = ev.pos
+        self.dirty = True
+
+    def handle_mousemotion(self, ev):
+        if self.last_pos is None:
+            return
+        self.draw_line(ev.pos)
+        self.last_pos = ev.pos
+        self.dirty = True
+
+    def handle_mousebuttonup(self, ev):
+        self.draw_line(ev.pos)
+        if self.last_pos is not None:
+            self.last_pos = None
+        self.dirty = True
+
+    def draw(self):
+        self.surf.blit(self.surface, self.pos)
index 10ed74e0790bf3e2f1ff63a00e3011560c413607..61dbeb4222963027c84f13e5cfdaa41ed80c0839 100644 (file)
--- a/ui/ui.py
+++ b/ui/ui.py
@@ -39,10 +39,17 @@ class Parent(EventMethodDispatcher):
     def __init__(self):
         self.children = []
 
-    def handle_event(self, ev):
-        super().handle_event(ev)
-        for child in self.children:
-            child.handle_event(ev)
+    def get_event_objects(self):
+        return (self, *self.children)
+
+    def handle_event_for_child(self, child, ev):
+        child.handle_event(ev)
+        return True
+
+    def call_event_handlers(self, ev):
+        for child in self.get_event_objects():
+            if not self.handle_event_for_child(child, ev):
+                break
 
     def update(self):
         for child in self.children:
@@ -82,36 +89,7 @@ class Child(EventMethodDispatcher):
         pass
 
 
-class CursorParent(Parent):
-    def __init__(self):
-        super().__init__()
-        self.cursor: Cursor | None = None
-
-    def handle_event(self, ev):
-        super().handle_event(ev)
-        if self.cursor is not None:
-            self.cursor.handle_event(ev)
-
-    def update(self):
-        super().update()
-        if self.cursor is not None:
-            self.cursor.update()
-
-
-class CursorChild(Child):
-    @property
-    def cursor(self):
-        cursor = self.root.cursor
-        if cursor is not None and cursor.text_input is self:
-            return cursor
-        return None
-
-    @cursor.setter
-    def cursor(self, value):
-        self.root.cursor = value
-
-
-class Root(CursorParent):
+class Root(Parent):
     BACKGROUND_COLOR: pygame.Color
 
     def __init__(self, surf, font=None):
@@ -121,6 +99,8 @@ class Root(CursorParent):
         self.running = True
         self.dirty = False
         self.clock = pygame.time.Clock()
+        self.cursor: Cursor | None = None
+        self.stop_event = False
 
     def handle_quit(self, _):
         self.running = False
@@ -136,17 +116,33 @@ class Root(CursorParent):
 
     KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: key_escape}}
 
+    def get_event_objects(self):
+        return filter(None, (self, self.cursor, *self.children))
+
+    def handle_event_for_child(self, child, ev):
+        if child is not None:
+            child.handle_event(ev)
+        return self.running and not self.stop_event
+
     def draw(self):
         if hasattr(self, "BACKGROUND_COLOR"):
             self.surf.fill(self.BACKGROUND_COLOR)
         super().draw()
 
+    def update(self):
+        super().update()
+        if self.cursor is not None:
+            self.cursor.update()
+
     def run(self):
         while True:
             for ev in pygame.event.get():
-                self.handle_event(ev)
+                self.stop_event = False
+                self.call_event_handlers(ev)
                 if not self.running:
-                    return
+                    break
+            if not self.running:
+                break
             self.update()
             if self.dirty:
                 self.draw()
@@ -167,18 +163,15 @@ class Button(Child):
     def draw(self):
         if not self.pushed:
             value_color = "lime" if self.is_active else "gray"
-            frame_color = value_color
+            colors = ("black", value_color, value_color)
         else:
-            pygame.draw.rect(self.surf, "darkgray", self.rect)
-            frame_color = "lightgray"
-            value_color = "black"
-        fs = self.font.render(self.value, True, value_color)
-        pygame.draw.rect(self.surf, frame_color, self.rect, 8)
+            colors = ("darkgray", "lightgray", "black")
+        pygame.draw.rect(self.surf, colors[0], self.rect)
+        pygame.draw.rect(self.surf, colors[1], self.rect, 8)
+        fs = self.font.render(self.value, True, colors[2])
         fs_size = fs.get_size()
         center = self.rect.center
-        self.surf.blit(
-            fs, (center[0] - fs_size[0] // 2, center[1] - fs_size[1] // 2)
-        )
+        self.surf.blit(fs, (center[0] - fs_size[0] // 2, center[1] - fs_size[1] // 2))
 
     def handle_mousebuttondown(self, ev):
         if ev.button == 1 and self.rect.collidepoint(ev.pos):
@@ -587,7 +580,7 @@ class Cursor(Child):
     }
 
 
-class TextInput(CursorChild):
+class TextInput(Child):
     def __init__(self, root, rect, callback, value="", value_filter=None):
         super().__init__(root)
         self.rect = rect
@@ -596,6 +589,17 @@ class TextInput(CursorChild):
         self.value_filter = value_filter
         self.offset = 0
 
+    @property
+    def cursor(self):
+        cursor = self.root.cursor
+        if cursor is not None and cursor.text_input is self:
+            return cursor
+        return None
+
+    @cursor.setter
+    def cursor(self, value):
+        self.root.cursor = value
+
     def draw(self):
         pygame.draw.rect(self.surf, "black", self.rect)
         if self.cursor is not None:
@@ -744,6 +748,7 @@ class Modal(Child):
         self.children = root.children.copy()
         root.children.clear()
         root.children.append(self)
+        root.stop_event = True
         self.dirty = True
 
     def draw(self):