]> git.mar77i.info Git - zenbook_gui/commitdiff
bookpaint: visually finish the menu
authormar77i <mar77i@protonmail.ch>
Sun, 16 Feb 2025 23:08:08 +0000 (00:08 +0100)
committermar77i <mar77i@protonmail.ch>
Sun, 16 Feb 2025 23:15:12 +0000 (00:15 +0100)
14 files changed:
bookpaint/bookpaint.py
bookpaint/bookpaint_menu.py
layout/__init__.py
layout/bar_layout.py [new file with mode: 0755]
layout/grid_layout.py [new file with mode: 0644]
layout/layout.py [deleted file]
ui/__init__.py
ui/color_button.py [new file with mode: 0644]
ui/drop_down.py
ui/label.py
ui/parent.py
ui/root.py
zenbook_conf/xrandr.py
zenbook_conf/zenbook_conf.py

index 7ee8e483d3ce87249282ce2e03717a1da0305feb..6af8a4cd58377930307303c08f87ae05e48f0e48 100644 (file)
@@ -1,6 +1,7 @@
 import pygame
 
 from ui import Root
+from zenbook_conf.xrandr import XrandrConf
 
 from .bookpaint_menu import BookPaintMenu
 from .draw_image import DrawImage
@@ -11,14 +12,20 @@ class BookPaint(Root):
 
     def __init__(self):
         pygame.init()
+        self.current_display = 0
+        self.display_flags = pygame.FULLSCREEN
         super().__init__(
-            pygame.display.set_mode((0, 0), pygame.FULLSCREEN, display=0),
+            pygame.display.set_mode(
+                (0, 0), self.display_flags, display=self.current_display
+            ),
             pygame.font.Font(None, size=96),
         )
-        self.background_color = "blue"
+        self.background_color = self.BACKGROUND_COLOR
+        self.foreground_color = "white"
+        self.menu = BookPaintMenu(self)
         self.draw_image = DrawImage(self)
         self.draw_image.clear(self.background_color)
-        self.menu = BookPaintMenu(self)
+        self.xrandr_conf = XrandrConf(True)
 
     def key_escape(self):
         self.menu.activate()
@@ -27,3 +34,23 @@ class BookPaint(Root):
 
     def quit(self):
         self.running = False
+
+    def next_display(self):
+        self.xrandr_conf.renew()
+        if self.xrandr_conf.count_active() != len(pygame.display.get_desktop_sizes()):
+            pygame.display.quit()
+            pygame.display.init()
+            reset = True
+        else:
+            reset = False
+        num_displays = len(pygame.display.get_desktop_sizes())
+        current_display = (self.current_display + 1) % num_displays
+        if current_display != self.current_display:
+            self.current_display = current_display
+            reset = True
+        if not reset:
+            return
+        self.surf = pygame.display.set_mode(
+            (0, 0), self.display_flags, display=self.current_display
+        )
+        self.dirty = True
index 314e0e23fd2ec6f44f0f1081825a70be08d42132..6bb376acdf3db24d286390c7f7df92fc3c79886c 100644 (file)
@@ -1,6 +1,18 @@
 import pygame
 
-from ui import Button, Modal
+from layout import BarLayout, GridLayout
+from ui import (
+    Button,
+    CenterLabel,
+    ColorButton,
+    DropDown,
+    Modal,
+    Rect,
+    RepeatButton,
+    RightLabel,
+    Slider,
+    TextInput,
+)
 
 
 class BookPaintMenu(Modal):
@@ -8,13 +20,157 @@ class BookPaintMenu(Modal):
         super().__init__(parent)
         Button(
             self,
-            pygame.Rect((200, 200), (256, 128)),
+            pygame.Rect((self.surf.get_width() - 128, 0), (128, 128)),
             "×",
             self.root.quit,
         )
+        Button(
+            self,
+            pygame.Rect((self.surf.get_width() - 256, 0), (128, 128)),
+            "_",
+            pygame.display.iconify,
+        )
+        Button(
+            self,
+            pygame.Rect((self.surf.get_width() - 384, 0), (128, 128)),
+            "»",
+            self.root.next_display,
+        )
+        size = self.surf.get_size()
+        grid_layout = GridLayout(
+            pygame.Rect((512, 256), (size[0] - 1024, size[1] - 768)), (3, 6)
+        )
+        RightLabel(
+            self,
+            grid_layout.get_rect((0, 0)),
+            "Book Path",
+        )
+        RightLabel(
+            self,
+            grid_layout.get_rect((0, 1)),
+            "New Page Color",
+        )
+        RightLabel(
+            self,
+            grid_layout.get_rect((0, 2)),
+            "Line Color",
+        )
+        RightLabel(
+            self,
+            grid_layout.get_rect((0, 3)),
+            "Line Width",
+        )
+        RightLabel(
+            self,
+            grid_layout.get_rect((0, 4)),
+            "Stroke Method",
+        )
+        RightLabel(
+            self,
+            grid_layout.get_rect((0, 5)),
+            "Input",
+        )
+        rect = grid_layout.get_rect((1, 0), (2, 1))
+        pad = 96 - rect.height
+        self.path_input = TextInput(
+            self,
+            rect.inflate((pad, pad)),
+            self.set_book_path,
+            "book/",
+        )
+        ColorButton(
+            self,
+            grid_layout.get_rect((1, 1)).inflate((pad, pad)),
+            self.root.background_color,
+            lambda: None,
+        )
+        Button(
+            self,
+            grid_layout.get_rect((2, 1)).inflate((pad, pad)),
+            "Fill Page",
+            lambda: None,
+        )
+        ColorButton(
+            self,
+            grid_layout.get_rect((1, 2)).inflate((pad, pad)),
+            self.root.foreground_color,
+            lambda: None,
+        )
+        Slider(
+            self,
+            grid_layout.get_rect((1, 3)).inflate((pad, pad)),
+            Slider.HORIZONTAL,
+            handle_size=96,
+            callback=lambda _: None,
+        ),
+        DropDown(
+            self,
+            grid_layout.get_rect((1, 4)).inflate((pad, pad)),
+            "<choose>",
+            ["a", "b", "c"],
+            lambda _: None,
+        )
+        DropDown(
+            self,
+            grid_layout.get_rect((1, 5)).inflate((pad, pad)),
+            "<choose>",
+            ["a", "b", "c"],
+            lambda _: None,
+        )
+        rect = pygame.Rect(
+            (512, size[1] - 384),
+            (size[0] - 1024, 256),
+        )
+        Rect(self, rect, "black", "darkgray")
+
+        self.page_label = CenterLabel(
+            self,
+            pygame.Rect(
+                (rect.left, rect.top + rect.height // 4 - 48), (rect.width, 96)
+            ),
+            "0 / 0",
+        )
+        button_bar_layout = BarLayout(
+            pygame.Rect(
+                (rect.left, rect.top + rect.height * 3 // 4 - 48), (rect.width, 96)
+            ),
+            abs(pad),
+        )
+        Button(
+            self,
+            button_bar_layout.get_rect(),
+            "|<",
+            lambda: None,
+        ),
+        RepeatButton(
+            self,
+            button_bar_layout.get_rect(),
+            "<",
+            lambda: None,
+        ),
+        RepeatButton(
+            self,
+            button_bar_layout.get_rect(),
+            ">",
+            lambda: None,
+        ),
+        Button(
+            self,
+            button_bar_layout.get_rect(),
+            ">|",
+            lambda: None,
+        ),
+        Button(
+            self,
+            button_bar_layout.get_rect(),
+            "New",
+            lambda: None,
+        ),
 
     def key_escape(self):
         self.deactivate()
-        self.dirty = True
 
     KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: key_escape}}
+
+    def set_book_path(self, value):
+        print("book path", value)
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ebec713f8af71269004c1ff67d9b26d15988e119 100644 (file)
@@ -0,0 +1,4 @@
+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
new file mode 100755 (executable)
index 0000000..932ebc4
--- /dev/null
@@ -0,0 +1,19 @@
+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
new file mode 100644 (file)
index 0000000..38b144b
--- /dev/null
@@ -0,0 +1,26 @@
+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]),
+        )
diff --git a/layout/layout.py b/layout/layout.py
deleted file mode 100755 (executable)
index 5b32648..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-import pygame
-
-
-class MenuLayout:
-    def __init__(self, size, num_columns, center_at_y, spacer_y):
-        self.size = size
-        self.num_columns = num_columns
-        self.columns = [[] for _ in range(num_columns)]
-        self.center_at_y = center_at_y
-        self.spacer_y = spacer_y
-
-    def get_center_x(self, column):
-        return self.size[0] * (column + 1) // (self.num_columns + 1)
-
-    def get_rect(self, column, rect_size):
-        rect = pygame.Rect(
-            (self.get_center_x(column) - rect_size[0] // 2, 0), rect_size
-        )
-        self.columns[column].append(rect)
-        return rect
-
-    def __call__(self):
-        for column in self.columns:
-            total_height = (len(column) - 1) * self.spacer_y + sum(
-                r.height for r in column
-            )
-            y = 0
-            for rect in column:
-                rect.top = self.center_at_y - total_height // 2 + y
-                y += rect.height + self.spacer_y
-
-
-class BarLayout:
-    def __init__(self, size, center_at_y, spacer_x):
-        self.size = size
-        self.center_at_y = center_at_y
-        self.spacer_x = spacer_x
-        self.row = []
-
-    def get_rect(self, rect_size):
-        rect = pygame.Rect((0, self.center_at_y - rect_size[1] // 2), rect_size)
-        self.row.append(rect)
-        return rect
-
-    def __call__(self):
-        total_width = (len(self.row) - 1) * self.spacer_x + sum(
-            r.width for r in self.row
-        )
-        x = (self.size[0] - total_width) // 2
-        for rect in self.row:
-            rect.left = x
-            x += rect.width + self.spacer_x
index d1186595fc2c32f593374ac797ea653aaf8db120..381fda04861de81e6f7d91c475fd38db43f3b3de 100644 (file)
@@ -1,11 +1,12 @@
 from .button import Button
 from .child import Child
+from .color_button import ColorButton
 from .drop_down import DropDown
 from .event_method_dispatcher import EventMethodDispatcher
 from .fps_widget import FPSWidget
 from .icon import Icon
 from .icon_button import IconButton
-from .label import Label
+from .label import CenterLabel, Label, LeftLabel, RightLabel
 from .message_box import MessageBox
 from .modal import Modal
 from .parent import Parent
@@ -20,18 +21,22 @@ from .text_input import TextInput
 
 __all__ = [
     "Button",
+    "CenterLabel",
     "Child",
+    "ColorButton",
     "DropDown",
     "EventMethodDispatcher",
     "FPSWidget",
     "Icon",
     "IconButton",
     "Label",
+    "LeftLabel",
     "MessageBox",
     "Modal",
     "Parent",
     "Rect",
     "RepeatButton",
+    "RightLabel",
     "Root",
     "Scroll",
     "Slider",
diff --git a/ui/color_button.py b/ui/color_button.py
new file mode 100644 (file)
index 0000000..5c641a6
--- /dev/null
@@ -0,0 +1,17 @@
+import pygame
+
+from .button import Button
+
+
+class ColorButton(Button):
+    def __init__(self, parent, rect, color, callback, highlight=False):
+        super().__init__(parent, rect, "", callback, highlight)
+        self.color = color
+
+    def draw(self):
+        if not self.pushed:
+            colors = (self.color, "lime" if self.highlight else "gray")
+        else:
+            colors = ("darkgray", "lightgray")
+        pygame.draw.rect(self.surf, colors[0], self.rect)
+        pygame.draw.rect(self.surf, colors[1], self.rect, 8)
index 8a76442c604868b20912bcba70923a63f8b7f10d..61ce3d65994dbdc732048f79133b916c61f45dd0 100644 (file)
@@ -23,6 +23,8 @@ class DropDownMenu(Modal):
             rect.bottomleft, (rect.width, rect.height * len(entries))
         )
 
+    KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: Modal.deactivate}}
+
     def choose(self, i=None):
         self.deactivate()
         self.callback(i)
index 275c25df17b923d236ab435c5a4d8680ed8d52c3..343ac9afa32e0bddd4ece70739f49a5f4b1e220f 100644 (file)
@@ -1,15 +1,35 @@
 from .child import Child
 
 
-class Label(Child):
+class CenterLabel(Child):
     def __init__(self, parent, rect, value):
         super().__init__(parent)
         self.rect = rect
         self.value = value
 
+    def get_blit_pos(self):
+        fs_size = self.font.size(self.value)
+        return (
+            self.rect.centerx - fs_size[0] // 2, self.rect.centery - fs_size[1] // 2
+        )
+
     def draw(self):
         fs = self.font.render(self.value, True, "gray")
-        self.surf.blit(
-            fs,
-            (self.rect.left + 16, self.rect.centery - fs.get_height() // 2)
+        self.surf.blit(fs, self.get_blit_pos())
+
+
+class LeftLabel(CenterLabel):
+    def get_blit_pos(self):
+        return (
+            self.rect.left + 16,
+            self.rect.centery - self.font.size(self.value)[1] // 2,
         )
+
+
+class RightLabel(CenterLabel):
+    def get_blit_pos(self):
+        fs_size = self.font.size(self.value)
+        return (self.rect.right - 16 - fs_size[0], self.rect.centery - fs_size[1] // 2)
+
+
+Label = LeftLabel
index 9004a6d8d32342b25b086bbfe9b28ba081933996..47c9a772c60c7ed907198bd4e2ccb601ae5c6ffa 100644 (file)
@@ -4,6 +4,7 @@ from .event_method_dispatcher import EventMethodDispatcher
 class Parent(EventMethodDispatcher):
     running: bool
     stop_event: bool
+    parent: "root.Root"
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
@@ -29,5 +30,5 @@ class Parent(EventMethodDispatcher):
     def draw(self):
         super().draw()
         for child in self.children:
-            if child.enabled:
+            if child.enabled and child not in self.root.focus_stack:
                 child.draw()
index 6d6f24124d53dc3e6ebcf60d6b3145123f9f9512..3a04f5118bdc1db0550a238d5aac27f9d262ffc5 100644 (file)
@@ -40,7 +40,11 @@ class Root(Parent):
     def draw(self):
         if hasattr(self, "BACKGROUND_COLOR"):
             self.surf.fill(self.BACKGROUND_COLOR)
-        super().draw()
+        for parent in self.focus_stack:
+            if parent is self:
+                super().draw()
+            else:
+                parent.draw()
 
     def run(self):
         while True:
index db135f1c4f36e34075e6fe1c605ee7f6cdce9fa9..3f694156a00a52c1d68a3f85838dd1c819d7d8b4 100644 (file)
@@ -25,7 +25,8 @@ class XrandrConf:
     WIDTH_HEIGHT_PATTERN = re.compile(r"^\s{8}(h: width|v: height)\s+(\d+) ")
     OUTPUTS = ("eDP-1", "eDP-2")
 
-    def __init__(self):
+    def __init__(self, simple=False):
+        self.simple = simple
         self.current_conf = self.get_conf()
 
     @staticmethod
@@ -53,24 +54,23 @@ class XrandrConf:
             "outputs": [],
         }
 
-    @classmethod
-    def parse_output_line(cls, line):
-        name, connected, rest = cls.strip_each(line.split(" ", 2))
+    def parse_output_line(self, line):
+        name, connected, rest = self.strip_each(line.split(" ", 2))
         output = {
             "name": name,
-            "connected": cls.CONNECTED_MAPPING.get(
+            "connected": self.CONNECTED_MAPPING.get(
                 connected, "unknown connection"
             ),
         }
         if output["connected"] is False:
             return output
-        resolution = cls.RESOLUTION_PATTERN.search(rest)
+        resolution = self.RESOLUTION_PATTERN.search(rest)
         output["active"] = bool(resolution)
-        if resolution is None:
+        if self.simple or resolution is None:
             return output
-        parsed = cls.int_tuple(resolution.group(i) for i in range(1, 5))
+        parsed = self.int_tuple(resolution.group(i) for i in range(1, 5))
         direction_reflection = (
-            cls.DIRECTIONS_REFLECTIONS_PATTERN.search(rest)
+            self.DIRECTIONS_REFLECTIONS_PATTERN.search(rest)
         )
         end = direction_reflection.end()
         output.update(
@@ -82,14 +82,14 @@ class XrandrConf:
                 "reflection": direction_reflection.group(2) or "none",
                 "available_rotations_reflections": [
                     m.group(0)
-                    for m in cls.AVAILABLE_DIRECTIONS_REFLECTIONS_PATTERN.finditer(
+                    for m in self.AVAILABLE_DIRECTIONS_REFLECTIONS_PATTERN.finditer(
                         rest[end:rest.find(")", end)]
                     )
                 ],
                 "modes": []
             }
         )
-        mm_size = cls.MM_SIZE_PATTERN.search(rest)
+        mm_size = self.MM_SIZE_PATTERN.search(rest)
         if mm_size:
             output["mm_size"] = (
                 int(mm_size.group(1)), int(mm_size.group(2))
@@ -120,25 +120,25 @@ class XrandrConf:
             "mode_flags": cls.MODE_FLAGS_PATTERN.findall(line),
         }
 
-    @classmethod
-    def get_conf(cls):
+    def get_conf(self):
         screens = []
         current_screen = None
-        output = subprocess.check_output(["xrandr", "--verbose"], text=True)
-        lines = output.split(os.linesep)
+        lines = subprocess.check_output(
+            ["xrandr", "--verbose"], text=True
+        ).split(os.linesep)
         for i, line in enumerate(lines):
             if not line:
                 continue
             elif line.startswith("Screen "):
                 current_screen = len(screens)
-                screens.append(cls.parse_screen_line(line))
+                screens.append(self.parse_screen_line(line))
             elif not line.startswith("\t") and not line.startswith(" "):
-                screens[current_screen]["outputs"].append(cls.parse_output_line(line))
-            elif line.startswith("  "):
+                screens[current_screen]["outputs"].append(self.parse_output_line(line))
+            elif line.startswith("  ") and not self.simple:
                 output = screens[current_screen]["outputs"][-1]
                 if line[3] == " " or "modes" not in output:
                     continue
-                output["modes"].append(cls.parse_mode(*lines[i:i + 3]))
+                output["modes"].append(self.parse_mode(*lines[i:i + 3]))
         return screens
 
     @classmethod
@@ -148,6 +148,9 @@ class XrandrConf:
             args.extend((f"--{key.replace("_", "-")}", value))
         return subprocess.run(["xrandr", "--output", cls.OUTPUTS[output], *args])
 
+    def renew(self):
+        self.current_conf = self.get_conf()
+
     def update(self, mode):
         if mode not in ("single", "double", "vertical"):
             raise ValueError(f"unknown mode: {mode}")
@@ -159,7 +162,7 @@ class XrandrConf:
         if mode == "vertical":
             self.call(0, "auto", rotate="left")
             self.call(1, "auto", rotate="left", left_of=self.OUTPUTS[0])
-        self.current_conf = self.get_conf()
+        self.renew()
 
     def get_relevant_outputs(self, screen_id=0):
         screen = next(s for s in self.current_conf if s["screen"] == screen_id)
index 41f8d6688b794112da42e05ddf524986b41bdd06..4d776f3673627248e1332dc45a1fb60ba87ee38d 100644 (file)
@@ -172,10 +172,21 @@ class ZenbookConf(Root):
         pygame.display.iconify()
 
     def next_display(self):
-        current_display = (self.current_display + 1) % self.xrandr_conf.count_active()
+        self.xrandr_conf.renew()
+        if self.xrandr_conf.count_active() != len(pygame.display.get_desktop_sizes()):
+            pygame.display.quit()
+            pygame.display.init()
+            reset = True
+        else:
+            reset = False
+        num_displays = len(pygame.display.get_desktop_sizes())
+        current_display = (self.current_display + 1) % num_displays
         if current_display != self.current_display:
             self.current_display = current_display
-            self.surf = pygame.display.set_mode(
-                self.surf.get_size(), display=self.current_display
-            )
-            self.dirty = True
+            reset = True
+        if not reset:
+            return
+        self.surf = pygame.display.set_mode(
+            self.surf.get_size(), display=self.current_display
+        )
+        self.dirty = True