From: mar77i Date: Tue, 9 Sep 2025 01:08:34 +0000 (+0200) Subject: add keyboard X-Git-Url: https://git.mar77i.info/?a=commitdiff_plain;h=2f2426e7871674a4e3203dba2b18dc270e21e666;p=zenbook_gui add keyboard --- diff --git a/keyboard.py b/keyboard.py new file mode 120000 index 0000000..a0e149e --- /dev/null +++ b/keyboard.py @@ -0,0 +1 @@ +launch.py \ No newline at end of file diff --git a/keyboard/__init__.py b/keyboard/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/keyboard/key.py b/keyboard/key.py new file mode 100644 index 0000000..1e00c12 --- /dev/null +++ b/keyboard/key.py @@ -0,0 +1,278 @@ +from functools import partial +from pathlib import Path + +import pygame +from pynput.keyboard import Key, KeyCode + +from ui import Button +from ui.multitouch import MultitouchHandler + +from .touchbutton import TouchButton + +KEYBOARD = ( + ("Esc", Key.esc), + (None, None), + ("F1", Key.f1), + ("F2", Key.f2), + ("F3", Key.f3), + ("F4", Key.f4), + (None, None), + ("F5", Key.f5), + ("F6", Key.f6), + ("F7", Key.f7), + ("F8", Key.f8), + (None, None), + ("F9", Key.f9), + ("F10", Key.f10), + ("F11", Key.f11), + ("F12", Key.f12), + "\n", + ("§", KeyCode.from_vk(0xa7), {"x_hint": 1.5}), + ("1", KeyCode.from_vk(0x31)), + ("2", KeyCode.from_vk(0x32)), + ("3", KeyCode.from_vk(0x33)), + ("4", KeyCode.from_vk(0x34)), + ("5", KeyCode.from_vk(0x35)), + ("6", KeyCode.from_vk(0x36)), + ("7", KeyCode.from_vk(0x37)), + ("8", KeyCode.from_vk(0x38)), + ("9", KeyCode.from_vk(0x39)), + ("0", KeyCode.from_vk(0x30)), + ("'", KeyCode.from_vk(0x27)), + ("^", KeyCode.from_vk(0xfe52)), + ("←", Key.backspace, {"x_hint": 1.5}), + "\n", + ("Tab", Key.tab, {"x_hint": 2}), + ("Q", KeyCode.from_vk(0x71)), + ("W", KeyCode.from_vk(0x77)), + ("E", KeyCode.from_vk(0x65)), + ("R", KeyCode.from_vk(0x72)), + ("T", KeyCode.from_vk(0x74)), + ("Z", KeyCode.from_vk(0x7a)), + ("U", KeyCode.from_vk(0x75)), + ("I", KeyCode.from_vk(0x69)), + ("O", KeyCode.from_vk(0x6f)), + ("P", KeyCode.from_vk(0x70)), + ("ü", KeyCode.from_vk(0xfc)), + ("¨", KeyCode.from_vk(0xfe57)), + ("$", KeyCode.from_vk(0x24)), + "\n", + ("CpsLk", Key.caps_lock, {"x_hint": 2.5, "led_mask": 1}), + ("A", KeyCode.from_vk(0x61)), + ("S", KeyCode.from_vk(0x73)), + ("D", KeyCode.from_vk(0x64)), + ("F", KeyCode.from_vk(0x66)), + ("G", KeyCode.from_vk(0x67)), + ("H", KeyCode.from_vk(0x68)), + ("J", KeyCode.from_vk(0x6a)), + ("K", KeyCode.from_vk(0x6b)), + ("L", KeyCode.from_vk(0x6c)), + ("ö", KeyCode.from_vk(0xf6)), + ("ä", KeyCode.from_vk(0xe4)), + ("↵", Key.enter, {"x_hint": 1.5}), + "\n", + ("↑", Key.shift, {"x_hint": 2}), + ("<", KeyCode.from_vk(0x3c)), + ("Y", KeyCode.from_vk(0x79)), + ("X", KeyCode.from_vk(0x78)), + ("C", KeyCode.from_vk(0x63)), + ("V", KeyCode.from_vk(0x76)), + ("B", KeyCode.from_vk(0x62)), + ("N", KeyCode.from_vk(0x6e)), + ("M", KeyCode.from_vk(0x6d)), + (",", KeyCode.from_vk(0x2c)), + (".", KeyCode.from_vk(0x2e)), + ("-", KeyCode.from_vk(0x2d)), + ("↑", Key.shift, {"x_hint": 2}), + "\n", + ("Ctrl", Key.ctrl_l, {"x_hint": 2}), + ("⊞", Key.cmd), + ("Alt", Key.alt_l), + ("␣", Key.space, {"x_hint": 7}), + ("⚙", "Settings"), + ("AltGr", KeyCode.from_vk(0xfe03)), + ("Ctrl", Key.ctrl_r, {"x_hint": 2}), +) + +CURSOR_KEYS = ( + ("INS", Key.insert), + ("HOME", Key.home), + ("PG UP", Key.page_up), + "\n", + ("DEL", Key.delete), + ("END", Key.end), + ("PG DN", Key.page_down), + "\n", + "\n", + (None, None), + ("↑", Key.up), + (None, None), + "\n", + ("←", Key.left), + ("↓", Key.down), + ("→", Key.right), +) + +KEYPAD_KEYS = ( + ("NUM", Key.num_lock, {"led_mask": 2}), + ("/", KeyCode.from_vk(0xffaf)), + ("*", KeyCode.from_vk(0xffaa)), + ("-", KeyCode.from_vk(0xffad)), + "\n", + ("7", KeyCode.from_vk(0xffb7)), + ("8", KeyCode.from_vk(0xffb8)), + ("9", KeyCode.from_vk(0xffb9)), + ("+", KeyCode.from_vk(0xffab), {"y_hint": 2}), + "\n", + ("4", KeyCode.from_vk(0xffb4)), + ("5", KeyCode.from_vk(0xffb5)), + ("6", KeyCode.from_vk(0xffb6)), + "\n", + ("1", KeyCode.from_vk(0xffb1)), + ("2", KeyCode.from_vk(0xffb2)), + ("3", KeyCode.from_vk(0xffb3)), + ("Enter", KeyCode.from_vk(0xff8d), {"y_hint": 2}), + "\n", + ("0", KeyCode.from_vk(0xffb0), {"x_hint": 2}), + (".", KeyCode.from_vk(0xffae)), +) + + +class KeyButton(Button): + def __init__(self, *args, **kwargs): + self.data = kwargs.pop("data", None) + self.led_mask = kwargs.pop("led_mask", None) + super().__init__(*args, **kwargs) + self.touch_key = None + # sounds from https://freesoundsite.com/sound-collections/click-sound-effects/ + self.sounds = ( + pygame.mixer.Sound(Path(__file__).parent / "sounds" / "Flicker_Sound_Effect.mp3"), + pygame.mixer.Sound(Path(__file__).parent / "sounds" / "Clutch.mp3"), + ) + for sound in self.sounds: + sound.set_volume(0.5) + + def press(self, touch_key=None): + if self.pushed: + return + if not self.root.muted: + self.sounds[0].play() + self.touch_key = touch_key + self.pushed = True + self.dirty = True + self.callback(self.data) + + def release(self, touch_key=None): + if not self.pushed or touch_key != self.touch_key: + return + if not self.root.muted: + self.sounds[1].play() + self.touch_key = None + self.pushed = False + self.dirty = True + self.callback(self.data, True) + + def handle_mousebuttondown(self, event): + if event.button == 1 and self.rect.collidepoint(event.pos): + self.press() + + def handle_mousemotion(self, event): + if event.buttons[0] and not self.rect.collidepoint(event.pos): + self.release() + + def handle_mousebuttonup(self, event): + if event.button == 1 and self.rect.collidepoint(event.pos): + self.release() + + def handle_fingerdown(self, event): + if self.rect.collidepoint( + MultitouchHandler.map_coords((event.x, event.y), self.surf.get_size()) + ): + self.press((event.touch_id, event.finger_id)) + + def handle_fingermotion(self, event): + if not self.rect.collidepoint( + MultitouchHandler.map_coords((event.x, event.y), self.surf.get_size()) + ): + self.release((event.touch_id, event.finger_id)) + + def handle_fingerup(self, event): + self.release((event.touch_id, event.finger_id)) + + def draw(self): + if self.led_mask: + self.highlight = self.root.led_mask & self.led_mask + super().draw() + + +def apply_y_spans(x_sums, y_spans): + for i, (width, y_remaining) in enumerate(y_spans): + if y_remaining <= 0: + continue + x_sums[-1] += width + y_spans[i][1] = y_remaining - 1 + + +def get_key_size(size, x_sums_iter, x_sums_len): + x_sum = next(x_sums_iter) + if x_sum == 0: + return (0, size[1] / x_sums_len) + return (size[0] / x_sum, size[1] / x_sums_len) + + +def get_keyboard_keys(rect, parent, callback, keys): + result = [] + x_sums = [0] + y_spans = [] + for key in keys: + if key == "\n": + x_sums.append(0) + apply_y_spans(x_sums, y_spans) + elif len(key) > 2: + width = key[2].get("x_hint", 1) + x_sums[-1] += width + y_hint = key[2].get("y_hint", 1) + if y_hint > 1: + y_spans.append([width, y_hint - 1]) + else: + x_sums[-1] += 1 + apply_y_spans(x_sums, y_spans) + assert all(item[1] <= 0 for item in y_spans) + x, y = rect.topleft + x_sums_iter = iter(x_sums) + x_sums_len = len(x_sums) + key_size = get_key_size(rect.size, x_sums_iter, x_sums_len) + for key in keys: + if key == "\n": + x, y = rect.left, y + key_size[1] + key_size = get_key_size(rect.size, x_sums_iter, x_sums_len) + continue + key_width, key_height = key_size + if key[1] == "Settings": + result.append( + TouchButton( + parent, + pygame.Rect((x + 8, y + 8), (key_width - 16, key_height - 16)), + key[0], + partial(callback, key[1]), + ) + ) + elif key != (None, None): + if len(key) > 2: + key_width *= key[2].get("x_hint", 1) + key_height *= key[2].get("y_hint", 1) + led_mask = key[2].get("led_mask") + else: + led_mask = None + result.append( + KeyButton( + parent, + pygame.Rect((x + 8, y + 8), (key_width - 16, key_height - 16)), + key[0], + callback, + data=key[1], + led_mask=led_mask + ) + ) + x += key_width + return result diff --git a/keyboard/keyboard.py b/keyboard/keyboard.py new file mode 100644 index 0000000..74af025 --- /dev/null +++ b/keyboard/keyboard.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 + +from pynput.keyboard import Controller as KeyboardController, Key, KeyCode +from pynput._util.xorg_keysyms import KEYPAD_KEYS + +from launch import pygame +from multitouch import MultitouchHandler +from ui import BaseRoot, FPSWidget + +from .key import CURSOR_KEYS, KEYBOARD, KEYPAD_KEYS, get_keyboard_keys +from .settings import Settings + +# add a launcher symbol + + +class Root(BaseRoot): + BACKGROUND_COLOR = "black" + + def __init__(self): + super().__init__( + pygame.display.set_mode((0, 0), pygame.NOFRAME, display=1), + pygame.font.SysFont("DejaVu Sans", size=64), + ) + size = self.surf.get_size() + widget_size = (size[0] // 3, size[1] // 2) + get_keyboard_keys( + pygame.Rect((0, 0), (size[0], widget_size[1])), self, self.key_cb, KEYBOARD + ) + get_keyboard_keys( + pygame.Rect((0, widget_size[1]), widget_size), + self, + self.key_cb, + CURSOR_KEYS, + ) + get_keyboard_keys( + pygame.Rect(widget_size, widget_size), self, self.key_cb, KEYPAD_KEYS + ) + self.pushed = set() + self.mt = MultitouchHandler( + MultitouchHandler.find_device(name="ELAN9009:00 04F3:425A") + ) + self.keyboard = KeyboardController() + self.display = self.keyboard._display + self.led_mask = self.display.get_keyboard_control().led_mask + self.muted = False + FPSWidget(self) + self.settings = Settings( + self, + pygame.Rect((size[0] // 4, size[1] // 4), (size[0] // 2, size[1] // 2)), + ) + + def key_cb(self, key, release=False): + if key == "Settings": + self.settings.activate() + if isinstance(key, (Key, KeyCode)): + (self.keyboard.press, self.keyboard.release)[release](key) + if release and key in self.pushed: + self.pushed.remove(key) + elif not release: + self.pushed.add(key) + + def handle_events(self): + self.mt.handle_events() + super().handle_events() + + def update(self): + super().update() + led_mask = self.keyboard._display.get_keyboard_control().led_mask + if led_mask != self.led_mask: + self.led_mask = led_mask + self.dirty = True + + def run(self): + try: + with self.mt.device.grab_context(): + super().run() + finally: + for key in self.pushed: + self.keyboard.release(key) diff --git a/keyboard/settings.py b/keyboard/settings.py new file mode 100644 index 0000000..f88f23e --- /dev/null +++ b/keyboard/settings.py @@ -0,0 +1,56 @@ +import os +import sys +from pathlib import Path + +import pygame + +from ui import Modal, Rect + +from .touchbutton import TouchButton + +# what settings do we need? +# - configure widgets +# - keyboard above, two widgets below +# - keyboard above, three widgets below +# - keyboard below, two widgets above +# - keyboard below, three widgets above +# - three available widgets: touchpad mouse, number keypad, cursor and navkeys + + +class Settings(Modal): + def __init__(self, parent, rect): + super().__init__(parent) + self.rect = rect + Rect(self, self.rect, "black", "gray") + width = rect.width * 2 / 3 + height = rect.height / 7 + left = rect.left + (rect.width - width) / 2 + 8 + y = rect.top + height + 8 + rect_size = (width - 16, height - 16) + self.buttons = [] + for args in ( + ("Mute", self.mute, self.root.muted), + ("Layout...", self.layout), + ("Restart", self.restart), + ("Exit", self.root.handle_quit), + ("Back", self.deactivate), + ): + self.buttons.append( + TouchButton(self, pygame.Rect((left, y), rect_size), *args) + ) + y += height + + def mute(self): + self.root.muted ^= True + self.buttons[0].highlight = self.root.muted + + def layout(self): + print("stub layout") + + def restart(self): + executable = Path(sys.executable).name + os.execl( + sys.executable, + executable, + *sys.orig_argv[sys.orig_argv[0] == executable:], + ) diff --git a/keyboard/sounds/Clutch.mp3 b/keyboard/sounds/Clutch.mp3 new file mode 100644 index 0000000..2a4bcf1 Binary files /dev/null and b/keyboard/sounds/Clutch.mp3 differ diff --git a/keyboard/sounds/Flicker_Sound_Effect.mp3 b/keyboard/sounds/Flicker_Sound_Effect.mp3 new file mode 100644 index 0000000..db8df4f Binary files /dev/null and b/keyboard/sounds/Flicker_Sound_Effect.mp3 differ diff --git a/keyboard/touchbutton.py b/keyboard/touchbutton.py new file mode 100644 index 0000000..1289e34 --- /dev/null +++ b/keyboard/touchbutton.py @@ -0,0 +1,41 @@ +from ui import Button +from ui.multitouch import MultitouchHandler + + +class TouchButton(Button): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.touch_key = None + + def handle_fingerdown(self, event): + if not self.pushed and self.rect.collidepoint( + MultitouchHandler.map_coords((event.x, event.y), self.surf.get_size()) + ): + self.touch_key = (event.touch_id, event.finger_id) + self.pushed = True + self.dirty = True + + def handle_fingermotion(self, event): + if not self.pushed or self.touch_key is None: + return + if ( + (event.touch_id, event.finger_id) == self.touch_key + and not self.rect.collidepoint( + MultitouchHandler.map_coords((event.x, event.y), self.surf.get_size()) + ) + ): + self.touch_key = None + self.pushed = False + self.dirty = True + + def handle_fingerup(self, event): + if not self.pushed or self.touch_key is None: + return + if (event.touch_id, event.finger_id) == self.touch_key: + if self.rect.collidepoint( + MultitouchHandler.map_coords((event.x, event.y), self.surf.get_size()) + ): + self.callback() + self.touch_key = None + self.pushed = False + self.dirty = True diff --git a/launch.py b/launch.py index 7156e30..95d345d 100755 --- a/launch.py +++ b/launch.py @@ -127,7 +127,19 @@ def setup_venv(): if not has_pygame or all( p.stat().st_mtime < yesterday for p in walk_outer(venv_dir) ): - run_cmd((executable, "-m", "pip", "install", "-qU", "pip", "pygame")) + run_cmd( + ( + executable, + "-m", + "pip", + "install", + "-qU", + "pip", + "pygame", + "pynput", + "evdev", + ) + ) venv_dir.touch(0o755, True) elif not has_pygame: raise ConnectionError("Internet needed to install requirement: pygame") diff --git a/ui/button.py b/ui/button.py index 15cf575..1758f81 100644 --- a/ui/button.py +++ b/ui/button.py @@ -33,15 +33,16 @@ class Button(Child): self.pushed = True self.dirty = True - def handle_mousebuttonup(self, ev): - if ev.button == 1 and self.pushed and self.rect.collidepoint(ev.pos): - self.pushed = False - self.callback() - self.dirty = True - def handle_mousemotion(self, ev): if not self.pushed: return if ev.buttons[0] and not self.rect.collidepoint(ev.pos): self.pushed = False self.dirty = True + + def handle_mousebuttonup(self, ev): + if ev.button == 1 and self.pushed: + if self.rect.collidepoint(ev.pos): + self.callback() + self.pushed = False + self.dirty = True diff --git a/ui/focus.py b/ui/focus.py index 1432a38..b372529 100644 --- a/ui/focus.py +++ b/ui/focus.py @@ -1,8 +1,8 @@ -from .root import BaseRoot +import ui class Focusable: - root: BaseRoot + root: "ui.root.BaseRoot" dirty: bool @property diff --git a/ui/multitouch.py b/ui/multitouch.py index 5336cc9..c24c91d 100755 --- a/ui/multitouch.py +++ b/ui/multitouch.py @@ -1,9 +1,18 @@ +import pygame from evdev import InputDevice, ecodes, list_devices class MultitouchHandler: + STRINGY_FIELDS = False + + @staticmethod + def map_coords(event_pos, size): + return (event_pos[0] * size[0], event_pos[1] * size[1]) + @staticmethod def find_device(**kwargs): + if "path" in kwargs: + return InputDevice(kwargs["path"]) for path in list_devices(): device = InputDevice(path) for k, v in kwargs.items(): @@ -13,74 +22,144 @@ class MultitouchHandler: return device return None - @staticmethod - def multitouch_ranges(device): - return { - v: (info.min, info.max) - for v, info in ( - (v, device.absinfo(v)) - for v in ( - ecodes.ABS_X, - ecodes.ABS_Y, - ecodes.ABS_MT_POSITION_X, - ecodes.ABS_MT_POSITION_Y, - ) - ) + def __init__(self, device): + self.device = device + self.touch_id = int(self.device.path[self.device.path.find("event") + 5:]) + x_info = self.device.absinfo(ecodes.ABS_MT_POSITION_X) + y_info = self.device.absinfo(ecodes.ABS_MT_POSITION_Y) + self.info = { + ecodes.ABS_MT_POSITION_X: (x_info.min, x_info.max), + ecodes.ABS_MT_POSITION_Y: (y_info.min, y_info.max), } - - def __init__(self, **kwargs): - self.device = self.find_device(**kwargs) - self.info = self.multitouch_ranges(self.device) - self.context_app = kwargs.get("context_app") - self.slots = [{}] + self._fingers = [{}] + self.fingers = [] + self.dropped = False self.slot = 0 + self.event_handlers = { + ecodes.EV_SYN: self.handle_syn, + ecodes.EV_ABS: self.handle_abs, + ecodes.EV_MSC: self.handle_msc, + ecodes.EV_KEY: self.handle_key, + } + self.tracking_id_lookup = self.get_lookup(ecodes.ABS, ecodes.ABS_MT_TRACKING_ID) + self.x_lookup = self.get_lookup(ecodes.ABS, ecodes.ABS_MT_POSITION_X) + self.y_lookup = self.get_lookup(ecodes.ABS, ecodes.ABS_MT_POSITION_Y) + + def current_finger(self): + while self.slot >= len(self._fingers): + self._fingers.append({}) + return self._fingers[self.slot] + + def _clear_fingers(self): + for finger in self._fingers: + finger.clear() + + @classmethod + def get_lookup(cls, mapping, code): + if cls.STRINGY_FIELDS: + return mapping[code] + return code + + def push_event(self, event_type, finger_new, finger_old): + x_info = self.info[ecodes.ABS_MT_POSITION_X] + y_info = self.info[ecodes.ABS_MT_POSITION_Y] + x = (finger_new.get(self.x_lookup, x_info[1]) - x_info[0]) / (x_info[1] - x_info[0]) + y = (finger_new.get(self.y_lookup, x_info[1]) - y_info[0]) / (y_info[1] - y_info[0]) + pygame.event.post( + pygame.event.Event( + event_type, + { + "finger_id": finger_new[self.tracking_id_lookup], + "touch_id": self.touch_id, + "x": x, + "y": y, + **( + { + "dx": x - ( + finger_old.get(self.x_lookup, x_info[1]) - x_info[0] + ) / x_info[1], + "dy": y - ( + finger_old.get(self.y_lookup, y_info[1]) - y_info[0] + ) / y_info[1], + } if finger_old is not None else {"dx": 0.0, "dy": 0.0} + ), + "evdev_event": finger_new.copy(), + **({"dropped": True} if self.dropped else {}), + } + ) + ) - def current_slot(self): - while self.slot >= len(self.slots): - self.slots.append({}) - return self.slots[self.slot] + @staticmethod + def cmp_fingers(finger_new, finger_old): + for key in (*finger_new, *finger_old): + if key == ecodes.MSC_TIMESTAMP: + continue + if finger_new.get(key) != finger_old.get(key): + return True + return False + + def sync(self): + num_fingers = len(self._fingers) + while len(self.fingers) < num_fingers: + self.fingers.append({}) + for finger_new, finger_old in zip(self._fingers, self.fingers): + old_finger_id = finger_old.get(self.tracking_id_lookup) + new_finger_id = finger_new.get(self.tracking_id_lookup) + if old_finger_id is not None: + if old_finger_id == new_finger_id: + if self.cmp_fingers(finger_new, finger_old): + self.push_event(pygame.FINGERMOTION, finger_new, finger_old) + else: + self.push_event(pygame.FINGERUP, finger_old, None) + if new_finger_id is not None and ( + old_finger_id is None or old_finger_id != new_finger_id + ): + self.push_event(pygame.FINGERDOWN, finger_new, None) + finger_old.clear() + finger_old.update(finger_new) + + def handle_syn(self, event): + if event.code == ecodes.SYN_DROPPED: + self._clear_fingers() + self.dropped = True + elif event.code == ecodes.SYN_REPORT: + self.sync() + self.dropped = False + + def handle_abs(self, event): + if event.code == ecodes.ABS_MT_SLOT: + self.slot = event.value + elif event.code == ecodes.ABS_MT_TRACKING_ID and event.value == -1: + self.current_finger().clear() + return + self.current_finger()[self.get_lookup(ecodes.ABS, event.code)] = event.value + + def handle_msc(self, event): + self.current_finger()[self.get_lookup(ecodes.MSC, event.code)] = event.value - def handle_event(self, event): + def handle_key(self, event): + if event.code == ecodes.BTN_TOUCH and event.value == 0: + self._clear_fingers() + + @staticmethod + def log_event(event): if event.type == ecodes.EV_SYN: - current_slot = self.current_slot() - if current_slot.get(ecodes.ABS_MT_TRACKING_ID) == -1: - current_slot.clear() + code = ecodes.SYN[event.code] elif event.type == ecodes.EV_ABS: - if event.code == ecodes.ABS_MT_SLOT: - self.slot = event.value - else: - self.current_slot()[event.code] = event.value + code = ecodes.ABS[event.code] elif event.type == ecodes.EV_MSC: - self.current_slot()[event.code] = event.value - elif event.type == ecodes.EV_KEY: - if event.code == ecodes.BTN_TOUCH: - if event.value == 0: - for s in self.slots: - s.clear() + code = ecodes.MSC[event.code] + elif event.type == ecodes.EV_KEY and event.code in { + *ecodes.KEY, *ecodes.BTN + }: + code = ecodes.KEY.get( + event.code, ecodes.BTN[event.code] + ) + else: + code = f"{ecodes.EV[event.type]}[{event.code}]" + print(ecodes.EV[event.type], code, event.value) def handle_events(self): while (event := self.device.read_one()) is not None: - self.handle_event(event) - - def map_coords(self, rect): - for slot in self.slots: - if ecodes.ABS_MT_POSITION_X in slot and ecodes.ABS_MT_POSITION_Y in slot: - infos = ( - self.info[ecodes.ABS_MT_POSITION_X], - self.info[ecodes.ABS_MT_POSITION_Y], - ) - pos = (slot[ecodes.ABS_MT_POSITION_X], slot[ecodes.ABS_MT_POSITION_Y]) - elif ecodes.ABS_X in slot and ecodes.ABS_Y in slot: - infos = (self.info[ecodes.ABS_X], self.info[ecodes.ABS_Y]) - pos = (slot[ecodes.ABS_X], slot[ecodes.ABS_Y]) - else: - continue - yield tuple( - offset + (value - info[0]) * scale / (info[1] - info[0]) - for value, info, offset, scale in zip( - pos, - infos, - rect.topleft, - rect.size, - ) - ), slot + # self.log_event(event) + self.event_handlers.get(event.type, lambda e: None)(event) diff --git a/ui/root.py b/ui/root.py index 3726482..2d63201 100644 --- a/ui/root.py +++ b/ui/root.py @@ -1,9 +1,10 @@ import pygame +from .focus import Focusable from .parent import Parent -class BaseRoot(Parent): +class BaseRoot(Focusable, Parent): BACKGROUND_COLOR: pygame.Color ACTIVATE_EVENTS = ( pygame.WINDOWENTER, @@ -29,9 +30,10 @@ class BaseRoot(Parent): key_methods = {frozenset(): {pygame.K_ESCAPE: handle_quit}} - @property - def is_focused(self): - return self.focus_stack[-1] is self + def handle_events(self): + for ev in pygame.event.get(): + self.stop_event = False + self.handle_event(ev) def handle_event(self, ev): if ev.type in self.ACTIVATE_EVENTS: @@ -53,12 +55,12 @@ class BaseRoot(Parent): def run(self): while True: - for ev in pygame.event.get(): - self.stop_event = False - self.handle_event(ev) - if not self.running: - return + self.handle_events() + if not self.running: + return self.update() + if not self.running: + return if self.dirty: self.draw() pygame.display.update()