From bdf318f8365f136f0d52423550d69cae9a7a3b9a Mon Sep 17 00:00:00 2001 From: mar77i Date: Thu, 19 Dec 2024 17:16:06 +0100 Subject: [PATCH] add some hierarchical structure --- ui.py | 121 ++++++++++++++++++--------- zenbook_conf.py | 215 +++++++++++++++++++++++++----------------------- 2 files changed, 194 insertions(+), 142 deletions(-) diff --git a/ui.py b/ui.py index 48ee7cd..34d88d6 100755 --- a/ui.py +++ b/ui.py @@ -9,6 +9,71 @@ import pygame tau = 2 * pi +class UIParent: + BACKGROUND_COLOR = None + + def __init__(self, surf, font): + self.surf = surf + self.font = font + self.running = True + self.dirty = False + self.children = [] + self.clock = pygame.time.Clock() + + def handle_event(self, ev): + if ev.type == pygame.QUIT: + self.running = False + return False + elif ev.type == pygame.WINDOWEXPOSED: + self.dirty = True + elif ev.type == pygame.KEYDOWN: + if ev.key == pygame.K_ESCAPE: + self.running = False + return False + method_name = f"handle_{pygame.event.event_name(ev.type).lower()}" + for child in self.children: + if not hasattr(child, method_name): + continue + getattr(child, method_name)(ev) + return True + + def update(self): + for child in self.children: + child.update() + + def draw(self): + background_color = getattr(self, "BACKGROUND_COLOR", None) + if background_color is not None: + self.surf.fill(background_color) + for child in self.children: + child.draw() + + def run(self): + while True: + for ev in pygame.event.get(): + if not self.handle_event(ev): + break + if not self.running: + break + self.update() + if self.dirty: + self.draw() + pygame.display.update() + self.dirty = False + self.clock.tick(60) + + +class UIChild: + def __init__(self, parent): + self.parent = parent + + def draw(self): + pass + + def update(self): + pass + + def ease_in_out_elastic(mag): p = 1 - mag s = p / tau * asin(1) @@ -26,12 +91,12 @@ def ease_in_out_elastic(mag): return inner -class Switch: +class Switch(UIChild): MOVE_FOR_SEC = 1 EASE = staticmethod(ease_in_out_elastic((5 ** .5 - 1) / 2)) def __init__(self, parent, rect, update_callback, setting=False): - self.parent = parent + super().__init__(parent) self.rect = rect self.update_callback = update_callback if setting is not None and not isinstance(setting, bool): @@ -55,7 +120,7 @@ class Switch: if self.setting is None: current = 0.5 else: - current = min(max(self.setting, 0), 1) + current = min(max(int(self.setting), 0), 1) else: current = (t - self.moving_since) / self.MOVE_FOR_SEC if not self.setting: @@ -95,9 +160,9 @@ class Switch: self.moving_since = time() - offset -class Button: +class Button(UIChild): def __init__(self, parent, rect, label, callback): - self.parent = parent + super().__init__(parent) self.rect = rect self.label = label self.pushed = False @@ -118,9 +183,6 @@ class Button: fs, (center[0] - fs_size[0] // 2, center[1] - fs_size[1] // 2) ) - def update(self): - pass - def handle_mousebuttondown(self, ev): if ev.button == 1 and self.rect.collidepoint(ev.pos): self.pushed = True @@ -140,55 +202,34 @@ class Button: self.parent.dirty = True -class IconButton: - def __init__(self, parent, name, shape, shape_size, rect): - self.parent = parent - self.name = name +class IconButton(Button): + def __init__(self, parent, shape, shape_size, rect, callback, is_active): + super().__init__(parent, rect, None, callback) self.shape = shape self.shape_size = shape_size - self.rect = rect + self.is_active = is_active def draw(self): - color = "lime " if self.parent.xrandr_conf.is_active(self.name) else "gray" - pygame.draw.rect(self.parent.surf, color, self.rect, 8) units = (16, 16) full_size = (units[0] * self.shape_size[0], units[1] * self.shape_size[1]) center = self.rect.center + if self.pushed: + pygame.draw.rect(self.parent.surf, "honeydew4", self.rect) self.shape.draw( self.parent.surf, (center[0] - full_size[0] // 2, center[1] - full_size[1] // 2), units, - "white", + "black" if self.pushed else "white", ) + color = "lime" if self.is_active() else ("red" if self.pushed else "gray") + pygame.draw.rect(self.parent.surf, color, self.rect, 8) - def update(self): - pass - def handle_mousebuttondown(self, ev): - if not self.rect.collidepoint(ev.pos): - return - self.parent.xrandr_conf.update(self.name) - if self.name != "single": - self.parent.bluetooth_conf.update(True) - self.parent.bt_switch.update(True) - settings_per_device_type = { - "touchpad": self.parent.touch_switch.setting, - "stylus": self.parent.stylus_switch.setting, - } - for device_type, setting in settings_per_device_type.items(): - if setting is not None: - self.parent.xinput_conf.update_by_type(device_type, setting) - self.parent.dirty = True - - -class Icon: +class Icon(UIChild): def __init__(self, parent, shape, pos): - self.parent = parent + super().__init__(parent) self.shape = shape self.pos = pos - def update(self): - pass - def draw(self): self.shape.draw(self.parent.surf, self.pos, (8, 8), "gray") diff --git a/zenbook_conf.py b/zenbook_conf.py index 5d7479b..19aece5 100755 --- a/zenbook_conf.py +++ b/zenbook_conf.py @@ -5,7 +5,7 @@ from functools import partial import pygame from bluetooth import BluetoothConf -from ui import Button, Icon, IconButton, Switch +from ui import Button, Icon, IconButton, Switch, UIParent from vectors import ( bluetooth, laptop_double, @@ -18,18 +18,41 @@ from xinput import XinputConf from xrandr import XrandrConf -class ZenbookConf: +class FPSMixin: + FPS_COLOR = "yellow" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.current_fps = None + + def update(self): + super().update() + new_fps = int(self.clock.get_fps()) + if self.current_fps != new_fps: + self.current_fps = new_fps + self.dirty = True + + def draw(self): + super().draw() + surf_size = self.surf.get_size() + fs = self.font.render(f"{int(self.current_fps)} FPS", True, self.FPS_COLOR) + fs_size = fs.get_size() + self.surf.blit( + fs, (surf_size[0] - fs_size[0] - 7, surf_size[1] - fs_size[1] - 7) + ) + + +class ZenbookConf(FPSMixin, UIParent): + BACKGROUND_COLOR = 0x333333 + def __init__(self): pygame.init() - self.surf = pygame.display.set_mode((1536, 1200)) - self.font = pygame.font.Font(None, size=96) - self.clock = pygame.time.Clock() - self.running = True - self.dirty = False + super().__init__( + pygame.display.set_mode((1536, 1200)), pygame.font.Font(None, size=96), + ) self.xrandr_conf = XrandrConf() self.xinput_conf = XinputConf(self.xrandr_conf) self.bluetooth_conf = BluetoothConf() - self.current_fps = -1 bt_switch = Switch( self, pygame.Rect((128, 552), (256, 128)), @@ -48,103 +71,91 @@ class ZenbookConf: partial(self.xinput_conf.update_by_type, "stylus"), self.xinput_conf.conf_by_type("stylus"), ) - self.children = ( - IconButton( - self, - "single", - laptop_single, - (22, 22), - pygame.Rect((68, 68), (416, 416)), - ), - IconButton( - self, - "double", - laptop_double, - (22, 22), - pygame.Rect((568, 68), (416, 416)), - ), - IconButton( - self, - "vertical", - laptop_vertical, - (22, 22), - pygame.Rect((1068, 68), (416, 416)), - ), - bt_switch, - Icon( - self, - bluetooth, - (bt_switch.rect.right + 68, bt_switch.rect.centery - 88), - ), - touch_switch, - Icon( - self, - touchscreen, - (touch_switch.rect.right + 68, touch_switch.rect.centery - 88), - ), - stylus_switch, - Icon( - self, - stylus, - (stylus_switch.rect.right + 68, stylus_switch.rect.centery - 88), - ), - Button( - self, - pygame.Rect((784, 748), (384, 128)), - "Re-apply", - partial(self.xinput_conf.reapply_by_type, "touchpad") - ), - Button( - self, - pygame.Rect((784, 944), (384, 128)), - "Re-apply", - partial(self.xinput_conf.reapply_by_type, "stylus") - ), + self.children.extend( + ( + IconButton( + self, + laptop_single, + (22, 22), + pygame.Rect((68, 68), (416, 416)), + partial( + self.laptop_cb, "single", bt_switch, touch_switch, stylus_switch + ), + partial(self.laptop_is_active, "single"), + ), + IconButton( + self, + laptop_double, + (22, 22), + pygame.Rect((568, 68), (416, 416)), + partial( + self.laptop_cb, "double", bt_switch, touch_switch, stylus_switch + ), + partial(self.laptop_is_active, "double"), + ), + IconButton( + self, + laptop_vertical, + (22, 22), + pygame.Rect((1068, 68), (416, 416)), + partial( + self.laptop_cb, + "vertical", + bt_switch, + touch_switch, + stylus_switch, + ), + partial(self.laptop_is_active, "vertical"), + ), + bt_switch, + Icon( + self, + bluetooth, + (bt_switch.rect.right + 68, bt_switch.rect.centery - 88), + ), + touch_switch, + Icon( + self, + touchscreen, + (touch_switch.rect.right + 68, touch_switch.rect.centery - 88), + ), + stylus_switch, + Icon( + self, + stylus, + (stylus_switch.rect.right + 68, stylus_switch.rect.centery - 88), + ), + Button( + self, + pygame.Rect((784, 748), (384, 128)), + "Re-apply", + partial(self.xinput_conf.reapply_by_type, "touchpad") + ), + Button( + self, + pygame.Rect((784, 944), (384, 128)), + "Re-apply", + partial(self.xinput_conf.reapply_by_type, "stylus") + ), + ) ) - def handle_event(self, ev): - if ev.type == pygame.QUIT: - self.running = False - return False - elif ev.type == pygame.WINDOWEXPOSED: - self.dirty = True - elif ev.type == pygame.KEYDOWN: - if ev.key == pygame.K_ESCAPE: - self.running = False - return False - method_name = f"handle_{pygame.event.event_name(ev.type).lower()}" - for child in self.children: - method = getattr(child, method_name, None) - if method is None: - continue - method(ev) - return True - - def update(self): - for child in self.children: - child.update() - if self.clock.get_fps() != self.current_fps: - self.current_fps = int(self.clock.get_fps()) - self.dirty = True - - def draw(self): - self.surf.fill(0x333333) - for child in self.children: - child.draw() + def laptop_cb(self, name, bt_switch, touch_switch, stylus_switch): + self.xrandr_conf.update(name) + if name != "single": + self.bluetooth_conf.update(True) + bt_switch.update(True) + settings_per_device_type = { + "touchpad": touch_switch.setting, + "stylus": stylus_switch.setting, + } + for device_type, setting in settings_per_device_type.items(): + if setting is not None: + self.xinput_conf.update_by_type(device_type, setting) + self.dirty = True - def run(self): - while True: - for ev in pygame.event.get(): - if not self.handle_event(ev): - break - if not self.running: - break - self.update() - if self.dirty: - self.draw() - pygame.display.update() - self.dirty = False - self.clock.tick(60) + def laptop_is_active(self, name): + return self.xrandr_conf.is_active(name) if __name__ == "__main__": -- 2.47.1