diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index c7b7a36e5..fd1ad5206 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -64,11 +64,16 @@ class GuiApplication: return texture def close(self): + if not rl.is_window_ready(): + return + for texture in self._textures: rl.unload_texture(texture) + self._textures = [] for font in self._fonts.values(): rl.unload_font(font) + self._fonts = [] rl.close_window() @@ -120,7 +125,6 @@ class GuiApplication: rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.TEXT_SIZE, DEFAULT_TEXT_SIZE) rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.BACKGROUND_COLOR, rl.color_to_int(rl.BLACK)) rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiControlProperty.TEXT_COLOR_NORMAL, rl.color_to_int(DEFAULT_TEXT_COLOR)) - rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiDefaultProperty.BACKGROUND_COLOR, rl.color_to_int(rl.Color(30, 30, 30, 255))) rl.gui_set_style(rl.GuiControl.DEFAULT, rl.GuiControlProperty.BASE_COLOR_NORMAL, rl.color_to_int(rl.Color(50, 50, 50, 255))) def _monitor_fps(self): diff --git a/system/ui/spinner.py b/system/ui/spinner.py index 639de2674..980930747 100755 --- a/system/ui/spinner.py +++ b/system/ui/spinner.py @@ -1,8 +1,7 @@ #!/usr/bin/env python3 import pyray as rl import os -import select -import sys +import threading from openpilot.common.basedir import BASEDIR from openpilot.system.ui.lib.application import gui_app @@ -20,57 +19,57 @@ def clamp(value, min_value, max_value): return max(min(value, max_value), min_value) -def check_input_non_blocking(): - if sys.stdin in select.select([sys.stdin], [], [], 0)[0]: - return sys.stdin.readline().strip() - return "" +class Spinner: + def __init__(self): + self._comma_texture = gui_app.load_texture_from_image(os.path.join(BASEDIR, "selfdrive/assets/img_spinner_comma.png"), TEXTURE_SIZE, TEXTURE_SIZE) + self._spinner_texture = gui_app.load_texture_from_image(os.path.join(BASEDIR, "selfdrive/assets/img_spinner_track.png"), TEXTURE_SIZE, TEXTURE_SIZE) + self._rotation = 0.0 + self._text: str = "" + self._lock = threading.Lock() + def set_text(self, text: str) -> None: + with self._lock: + self._text = text -def main(): - gui_app.init_window("Spinner") + def render(self): + center = rl.Vector2(gui_app.width / 2.0, gui_app.height / 2.0) + spinner_origin = rl.Vector2(TEXTURE_SIZE / 2.0, TEXTURE_SIZE / 2.0) + comma_position = rl.Vector2(center.x - TEXTURE_SIZE / 2.0, center.y - TEXTURE_SIZE / 2.0) - # Load textures - comma_texture = gui_app.load_texture_from_image(os.path.join(BASEDIR, "selfdrive/assets/img_spinner_comma.png"), TEXTURE_SIZE, TEXTURE_SIZE) - spinner_texture = gui_app.load_texture_from_image(os.path.join(BASEDIR, "selfdrive/assets/img_spinner_track.png"), TEXTURE_SIZE, TEXTURE_SIZE) - - # Initial values - rotation = 0.0 - user_input = "" - center = rl.Vector2(gui_app.width / 2.0, gui_app.height / 2.0) - spinner_origin = rl.Vector2(TEXTURE_SIZE / 2.0, TEXTURE_SIZE / 2.0) - comma_position = rl.Vector2(center.x - TEXTURE_SIZE / 2.0, center.y - TEXTURE_SIZE / 2.0) - - for _ in gui_app.render(): fps = rl.get_fps() if fps > 0: degrees_per_frame = 360.0 / (ROTATION_TIME_SECONDS * fps) - rotation = (rotation + degrees_per_frame) % 360.0 + self._rotation = (self._rotation + degrees_per_frame) % 360.0 # Draw rotating spinner and static comma logo - rl.draw_texture_pro(spinner_texture, rl.Rectangle(0, 0, TEXTURE_SIZE, TEXTURE_SIZE), + rl.draw_texture_pro(self._spinner_texture, rl.Rectangle(0, 0, TEXTURE_SIZE, TEXTURE_SIZE), rl.Rectangle(center.x, center.y, TEXTURE_SIZE, TEXTURE_SIZE), - spinner_origin, rotation, rl.WHITE) - rl.draw_texture_v(comma_texture, comma_position, rl.WHITE) - - # Read user input - if input_str := check_input_non_blocking(): - user_input = input_str + spinner_origin, self._rotation, rl.WHITE) + rl.draw_texture_v(self._comma_texture, comma_position, rl.WHITE) # Display progress bar or text based on user input - if user_input: + text = None + with self._lock: + text = self._text + + if text: y_pos = rl.get_screen_height() - MARGIN - PROGRESS_BAR_HEIGHT - if user_input.isdigit(): - progress = clamp(int(user_input), 0, 100) + if text.isdigit(): + progress = clamp(int(text), 0, 100) bar = rl.Rectangle(center.x - PROGRESS_BAR_WIDTH / 2.0, y_pos, PROGRESS_BAR_WIDTH, PROGRESS_BAR_HEIGHT) rl.draw_rectangle_rounded(bar, 0.5, 10, rl.GRAY) bar.width *= progress / 100.0 rl.draw_rectangle_rounded(bar, 0.5, 10, rl.WHITE) else: - text_size = rl.measure_text_ex(gui_app.font(), user_input, FONT_SIZE, 1.0) - rl.draw_text_ex(gui_app.font(), user_input, + text_size = rl.measure_text_ex(gui_app.font(), text, FONT_SIZE, 1.0) + rl.draw_text_ex(gui_app.font(), text, rl.Vector2(center.x - text_size.x / 2, y_pos), FONT_SIZE, 1.0, rl.WHITE) if __name__ == "__main__": - main() + gui_app.init_window("Spinner") + spinner = Spinner() + spinner.set_text("Spinner text") + for _ in gui_app.render(): + spinner.render() diff --git a/system/ui/text.py b/system/ui/text.py index 33299605d..97d0ebcce 100755 --- a/system/ui/text.py +++ b/system/ui/text.py @@ -1,7 +1,5 @@ #!/usr/bin/env python3 -import sys import pyray as rl - from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.button import gui_button, ButtonStyle from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel @@ -33,32 +31,36 @@ def wrap_text(text, font_size, max_width): return lines +class TextWindow: + def __init__(self, text: str): + self._textarea_rect = rl.Rectangle(MARGIN, MARGIN, gui_app.width - MARGIN * 2, gui_app.height - MARGIN * 2) + self._wrapped_lines = wrap_text(text, FONT_SIZE, self._textarea_rect.width - 20) + self._content_rect = rl.Rectangle(0, 0, self._textarea_rect.width - 20, len(self._wrapped_lines) * LINE_HEIGHT) + self._scroll_panel = GuiScrollPanel(show_vertical_scroll_bar=True) -def main(): - gui_app.init_window("Text") - - text_content = sys.argv[1] if len(sys.argv) > 1 else DEMO_TEXT - - textarea_rect = rl.Rectangle(MARGIN, MARGIN, gui_app.width - MARGIN * 2, gui_app.height - MARGIN * 2) - wrapped_lines = wrap_text(text_content, FONT_SIZE, textarea_rect.width - 20) - content_rect = rl.Rectangle(0, 0, textarea_rect.width - 20, len(wrapped_lines) * LINE_HEIGHT) - scroll_panel = GuiScrollPanel(show_vertical_scroll_bar=True) - - for _ in gui_app.render(): - scroll = scroll_panel.handle_scroll(textarea_rect, content_rect) - - rl.begin_scissor_mode(int(textarea_rect.x), int(textarea_rect.y), int(textarea_rect.width), int(textarea_rect.height)) - for i, line in enumerate(wrapped_lines): - position = rl.Vector2(textarea_rect.x + scroll.x, textarea_rect.y + scroll.y + i * LINE_HEIGHT) - if position.y + LINE_HEIGHT < textarea_rect.y or position.y > textarea_rect.y + textarea_rect.height: + def render(self): + scroll = self._scroll_panel.handle_scroll(self._textarea_rect, self._content_rect) + rl.begin_scissor_mode(int(self._textarea_rect.x), int(self._textarea_rect.y), int(self._textarea_rect.width), int(self._textarea_rect.height)) + for i, line in enumerate(self._wrapped_lines): + position = rl.Vector2(self._textarea_rect.x + scroll.x, self._textarea_rect.y + scroll.y + i * LINE_HEIGHT) + if position.y + LINE_HEIGHT < self._textarea_rect.y or position.y > self._textarea_rect.y + self._textarea_rect.height: continue rl.draw_text_ex(gui_app.font(), line.strip(), position, FONT_SIZE, 0, rl.WHITE) rl.end_scissor_mode() button_bounds = rl.Rectangle(gui_app.width - MARGIN - BUTTON_SIZE.x, gui_app.height - MARGIN - BUTTON_SIZE.y, BUTTON_SIZE.x, BUTTON_SIZE.y) - if gui_button(button_bounds, "Reboot", button_style=ButtonStyle.TRANSPARENT): + ret = gui_button(button_bounds, "Reboot", button_style=ButtonStyle.TRANSPARENT) + if ret: HARDWARE.reboot() + return ret + + +def show_text_in_window(text: str): + gui_app.init_window("Text") + text_window = TextWindow(text) + for _ in gui_app.render(): + text_window.render() if __name__ == "__main__": - main() + show_text_in_window(DEMO_TEXT)