From: mar77i Date: Tue, 28 Jan 2025 16:23:23 +0000 (+0100) Subject: MVP bookpaint. collapse some unnecessary abstractions X-Git-Url: https://git.mar77i.info/?a=commitdiff_plain;h=9bffea06ccbb36930ddc822595f1171046feeee1;p=zenbook_gui MVP bookpaint. collapse some unnecessary abstractions --- diff --git a/bookpaint.py b/bookpaint.py new file mode 100755 index 0000000..bfdd01a --- /dev/null +++ b/bookpaint.py @@ -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 index 0000000..e69de29 diff --git a/bookpaint/bookpaint.py b/bookpaint/bookpaint.py new file mode 100644 index 0000000..50162f5 --- /dev/null +++ b/bookpaint/bookpaint.py @@ -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 index 0000000..b73c259 --- /dev/null +++ b/bookpaint/draw_ui.py @@ -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) diff --git a/ui/ui.py b/ui/ui.py index 10ed74e..61dbeb4 100644 --- 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):