From d17074cf2ab667898f20f9033deb05a312ec7dc2 Mon Sep 17 00:00:00 2001 From: mar77i Date: Tue, 11 Feb 2025 09:25:57 +0100 Subject: [PATCH] split up ui/base.py --- ui/__init__.py | 5 +- ui/base.py | 170 ---------------------------------- ui/button.py | 2 +- ui/child.py | 61 ++++++++++++ ui/drop_down.py | 2 +- ui/event_method_dispatcher.py | 36 +++++++ ui/fps_widget.py | 2 +- ui/icon.py | 2 +- ui/label.py | 2 +- ui/modal.py | 3 +- ui/parent.py | 30 ++++++ ui/rect.py | 2 +- ui/root.py | 50 ++++++++++ ui/scroll.py | 3 +- ui/slider.py | 2 +- ui/spinner.py | 3 +- ui/switch.py | 2 +- ui/tab_bar.py | 3 +- ui/text_input.py | 2 +- 19 files changed, 198 insertions(+), 184 deletions(-) delete mode 100644 ui/base.py create mode 100644 ui/child.py create mode 100644 ui/event_method_dispatcher.py create mode 100644 ui/parent.py create mode 100644 ui/root.py diff --git a/ui/__init__.py b/ui/__init__.py index affe335..ec510ba 100644 --- a/ui/__init__.py +++ b/ui/__init__.py @@ -1,12 +1,15 @@ -from .base import Child, Parent, Root from .button import Button +from .child import Child 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 .message_box import MessageBox from .modal import Modal +from .parent import Parent +from .root import Root from .scroll import Scroll from .slider import Slider from .spinner import Spinner diff --git a/ui/base.py b/ui/base.py deleted file mode 100644 index 3eb6a3d..0000000 --- a/ui/base.py +++ /dev/null @@ -1,170 +0,0 @@ -from functools import cached_property, partial - -import pygame - -from .focus import FocusRootMixin - - -class EventMethodDispatcher: - MODS = (pygame.KMOD_CTRL, pygame.KMOD_ALT, pygame.KMOD_META, pygame.KMOD_SHIFT) - KEY_METHODS = {} - - def get_key_method(self, key, mod): - mods = set() - for mask in self.MODS: - if mod & mask: - mods.add(mask) - method = self.KEY_METHODS.get(frozenset(mods), {}).get(key) - if method is not None: - return partial(method, self) - return None - - def handle_keydown(self, ev): - if not self.KEY_METHODS: - return - key_method = self.get_key_method(ev.key, ev.mod) - if key_method is not None: - key_method() - - def handle_event(self, ev): - method_name = f"handle_{pygame.event.event_name(ev.type).lower()}" - if hasattr(self, method_name): - getattr(self, method_name)(ev) - - def update(self): - pass - - def draw(self): - pass - - -class Parent(EventMethodDispatcher): - root: "Root" - running: bool - stop_event: bool - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.children = [] - - def handle_event(self, ev): - super().handle_event(ev) - if not self.running or self.stop_event: - return - for child in self.children: - child.handle_event(ev) - if not self.running or self.stop_event: - break - - def update(self): - super().update() - for child in self.children: - child.update() - - def draw(self): - super().draw() - for child in self.children: - child.draw() - - -class Child(EventMethodDispatcher): - def __init__(self, parent): - self._parent = parent - if parent is not None: - parent.children.append(self) - - @property - def parent(self): - return self._parent - - @parent.setter - def parent(self, parent): - if self._parent is not None: - self._parent.children.remove(self) - self._parent = parent - if parent is not None: - parent.children.append(self) - if "root" in self.__dict__: - self.__dict__.pop("root") - - @cached_property - def root(self): - parent = self.parent - while hasattr(parent, "parent"): - parent = parent.root or parent.parent - if not isinstance(parent, Root): - raise AttributeError(f"No root found for {self}") - return parent - - @property - def dirty(self): - return self.root.dirty - - @dirty.setter - def dirty(self, value): - self.root.dirty = value - - @cached_property - def font(self): - return self.root.font - - @property - def surf(self): - parent = self.parent - while not hasattr(parent, "surf"): - parent = parent.root or parent.parent - return parent.surf - - @property - def running(self): - return self.root.running - - @property - def stop_event(self): - return self.root.stop_event - - -class Root(FocusRootMixin, Parent): - BACKGROUND_COLOR: pygame.Color - - def __init__(self, surf, font=None): - super().__init__() - self.font = font - self.running = True - self.dirty = False - self.surf = surf - self.clock = pygame.time.Clock() - self.stop_event = False - self.root = self - - def handle_quit(self, _=None): - self.running = False - - KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: handle_quit}} - - def handle_event(self, ev): - if ev.type in (pygame.WINDOWEXPOSED, pygame.ACTIVEEVENT): - self.dirty = True - return - super().handle_event(ev) - - def draw(self): - if hasattr(self, "BACKGROUND_COLOR"): - self.surf.fill(self.BACKGROUND_COLOR) - super().draw() - - def run(self): - while True: - for ev in pygame.event.get(): - self.stop_event = False - self.handle_event(ev) - if not self.running: - break - if not self.running: - break - self.update() - if self.dirty: - self.draw() - pygame.display.update() - self.dirty = False - self.clock.tick(60) diff --git a/ui/button.py b/ui/button.py index 3d6286f..52fe025 100644 --- a/ui/button.py +++ b/ui/button.py @@ -1,6 +1,6 @@ import pygame -from .base import Child +from .child import Child class Button(Child): diff --git a/ui/child.py b/ui/child.py new file mode 100644 index 0000000..efbec9a --- /dev/null +++ b/ui/child.py @@ -0,0 +1,61 @@ +from functools import cached_property + +from .event_method_dispatcher import EventMethodDispatcher +from .root import Root + + +class Child(EventMethodDispatcher): + def __init__(self, parent): + self._parent = parent + if parent is not None: + parent.children.append(self) + + @property + def parent(self): + return self._parent + + @parent.setter + def parent(self, parent): + if self._parent is not None: + self._parent.children.remove(self) + self._parent = parent + if parent is not None: + parent.children.append(self) + if "root" in self.__dict__: + self.__dict__.pop("root") + + @cached_property + def root(self): + parent = self.parent + while hasattr(parent, "parent"): + parent = parent.root or parent.parent + if not isinstance(parent, Root): + raise AttributeError(f"No root found for {self}") + return parent + + @property + def dirty(self): + return self.root.dirty + + @dirty.setter + def dirty(self, value): + self.root.dirty = value + + @cached_property + def font(self): + return self.root.font + + @property + def surf(self): + parent = self.parent + while not hasattr(parent, "surf"): + parent = parent.root or parent.parent + return parent.surf + + @property + def running(self): + return self.root.running + + @property + def stop_event(self): + return self.root.stop_event diff --git a/ui/drop_down.py b/ui/drop_down.py index 2673d3b..92076e8 100644 --- a/ui/drop_down.py +++ b/ui/drop_down.py @@ -2,9 +2,9 @@ from functools import partial import pygame -from .base import Parent from .button import Button from .modal import Modal +from .parent import Parent class DropDownMenu(Modal): diff --git a/ui/event_method_dispatcher.py b/ui/event_method_dispatcher.py new file mode 100644 index 0000000..8e54854 --- /dev/null +++ b/ui/event_method_dispatcher.py @@ -0,0 +1,36 @@ +from functools import partial + +import pygame + + +class EventMethodDispatcher: + MODS = (pygame.KMOD_CTRL, pygame.KMOD_ALT, pygame.KMOD_META, pygame.KMOD_SHIFT) + KEY_METHODS = {} + + def get_key_method(self, key, mod): + mods = set() + for mask in self.MODS: + if mod & mask: + mods.add(mask) + method = self.KEY_METHODS.get(frozenset(mods), {}).get(key) + if method is not None: + return partial(method, self) + return None + + def handle_keydown(self, ev): + if not self.KEY_METHODS: + return + key_method = self.get_key_method(ev.key, ev.mod) + if key_method is not None: + key_method() + + def handle_event(self, ev): + method_name = f"handle_{pygame.event.event_name(ev.type).lower()}" + if hasattr(self, method_name): + getattr(self, method_name)(ev) + + def update(self): + pass + + def draw(self): + pass diff --git a/ui/fps_widget.py b/ui/fps_widget.py index d14866b..25530ad 100644 --- a/ui/fps_widget.py +++ b/ui/fps_widget.py @@ -1,4 +1,4 @@ -from .base import Child +from .child import Child class FPSWidget(Child): diff --git a/ui/icon.py b/ui/icon.py index 9e3becb..9bcc485 100644 --- a/ui/icon.py +++ b/ui/icon.py @@ -1,4 +1,4 @@ -from .base import Child +from .child import Child class Icon(Child): diff --git a/ui/label.py b/ui/label.py index 20de04c..275c25d 100644 --- a/ui/label.py +++ b/ui/label.py @@ -1,4 +1,4 @@ -from .base import Child +from .child import Child class Label(Child): diff --git a/ui/modal.py b/ui/modal.py index e9115a5..20c3b37 100644 --- a/ui/modal.py +++ b/ui/modal.py @@ -1,7 +1,8 @@ import pygame -from .base import Child,Parent +from .child import Child from .focus import FocusableMixin +from .parent import Parent class Modal(FocusableMixin, Parent, Child): diff --git a/ui/parent.py b/ui/parent.py new file mode 100644 index 0000000..bfd87d2 --- /dev/null +++ b/ui/parent.py @@ -0,0 +1,30 @@ +from .event_method_dispatcher import EventMethodDispatcher + + +class Parent(EventMethodDispatcher): + root: "root.Root" + running: bool + stop_event: bool + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.children = [] + + def handle_event(self, ev): + super().handle_event(ev) + if not self.running or self.stop_event: + return + for child in self.children: + child.handle_event(ev) + if not self.running or self.stop_event: + break + + def update(self): + super().update() + for child in self.children: + child.update() + + def draw(self): + super().draw() + for child in self.children: + child.draw() diff --git a/ui/rect.py b/ui/rect.py index 863eb00..3959c31 100644 --- a/ui/rect.py +++ b/ui/rect.py @@ -1,6 +1,6 @@ import pygame -from .base import Child +from .child import Child class Rect(Child): diff --git a/ui/root.py b/ui/root.py new file mode 100644 index 0000000..89974c6 --- /dev/null +++ b/ui/root.py @@ -0,0 +1,50 @@ +import pygame + +from .parent import Parent +from .focus import FocusRootMixin + + +class Root(FocusRootMixin, Parent): + BACKGROUND_COLOR: pygame.Color + + def __init__(self, surf, font=None): + super().__init__() + self.font = font + self.running = True + self.dirty = False + self.surf = surf + self.clock = pygame.time.Clock() + self.stop_event = False + self.root = self + + def handle_quit(self, _=None): + self.running = False + + KEY_METHODS = {frozenset(set()): {pygame.K_ESCAPE: handle_quit}} + + def handle_event(self, ev): + if ev.type in (pygame.WINDOWEXPOSED, pygame.ACTIVEEVENT): + self.dirty = True + return + super().handle_event(ev) + + def draw(self): + if hasattr(self, "BACKGROUND_COLOR"): + self.surf.fill(self.BACKGROUND_COLOR) + super().draw() + + def run(self): + while True: + for ev in pygame.event.get(): + self.stop_event = False + self.handle_event(ev) + if not self.running: + break + if not self.running: + break + self.update() + if self.dirty: + self.draw() + pygame.display.update() + self.dirty = False + self.clock.tick(60) diff --git a/ui/scroll.py b/ui/scroll.py index 6a98cf0..4053676 100644 --- a/ui/scroll.py +++ b/ui/scroll.py @@ -1,6 +1,7 @@ import pygame -from .base import Child, Parent +from .child import Child +from .parent import Parent class Scroll(Parent, Child): diff --git a/ui/slider.py b/ui/slider.py index 5f1b510..cb64943 100644 --- a/ui/slider.py +++ b/ui/slider.py @@ -1,6 +1,6 @@ import pygame -from .base import Child +from .child import Child class Slider(Child): diff --git a/ui/spinner.py b/ui/spinner.py index 8154829..4dbfca4 100644 --- a/ui/spinner.py +++ b/ui/spinner.py @@ -5,8 +5,9 @@ from time import time import pygame -from .base import Child, Parent +from .child import Child from .button import Button +from .parent import Parent from .text_input import TextInput diff --git a/ui/switch.py b/ui/switch.py index 14279db..ed2921b 100644 --- a/ui/switch.py +++ b/ui/switch.py @@ -4,7 +4,7 @@ import pygame from colorsys import hsv_to_rgb from time import time -from .base import Child +from .child import Child class EaseInOutElastic: diff --git a/ui/tab_bar.py b/ui/tab_bar.py index 5d44e2e..c1732ec 100644 --- a/ui/tab_bar.py +++ b/ui/tab_bar.py @@ -2,8 +2,9 @@ from functools import partial import pygame -from .base import Child, Parent from .button import Button +from .child import Child +from .parent import Parent class TabBar(Parent, Child): diff --git a/ui/text_input.py b/ui/text_input.py index ed20171..772c621 100644 --- a/ui/text_input.py +++ b/ui/text_input.py @@ -7,7 +7,7 @@ from typing import Optional, Callable import pygame -from .base import Child +from .child import Child from .focus import FocusableMixin -- 2.51.0