]> git.mar77i.info Git - zenbook_gui/commitdiff
basic sketch out for bookpaint
authormar77i <mar77i@protonmail.ch>
Fri, 9 May 2025 23:44:47 +0000 (01:44 +0200)
committermar77i <mar77i@protonmail.ch>
Fri, 9 May 2025 23:44:47 +0000 (01:44 +0200)
bookpaint/bookpaint.py
bookpaint/color_menu.py [new file with mode: 0644]
bookpaint/draw_image.py
bookpaint/line_menu.py [new file with mode: 0644]
bookpaint/menu.py
bookpaint/page_menu.py [new file with mode: 0644]
layout/__init__.py [deleted file]
layout/bar_layout.py [deleted file]
layout/grid_layout.py [deleted file]
ui/slider.py

index 473f03dc050d05aefa60e4d0f3d8872a548f2958..e42e6147819a7c73b59cf638e0b030f43538835b 100644 (file)
@@ -1,3 +1,4 @@
+from functools import partial
 from pathlib import Path
 
 import pygame
@@ -5,15 +6,19 @@ import pygame
 from ui import Root
 
 from .book_manager import BookManager
+from .color_menu import ColorMenu
 from .draw_image import DrawImage
+from .line_menu import LineMenu
 from .menu import Menu
+from .page_menu import PageMenu
 
 
 # - color menu; set background (default for new pages) and foreground color
-#   - stub with a 6 hex-digit textinputs and a little rectangle
+#   - stub with a 6 hex-digit textinput and a little rectangle to show the color
 # - page menu; browse through next and prev pages using stylus
+# - line menu: choose line width and stroke method
 # - info layer (F3)
-# - list color and page menus in the esc menu but also using keyboard shortcuts c/p
+# - list color and page menus in the main menu but also using keyboard shortcuts c/p/l
 
 
 class BookPaint(Root):
@@ -25,15 +30,18 @@ class BookPaint(Root):
             pygame.display.set_mode((0, 0), pygame.FULLSCREEN),
             pygame.font.Font(None, size=96),
         )
-        self.menu = Menu(self)
+        self.color_menu = ColorMenu(self)
         self.draw_image = DrawImage(self, "white")
-        book_path = Path("/dev/shm/book")
+        self.line_menu = LineMenu(self)
+        self.page_menu = PageMenu(self)
+        self.menu = Menu(self)
+        book_path = Path("book")
         if not book_path.exists():
             book_path.mkdir(0o755)
         self.book_manager = BookManager(book_path)
 
-    def key_escape(self):
-        self.menu.activate()
+    def show_menu(self, menu_attr):
+        getattr(self, menu_attr).activate()
 
     def save_file(self):
         self.book_manager.save_file(self.draw_image.surface)
@@ -56,7 +64,12 @@ class BookPaint(Root):
         self.set_image(self.book_manager.prev_file())
 
     KEY_METHODS = {
-        frozenset(): {pygame.K_ESCAPE: key_escape},
+        frozenset(): {
+            pygame.K_ESCAPE: partial(show_menu, menu_attr="menu"),
+            pygame.K_c: partial(show_menu, menu_attr="color_menu"),
+            pygame.K_l: partial(show_menu, menu_attr="line_menu"),
+            pygame.K_p: partial(show_menu, menu_attr="page_menu"),
+        },
         frozenset({pygame.KMOD_CTRL}): {
             pygame.K_s: save_file,
             pygame.K_PAGEDOWN: next_file,
diff --git a/bookpaint/color_menu.py b/bookpaint/color_menu.py
new file mode 100644 (file)
index 0000000..ead9b8d
--- /dev/null
@@ -0,0 +1,67 @@
+import pygame
+
+from ui import Button, Label, Modal, TextInput
+
+
+class ColorMenu(Modal):
+    def __init__(self, parent):
+        super().__init__(parent)
+        size = self.surf.get_size()
+        self.size_third = tuple(a // 3 for a in size)
+        button_height = 128
+        y = self.size_third[1]
+        Label(
+            self,
+            pygame.Rect((self.size_third[0], y), (self.size_third[0], 128)),
+            "Color Menu",
+            Label.HAlign.CENTER,
+        )
+        y += button_height + 16
+        self.input = TextInput(
+            self,
+            pygame.Rect((self.size_third[0], y), (self.size_third[0], 128)),
+            self.set_color,
+            "",
+        )
+        y += (button_height + 16) * 2
+        Button(
+            self,
+            pygame.Rect((self.size_third[0], y), (self.size_third[0], 128)),
+            "Close",
+            self.deactivate
+        )
+
+    def activate(self):
+        super().activate()
+        self.input.value = f"{int(self.parent.draw_image.color) >> 8:06x}"
+
+    def set_color(self, value):
+        color = self.parent.draw_image.color
+        try:
+            color = pygame.Color(f"0x{value}")
+        except ValueError:
+            try:
+                color = pygame.Color(value)
+            except ValueError:
+                pass
+        self.parent.draw_image.color = color
+
+    def draw_modal(self):
+        super().draw_modal()
+        rect = pygame.Rect(self.size_third, self.size_third)
+        rect.inflate_ip((32, 32))
+        pygame.draw.rect(self.surf, "black", rect)
+        pygame.draw.rect(self.surf, "gray", rect, 1)
+
+    def draw(self):
+        super().draw()
+        pygame.draw.rect(
+            self.surf,
+            self.parent.draw_image.color,
+            pygame.Rect(
+                (self.size_third[0], self.size_third[1] + 288),
+                (self.size_third[0], 128),
+            ),
+        )
+
+    KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: Modal.deactivate}}
index 46b9075f4df4498b2bc729cc897b912f3fe5d1b9..a55dc182f93ad5cb471744a87a60cfcbcb848474 100644 (file)
@@ -6,37 +6,50 @@ from ui import Child
 from vectors import StrokeRoundLine, StrokeSquareLine
 
 
-class StrokeMethod(Enum):
-    ROUND = auto()
-    SQUARE = auto()
-    PYGAME = auto()
-
-
 class DrawImage(Child):
-    FINGER_TIMEOUT = 30
+    class StrokeMethod(Enum):
+        ROUND = auto()
+        SQUARE = auto()
+        PYGAME = auto()
+
+    DEFAULT_STROKE_METHOD = StrokeMethod.ROUND
 
     def __init__(self, parent, color):
         super().__init__(parent)
+        if not isinstance(color, pygame.Color):
+            color = pygame.Color(color)
         self.color = color
         self.line_width = 1
-
-        self.surface = pygame.Surface(self.surf.get_size(), 0, 24)
-        self.clear()
+        self.image = pygame.Surface(self.surf.get_size(), 0, 24)
+        self.stroke_method = self.DEFAULT_STROKE_METHOD
         self.image_dirty = False
-        self.stroke_method = next(iter(StrokeMethod))
         self.prev_pos = None
+        self.clear()
 
     def clear(self):
-        self.surface.fill("black")
+        self.image.fill("black")
         self.image_dirty = False
         self.dirty = True
+        self.prev_pos = None
 
-    def dot(self, pos):
-        pygame.draw.circle(self.surf, self.color, pos, self.line_width // 2)
+    def dot(self, surf, pos):
+        if self.stroke_method == self.StrokeMethod.SQUARE:
+            hw = self.line_width // 2
+            pygame.draw.rect(
+                surf,
+                self.color,
+                pygame.Rect(
+                    (pos[0] - hw, pos[1] - hw), (self.line_width, self.line_width)
+                )
+            )
+        elif self.line_width // 2 == 0:
+            surf.set_at(pos, self.color)
+        else:
+            pygame.draw.circle(surf, self.color, pos, self.line_width // 2)
 
     def draw(self):
-        self.surf.blit(self.surface, (0, 0))
-        self.dot(pygame.mouse.get_pos())
+        self.surf.blit(self.image, (0, 0))
+        self.dot(self.surf, pygame.mouse.get_pos())
 
     def handle_mousebuttondown(self, ev):
         if ev.button == 1:
@@ -44,47 +57,32 @@ class DrawImage(Child):
             self.dirty = True
 
     def handle_mousemotion(self, ev):
-        if self.prev_pos is not None:
-            if self.stroke_method == StrokeMethod.ROUND:
-                if ev.pos == self.prev_pos:
-                    self.dot(ev.pos)
-                else:
-                    StrokeRoundLine(
-                        self.prev_pos, ev.pos, self.line_width
-                    ).draw(self.surface, self.color)
-            elif self.stroke_method == StrokeMethod.SQUARE:
-                if ev.pos == self.prev_pos:
-                    pygame.draw.rect(
-                        self.surface,
-                        self.color,
-                        pygame.Rect(
-                            (
-                                ev.pos[0] - self.line_width // 2,
-                                ev.pos[1] - self.line_width // 2,
-                            ),
-                            (self.line_width, self.line_width),
-                        )
-                    )
-                else:
-                    StrokeSquareLine(
-                        self.prev_pos, ev.pos, self.line_width
-                    ).draw(self.surface, self.color)
-            elif self.stroke_method == StrokeMethod.PYGAME:
-                if ev.pos == self.prev_pos:
-                    self.dot(ev.pos)
-                else:
-                    pygame.draw.line(
-                        self.surface, self.color, self.prev_pos, ev.pos, self.line_width
-                    )
-            self.prev_pos = ev.pos
-            self.image_dirty = True
         self.dirty = True
+        if self.prev_pos is None:
+            return
+        self.image_dirty = True
+        if ev.pos == self.prev_pos:
+            self.dot(self.image, ev.pos)
+            return
+        if self.stroke_method == self.StrokeMethod.ROUND:
+            StrokeRoundLine(
+                self.prev_pos, ev.pos, self.line_width
+            ).draw(self.image, self.color)
+        elif self.stroke_method == self.StrokeMethod.SQUARE:
+            StrokeSquareLine(
+                self.prev_pos, ev.pos, self.line_width
+            ).draw(self.image, self.color)
+        elif self.stroke_method == self.StrokeMethod.PYGAME:
+            pygame.draw.line(
+                self.image, self.color, self.prev_pos, ev.pos, self.line_width
+            )
+        self.prev_pos = ev.pos
 
     def handle_mousebuttonup(self, ev):
-        if ev.button == 1 and self.prev_pos is not None:
+        if ev.button == 1:
             self.handle_mousemotion(ev)
             self.prev_pos = None
 
     def set_image(self, surf):
-        self.surface.blit(surf, (0, 0))
+        self.image.blit(surf, (0, 0))
         self.dirty = True
diff --git a/bookpaint/line_menu.py b/bookpaint/line_menu.py
new file mode 100644 (file)
index 0000000..a3468d5
--- /dev/null
@@ -0,0 +1,78 @@
+import pygame
+
+from ui import DropDown, Label, Modal, Slider, TextInput
+
+from .draw_image import DrawImage
+
+
+class LineMenu(Modal):
+    def __init__(self, parent):
+        super().__init__(parent)
+        size = self.surf.get_size()
+        self.size_third = tuple(a // 3 for a in size)
+        button_height = 128
+        x = self.size_third[0]
+        y = self.size_third[1]
+        Label(
+            self,
+            pygame.Rect((x, y), (x, button_height)),
+            "Line Menu",
+            Label.HAlign.CENTER,
+        )
+        y += button_height + 16
+        self.slider = Slider(
+            self,
+            pygame.Rect((x, y), (x, button_height)),
+            Slider.Direction.HORIZONTAL,
+            0,
+            button_height,
+            self.set_line_width,
+        )
+        y += button_height + 16
+        self.input = TextInput(
+            self,
+            pygame.Rect((x, y), (x, button_height)),
+            self.set_line_width,
+            "",
+        )
+        y += button_height + 16
+        self.stroke_methods = list(DrawImage.StrokeMethod)
+        self.stroke_method = DropDown(
+            self,
+            pygame.Rect((self.size_third[0], y), (x, 128)),
+            self.parent.draw_image.stroke_method.name.title(),
+            [m.name.title() for m in self.stroke_methods],
+            self.set_stroke_method,
+        )
+
+    def activate(self):
+        super().activate()
+        self.update_value(self.parent.draw_image.line_width)
+
+    def set_line_width(self, value):
+        if isinstance(value, str):
+            value = int(value)
+        if value <= 0:
+            value = 1
+        self.parent.draw_image.line_width = value
+        self.update_value(value)
+
+    def set_stroke_method(self, value):
+        value = self.stroke_methods[value]
+        self.parent.draw_image.stroke_method = value
+        self.stroke_method.value = value.name.title()
+
+    def update_value(self, value):
+        self.slider.value = value
+        self.input.value = str(value)
+        self.dirty = True
+
+    def draw_modal(self):
+        super().draw_modal()
+        rect = pygame.Rect(self.size_third, self.size_third)
+        rect.inflate_ip((32, 32))
+        pygame.draw.rect(self.surf, "black", rect)
+        pygame.draw.rect(self.surf, "gray", rect, 1)
+
+
+    KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: Modal.deactivate}}
index 3b836b6aba951b096e0a0f0bc9ea7dfd12914f25..cbfe73bf0746ac85fbbdcc20b6433b78e29f4c9a 100644 (file)
@@ -1,21 +1,71 @@
+from functools import partial
+
 import pygame
 
-from ui import (
-    Button,
-    Modal,
-)
+from ui import Button, Modal
 
 
 class Menu(Modal):
     def __init__(self, parent):
+        from .bookpaint import BookPaint
+
+        self.parent: BookPaint
         super().__init__(parent)
-        size = self.root.surf.get_size()
-        width_third = size[0] // 3
+        self.suspended = False
+        size = self.surf.get_size()
+        self.size_third = tuple(a // 3 for a in size)
+        height_two_thirds = size[1] * 2 // 3
+        self.button_height = 128
         Button(
             self,
-            pygame.Rect((width_third, size[1] * 2 // 3), (width_third, 128)),
+            pygame.Rect(
+                (self.size_third[0], height_two_thirds),
+                (self.size_third[0], self.button_height),
+            ),
             "Quit",
             self.root.handle_quit,
         )
+        y = size[1] // 3
+        Button(
+            self,
+            pygame.Rect(
+                (self.size_third[0], y), (self.size_third[0], self.button_height)
+            ),
+            "Color",
+            partial(self.parent.show_menu, menu_attr="color_menu"),
+        )
+        y += self.button_height + 16
+        Button(
+            self,
+            pygame.Rect(
+                (self.size_third[0], y), (self.size_third[0], self.button_height)
+            ),
+            "Line",
+            partial(self.parent.show_menu, menu_attr="line_menu"),
+        )
+        y += self.button_height + 16
+        Button(
+            self,
+            pygame.Rect(
+                (self.size_third[0], y), (self.size_third[0], self.button_height)
+            ),
+            "Page",
+            partial(self.parent.show_menu, menu_attr="page_menu"),
+        )
+        assert y + self.button_height < height_two_thirds
+
+    def draw(self):
+        if self.root.focus_stack[-1] is self:
+            super().draw()
+
+    def draw_modal(self):
+        super().draw_modal()
+        rect = pygame.Rect(
+            self.size_third,
+            (self.size_third[0], self.size_third[1] + self.button_height),
+        )
+        rect.inflate_ip((32, 32))
+        pygame.draw.rect(self.surf, "black", rect)
+        pygame.draw.rect(self.surf, "gray", rect, 1)
 
     KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: Modal.deactivate}}
diff --git a/bookpaint/page_menu.py b/bookpaint/page_menu.py
new file mode 100644 (file)
index 0000000..2f19c8f
--- /dev/null
@@ -0,0 +1,14 @@
+import pygame
+
+from ui import Label, Modal
+
+
+class PageMenu(Modal):
+    def __init__(self, parent):
+        super().__init__(parent)
+        Label(self, pygame.Rect((100, 100), (200, 128)), "Page Menu")
+
+    def draw_modal(self):
+        pass
+
+    KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: Modal.deactivate}}
diff --git a/layout/__init__.py b/layout/__init__.py
deleted file mode 100644 (file)
index ebec713..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-from .bar_layout import BarLayout
-from .grid_layout import GridLayout
-
-__all__ = ["BarLayout", "GridLayout"]
diff --git a/layout/bar_layout.py b/layout/bar_layout.py
deleted file mode 100755 (executable)
index 932ebc4..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-import pygame
-
-
-class BarLayout:
-    def __init__(self, rect, pad):
-        self.rect = rect
-        self.pad = pad
-        self.rects = []
-
-    def get_rect(self):
-        rect = pygame.Rect((0, self.rect.top), (0, self.rect.height))
-        self.rects.append(rect)
-        len_rects = len(self.rects)
-        left = self.rect.left + self.pad // 2
-        width = (self.rect.width - len_rects * self.pad) // len_rects
-        for i, r in enumerate(self.rects):
-            r.left = left + i * (self.pad + width)
-            r.width = width
-        return rect
diff --git a/layout/grid_layout.py b/layout/grid_layout.py
deleted file mode 100644 (file)
index 38b144b..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-import pygame
-
-
-class GridLayout:
-    def __init__(self, rect, size):
-        self.rect = rect
-        self.size = size
-
-    def get_rect(self, pos, span=(1, 1)):
-        if pos[0] < 0 or pos[0] >= self.size[0] or pos[1] < 0 or pos[1] >= self.size[1]:
-            raise ValueError(f"coordinate not on grid: {pos}")
-        if (
-            span[0] < 1
-            or pos[0] + span[0] > self.size[0]
-            or span[1] < 1
-            or pos[0] + span[0] > self.size[0]
-        ):
-            raise ValueError(f"span exceeds the grid: {span}")
-        size = (self.rect.width / self.size[0], self.rect.height / self.size[1])
-        return pygame.Rect(
-            (
-                int(self.rect.left + pos[0] * size[0]),
-                int(self.rect.top + pos[1] * size[1]),
-            ),
-            (size[0] * span[0], size[1] * span[1]),
-        )
index f45145b0bb62bf12eb58da4aaa38f1b3ff591607..7ce43e19052efcc6ab4e78b8a42db4dffa4d7198 100644 (file)
@@ -1,3 +1,4 @@
+from enum import Enum, auto
 import pygame
 
 from .child import Child
@@ -11,7 +12,7 @@ class HorizontalSlider(Child):
         super().__init__(parent)
         self.rect = rect
         self.extent = (
-            self._get_extent_base() - 1 if handle_size is None else handle_size
+            self._get_extent_base() - (1 if handle_size is None else handle_size)
         )
         self.value = value
         self.handle_size = handle_size
@@ -178,12 +179,13 @@ class VerticalSlider(HorizontalSlider):
 
 
 class Slider:
-    HORIZONTAL = 0
-    VERTICAL = 1
+    class Direction(Enum):
+        HORIZONTAL = auto()
+        VERTICAL = auto()
 
     def __new__(cls, parent, rect, direction, value=0, handle_size=None, callback=None):
-        if direction == cls.HORIZONTAL:
+        if direction == cls.Direction.HORIZONTAL:
             klass = HorizontalSlider
-        else:  # direction == cls.VERTICAL
+        else:  # direction == cls.Direction.VERTICAL
             klass = VerticalSlider
         return klass(parent, rect, value, handle_size, callback)