From: mar77i Date: Sun, 16 Feb 2025 23:08:08 +0000 (+0100) Subject: bookpaint: visually finish the menu X-Git-Url: https://git.mar77i.info/?a=commitdiff_plain;h=4c4d9d57be3a51b6690febad265ee2c64643a18a;p=zenbook_gui bookpaint: visually finish the menu --- diff --git a/bookpaint/bookpaint.py b/bookpaint/bookpaint.py index 7ee8e48..6af8a4c 100644 --- a/bookpaint/bookpaint.py +++ b/bookpaint/bookpaint.py @@ -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 diff --git a/bookpaint/bookpaint_menu.py b/bookpaint/bookpaint_menu.py index 314e0e2..6bb376a 100644 --- a/bookpaint/bookpaint_menu.py +++ b/bookpaint/bookpaint_menu.py @@ -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)), + "", + ["a", "b", "c"], + lambda _: None, + ) + DropDown( + self, + grid_layout.get_rect((1, 5)).inflate((pad, pad)), + "", + ["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) diff --git a/layout/__init__.py b/layout/__init__.py index e69de29..ebec713 100644 --- a/layout/__init__.py +++ b/layout/__init__.py @@ -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 index 0000000..932ebc4 --- /dev/null +++ b/layout/bar_layout.py @@ -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 index 0000000..38b144b --- /dev/null +++ b/layout/grid_layout.py @@ -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 index 5b32648..0000000 --- a/layout/layout.py +++ /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 diff --git a/ui/__init__.py b/ui/__init__.py index d118659..381fda0 100644 --- a/ui/__init__.py +++ b/ui/__init__.py @@ -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 index 0000000..5c641a6 --- /dev/null +++ b/ui/color_button.py @@ -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) diff --git a/ui/drop_down.py b/ui/drop_down.py index 8a76442..61ce3d6 100644 --- a/ui/drop_down.py +++ b/ui/drop_down.py @@ -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) diff --git a/ui/label.py b/ui/label.py index 275c25d..343ac9a 100644 --- a/ui/label.py +++ b/ui/label.py @@ -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 diff --git a/ui/parent.py b/ui/parent.py index 9004a6d..47c9a77 100644 --- a/ui/parent.py +++ b/ui/parent.py @@ -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() diff --git a/ui/root.py b/ui/root.py index 6d6f241..3a04f51 100644 --- a/ui/root.py +++ b/ui/root.py @@ -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: diff --git a/zenbook_conf/xrandr.py b/zenbook_conf/xrandr.py index db135f1..3f69415 100644 --- a/zenbook_conf/xrandr.py +++ b/zenbook_conf/xrandr.py @@ -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) diff --git a/zenbook_conf/zenbook_conf.py b/zenbook_conf/zenbook_conf.py index 41f8d66..4d776f3 100644 --- a/zenbook_conf/zenbook_conf.py +++ b/zenbook_conf/zenbook_conf.py @@ -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