From faafd0f1ca26ddc7df38087a5de6063a6dc166f6 Mon Sep 17 00:00:00 2001 From: Cameron Clough Date: Sat, 26 Apr 2025 01:21:55 +0100 Subject: [PATCH] Reapply "ui(raylib): create BaseWindow (#35074)" This reverts commit 83b84a5bec719c3e8cc1501d56344d95d4a9c4f4. --- system/ui/lib/window.py | 57 +++++++++++++++++++++++++++++++++++++++++ system/ui/spinner.py | 42 ++++-------------------------- system/ui/text.py | 45 ++++---------------------------- 3 files changed, 67 insertions(+), 77 deletions(-) create mode 100644 system/ui/lib/window.py diff --git a/system/ui/lib/window.py b/system/ui/lib/window.py new file mode 100644 index 0000000000..d039d80b80 --- /dev/null +++ b/system/ui/lib/window.py @@ -0,0 +1,57 @@ +import threading +import time +import os +from typing import Generic, Protocol, TypeVar +from openpilot.system.ui.lib.application import gui_app + + +class RendererProtocol(Protocol): + def render(self): ... + + +R = TypeVar("R", bound=RendererProtocol) + + +class BaseWindow(Generic[R]): + def __init__(self, title: str): + self._title = title + self._renderer: R | None = None + self._stop_event = threading.Event() + self._thread = threading.Thread(target=self._run) + self._thread.start() + + # wait for the renderer to be initialized + while self._renderer is None and self._thread.is_alive(): + time.sleep(0.01) + + def _create_renderer(self) -> R: + raise NotImplementedError("Subclasses of BaseWindow must implement _create_renderer()") + + def _run(self): + if os.getenv("CI") is not None: + return + gui_app.init_window("Spinner") + self._renderer = self._create_renderer() + try: + for _ in gui_app.render(): + if self._stop_event.is_set(): + break + self._renderer.render() + finally: + gui_app.close() + + def __enter__(self): + return self + + def close(self): + if self._thread.is_alive(): + self._stop_event.set() + self._thread.join(timeout=2.0) + if self._thread.is_alive(): + print(f"WARNING: failed to join {self._title} thread") + + def __del__(self): + self.close() + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() diff --git a/system/ui/spinner.py b/system/ui/spinner.py index 5da96c73f9..119bdba3e7 100755 --- a/system/ui/spinner.py +++ b/system/ui/spinner.py @@ -6,6 +6,7 @@ import time from openpilot.common.basedir import BASEDIR from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.window import BaseWindow from openpilot.system.ui.text import wrap_text # Constants @@ -85,16 +86,12 @@ class SpinnerRenderer: FONT_SIZE, 0.0, rl.WHITE) -class Spinner: +class Spinner(BaseWindow[SpinnerRenderer]): def __init__(self): - self._renderer: SpinnerRenderer | None = None - self._stop_event = threading.Event() - self._thread = threading.Thread(target=self._run) - self._thread.start() + super().__init__("Spinner") - # wait for the renderer to be initialized - while self._renderer is None and self._thread.is_alive(): - time.sleep(0.01) + def _create_renderer(self): + return SpinnerRenderer() def update(self, spinner_text: str): if self._renderer is not None: @@ -103,35 +100,6 @@ class Spinner: def update_progress(self, cur: float, total: float): self.update(str(round(100 * cur / total))) - def _run(self): - if os.getenv("CI") is not None: - return - gui_app.init_window("Spinner") - self._renderer = renderer = SpinnerRenderer() - try: - for _ in gui_app.render(): - if self._stop_event.is_set(): - break - renderer.render() - finally: - gui_app.close() - - def __enter__(self): - return self - - def close(self): - if self._thread.is_alive(): - self._stop_event.set() - self._thread.join(timeout=2.0) - if self._thread.is_alive(): - print("WARNING: failed to join spinner thread") - - def __del__(self): - self.close() - - def __exit__(self, exc_type, exc_val, exc_tb): - self.close() - if __name__ == "__main__": with Spinner() as s: diff --git a/system/ui/text.py b/system/ui/text.py index e57ae45d9a..33e8167c64 100755 --- a/system/ui/text.py +++ b/system/ui/text.py @@ -1,13 +1,12 @@ #!/usr/bin/env python3 -import os import re -import threading import time import pyray as rl from openpilot.system.hardware import HARDWARE, PC from openpilot.system.ui.lib.button import gui_button, ButtonStyle from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.window import BaseWindow MARGIN = 50 SPACING = 40 @@ -74,52 +73,18 @@ class TextWindowRenderer: return ret -class TextWindow: +class TextWindow(BaseWindow[TextWindowRenderer]): def __init__(self, text: str): self._text = text + super().__init__("Text") - self._renderer: TextWindowRenderer | None = None - self._stop_event = threading.Event() - self._thread = threading.Thread(target=self._run) - self._thread.start() - - # wait for the renderer to be initialized - while self._renderer is None and self._thread.is_alive(): - time.sleep(0.01) + def _create_renderer(self): + return TextWindowRenderer(self._text) def wait_for_exit(self): while self._thread.is_alive(): time.sleep(0.01) - def _run(self): - if os.getenv("CI") is not None: - return - gui_app.init_window("Text") - self._renderer = renderer = TextWindowRenderer(self._text) - try: - for _ in gui_app.render(): - if self._stop_event.is_set(): - break - renderer.render() - finally: - gui_app.close() - - def __enter__(self): - return self - - def close(self): - if self._thread.is_alive(): - self._stop_event.set() - self._thread.join(timeout=2.0) - if self._thread.is_alive(): - print("WARNING: failed to join text window thread") - - def __del__(self): - self.close() - - def __exit__(self, exc_type, exc_val, exc_tb): - self.close() - if __name__ == "__main__": with TextWindow(DEMO_TEXT):