From 1ce03e79f063b1993abc976b2d72c5d3f1c9123b Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Tue, 14 Apr 2026 17:16:17 -0500 Subject: [PATCH] ui --- selfdrive/ui/mici/onroad/cameraview.py | 2 +- system/ui/lib/application.py | 8 +- system/ui/spinner.py | 155 ++++++++++++++++++------- 3 files changed, 119 insertions(+), 46 deletions(-) diff --git a/selfdrive/ui/mici/onroad/cameraview.py b/selfdrive/ui/mici/onroad/cameraview.py index 44d0c9fa7..0fd1f6503 100644 --- a/selfdrive/ui/mici/onroad/cameraview.py +++ b/selfdrive/ui/mici/onroad/cameraview.py @@ -12,7 +12,7 @@ from openpilot.system.ui.widgets import Widget from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus CONNECTION_RETRY_INTERVAL = 0.2 # seconds between connection attempts -MICI_FORCE_TEXTURE_CAMERA = os.getenv("MICI_FORCE_TEXTURE_CAMERA", "1") == "1" +MICI_FORCE_TEXTURE_CAMERA = os.getenv("MICI_FORCE_TEXTURE_CAMERA", "0") == "1" VERSION = """ #version 300 es diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 8c2ec102b..aa9aac61a 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -22,7 +22,8 @@ from openpilot.system.hardware import HARDWARE, PC from openpilot.system.ui.lib.multilang import multilang from openpilot.common.realtime import Ratekeeper -_DEFAULT_FPS = int(os.getenv("FPS", {'tizi': 20}.get(HARDWARE.get_device_type(), 60))) +DEVICE_TYPE = HARDWARE.get_device_type() +_DEFAULT_FPS = int(os.getenv("FPS", {'tizi': 20}.get(DEVICE_TYPE, 60))) FPS_LOG_INTERVAL = 5 # Seconds between logging FPS drops FPS_DROP_THRESHOLD = 0.9 # FPS drop threshold for triggering a warning FPS_CRITICAL_THRESHOLD = 0.5 # Critical threshold for triggering strict actions @@ -32,6 +33,7 @@ TOUCH_HISTORY_TIMEOUT = 3.0 # Seconds before touch points fade out BIG_UI = os.getenv("BIG", "0") == "1" ENABLE_VSYNC = os.getenv("ENABLE_VSYNC", "0") == "1" +MICI_FORCE_RENDER_TEXTURE = os.getenv("MICI_FORCE_RENDER_TEXTURE", "1" if DEVICE_TYPE == "mici" else "0") == "1" SHOW_FPS = os.getenv("SHOW_FPS") == "1" SHOW_TOUCHES = os.getenv("SHOW_TOUCHES") == "1" STRICT_MODE = os.getenv("STRICT_MODE") == "1" @@ -279,10 +281,12 @@ class GuiApplication: rl.init_window(self._scaled_width, self._scaled_height, title) - needs_render_texture = self._scale != 1.0 or BURN_IN_MODE or RECORD + needs_render_texture = self._scale != 1.0 or BURN_IN_MODE or RECORD or MICI_FORCE_RENDER_TEXTURE if self._scale != 1.0: rl.set_mouse_scale(1 / self._scale, 1 / self._scale) if needs_render_texture: + if MICI_FORCE_RENDER_TEXTURE: + cloudlog.warning("Forcing render texture path for mici UI") self._render_texture = rl.load_render_texture(self._scaled_width, self._scaled_height) rl.set_texture_filter(self._render_texture.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR) diff --git a/system/ui/spinner.py b/system/ui/spinner.py index 2a48b3889..d7668ea8f 100755 --- a/system/ui/spinner.py +++ b/system/ui/spinner.py @@ -1,30 +1,55 @@ #!/usr/bin/env python3 +from dataclasses import dataclass import pyray as rl import select import sys -from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.text import wrap_text from openpilot.system.ui.widgets import Widget -# Constants -if gui_app.big_ui(): - PROGRESS_BAR_WIDTH = 1000 - PROGRESS_BAR_HEIGHT = 20 - TEXTURE_SIZE = 360 - WRAPPED_SPACING = 50 - CENTERED_SPACING = 150 -else: - PROGRESS_BAR_WIDTH = 268 - PROGRESS_BAR_HEIGHT = 10 - TEXTURE_SIZE = 140 - WRAPPED_SPACING = 10 - CENTERED_SPACING = 20 + +@dataclass(frozen=True) +class SpinnerTheme: + progress_bar_width: int + progress_bar_height: int + texture_size: int + wrapped_spacing: int + centered_spacing: int + text_font_size: int + line_height: int + horizontal_margin: int + progress_label_font_size: int | None = None + + +BIG_THEME = SpinnerTheme( + progress_bar_width=1000, + progress_bar_height=20, + texture_size=360, + wrapped_spacing=50, + centered_spacing=150, + text_font_size=96, + line_height=104, + horizontal_margin=100, +) + +SMALL_THEME = SpinnerTheme( + progress_bar_width=320, + progress_bar_height=12, + texture_size=92, + wrapped_spacing=14, + centered_spacing=26, + text_font_size=30, + line_height=36, + horizontal_margin=24, + progress_label_font_size=24, +) + + DEGREES_PER_SECOND = 360.0 # one full rotation per second -MARGIN_H = 100 -FONT_SIZE = 96 -LINE_HEIGHT = 104 +SMALL_MAX_WRAPPED_LINES = 4 +PROGRESS_LABEL_SPACING = 12 DARKGRAY = (55, 55, 55, 255) @@ -35,11 +60,14 @@ def clamp(value, min_value, max_value): class Spinner(Widget): def __init__(self): super().__init__() - self._comma_texture = gui_app.texture("images/spinner_comma.png", TEXTURE_SIZE, TEXTURE_SIZE) - self._spinner_texture = gui_app.texture("images/spinner_track.png", TEXTURE_SIZE, TEXTURE_SIZE, alpha_premultiply=True) + self._theme = BIG_THEME if gui_app.big_ui() else SMALL_THEME + self._comma_texture = gui_app.texture("images/spinner_comma.png", self._theme.texture_size, self._theme.texture_size) + self._spinner_texture = gui_app.texture("images/spinner_track.png", self._theme.texture_size, self._theme.texture_size, alpha_premultiply=True) self._rotation = 0.0 self._progress: int | None = None self._wrapped_lines: list[str] = [] + self._text_font_size = self._theme.text_font_size + self._line_height = self._theme.line_height def set_text(self, text: str) -> None: if text.isdigit(): @@ -47,45 +75,86 @@ class Spinner(Widget): self._wrapped_lines = [] else: self._progress = None - self._wrapped_lines = wrap_text(text, FONT_SIZE, gui_app.width - MARGIN_H) + self._wrapped_lines, self._text_font_size = self._wrap_text(text) + scale = self._text_font_size / self._theme.text_font_size + self._line_height = round(self._theme.line_height * scale) + + def _wrap_text(self, text: str) -> tuple[list[str], int]: + max_width = gui_app.width - self._theme.horizontal_margin * 2 + font_sizes = [self._theme.text_font_size] + if not gui_app.big_ui(): + font_sizes.extend([28, 26, 24]) + + for font_size in font_sizes: + wrapped_lines = wrap_text(text, font_size, max_width) + if gui_app.big_ui() or len(wrapped_lines) <= SMALL_MAX_WRAPPED_LINES: + return wrapped_lines, font_size + + return wrapped_lines, font_sizes[-1] + + def _content_height(self) -> float: + if self._progress is not None: + height = self._theme.texture_size + self._theme.centered_spacing + self._theme.progress_bar_height + if self._theme.progress_label_font_size is not None: + height += PROGRESS_LABEL_SPACING + self._theme.progress_label_font_size + return height + + if self._wrapped_lines: + return self._theme.texture_size + self._theme.wrapped_spacing + len(self._wrapped_lines) * self._line_height + + return self._theme.texture_size + + def _draw_spinner(self, center_x: float, center_y: float) -> None: + spinner_origin = rl.Vector2(self._theme.texture_size / 2.0, self._theme.texture_size / 2.0) + comma_position = rl.Vector2(center_x - self._theme.texture_size / 2.0, center_y - self._theme.texture_size / 2.0) + + rl.draw_texture_pro( + self._spinner_texture, + rl.Rectangle(0, 0, self._theme.texture_size, self._theme.texture_size), + rl.Rectangle(center_x, center_y, self._theme.texture_size, self._theme.texture_size), + spinner_origin, + self._rotation, + rl.WHITE, + ) + rl.draw_texture_v(self._comma_texture, comma_position, rl.WHITE) def _render(self, rect: rl.Rectangle): - if self._wrapped_lines: - # Calculate total height required for spinner and text - spacing = WRAPPED_SPACING - total_height = TEXTURE_SIZE + spacing + len(self._wrapped_lines) * LINE_HEIGHT - center_y = (rect.height - total_height) / 2.0 + TEXTURE_SIZE / 2.0 - else: - # Center spinner vertically - spacing = CENTERED_SPACING - center_y = rect.height / 2.0 - y_pos = center_y + TEXTURE_SIZE / 2.0 + spacing + total_height = self._content_height() + top_y = rect.y + (rect.height - total_height) / 2.0 + center_x = rect.x + rect.width / 2.0 + center_y = top_y + self._theme.texture_size / 2.0 - center = rl.Vector2(rect.width / 2.0, center_y) - 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) + y_pos = top_y + self._theme.texture_size + y_pos += self._theme.wrapped_spacing if self._wrapped_lines else self._theme.centered_spacing delta_time = rl.get_frame_time() self._rotation = (self._rotation + DEGREES_PER_SECOND * delta_time) % 360.0 - # Draw rotating spinner and static comma logo - 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, self._rotation, rl.WHITE) - rl.draw_texture_v(self._comma_texture, comma_position, rl.WHITE) + self._draw_spinner(center_x, center_y) - # Display the progress bar or text based on user input if self._progress is not None: - bar = rl.Rectangle(center.x - PROGRESS_BAR_WIDTH / 2.0, y_pos, PROGRESS_BAR_WIDTH, PROGRESS_BAR_HEIGHT) + bar = rl.Rectangle( + center_x - self._theme.progress_bar_width / 2.0, + y_pos, + self._theme.progress_bar_width, + self._theme.progress_bar_height, + ) rl.draw_rectangle_rounded(bar, 1, 10, DARKGRAY) bar.width *= self._progress / 100.0 rl.draw_rectangle_rounded(bar, 1, 10, rl.WHITE) + if self._theme.progress_label_font_size is not None: + progress_text = f"{self._progress}%" + font = gui_app.font(FontWeight.BOLD) + text_size = measure_text_cached(font, progress_text, self._theme.progress_label_font_size) + text_pos = rl.Vector2(center_x - text_size.x / 2.0, y_pos + bar.height + PROGRESS_LABEL_SPACING) + rl.draw_text_ex(font, progress_text, text_pos, self._theme.progress_label_font_size, 0.0, rl.Color(255, 255, 255, 180)) elif self._wrapped_lines: + font = gui_app.font(FontWeight.BOLD if not gui_app.big_ui() else FontWeight.NORMAL) for i, line in enumerate(self._wrapped_lines): - text_size = measure_text_cached(gui_app.font(), line, FONT_SIZE) - rl.draw_text_ex(gui_app.font(), line, rl.Vector2(center.x - text_size.x / 2, y_pos + i * LINE_HEIGHT), - FONT_SIZE, 0.0, rl.WHITE) + text_size = measure_text_cached(font, line, self._text_font_size) + line_y = y_pos + i * self._line_height + rl.draw_text_ex(font, line, rl.Vector2(center_x - text_size.x / 2.0, line_y), self._text_font_size, 0.0, rl.WHITE) def _read_stdin():