From ffef0e6d3ba10c9fac470b4d316db92f05ca1d35 Mon Sep 17 00:00:00 2001 From: stef <19478336+stefpi@users.noreply.github.com> Date: Mon, 14 Sep 2026 23:03:25 -0400 Subject: [PATCH] ui: custom alert icons (#38917) * custom alerts and alert pill better * add comment back * fix --- openpilot/selfdrive/ui/mici/layouts/home.py | 32 +++++++---------- openpilot/selfdrive/ui/mici/layouts/main.py | 2 +- .../ui/mici/layouts/offroad_alerts.py | 36 ++++++++++++------- 3 files changed, 38 insertions(+), 32 deletions(-) diff --git a/openpilot/selfdrive/ui/mici/layouts/home.py b/openpilot/selfdrive/ui/mici/layouts/home.py index 9517701b50..b852bf9ea6 100644 --- a/openpilot/selfdrive/ui/mici/layouts/home.py +++ b/openpilot/selfdrive/ui/mici/layouts/home.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import datetime import math import time @@ -39,16 +41,13 @@ class AlertsPill(Widget): self.set_rect(rl.Rectangle(0, 0, 104, 52)) self._pill_bg_txt = gui_app.texture("icons_mici/alerts_pill.png", 104, 52) - self._icon_red = gui_app.texture("icons_mici/offroad_alerts/red_warning.png", 36, 36) - self._icon_orange = gui_app.texture("icons_mici/offroad_alerts/orange_warning.png", 36, 36) - self._icon_green = gui_app.texture("icons_mici/offroad_alerts/green_wheel.png", 36, 36) self._alert_count_callback: Callable[[], int] | None = None - self._max_severity_callback: Callable[[], int | None] | None = None + self._alert_icon_callback: Callable[[], rl.Texture | None] | None = None def set_alert_count_callback(self, callback: Callable[[], int] | None, - severity_callback: Callable[[], int | None] | None = None): + icon_callback: Callable[[], rl.Texture | None] | None = None): self._alert_count_callback = callback - self._max_severity_callback = severity_callback + self._alert_icon_callback = icon_callback def _render(self, _): alert_count = self._alert_count_callback() if self._alert_count_callback else 0 @@ -56,17 +55,12 @@ class AlertsPill(Widget): pill_w, pill_h = self._pill_bg_txt.width, self._pill_bg_txt.height rl.draw_texture_ex(self._pill_bg_txt, rl.Vector2(self.rect.x, self.rect.y), 0.0, 1.0, rl.WHITE) - severity = self._max_severity_callback() if self._max_severity_callback else None - if severity == -1: - warning_txt = self._icon_green - elif severity is not None and severity > 0: - warning_txt = self._icon_red - else: - warning_txt = self._icon_orange - - warn_x = self.rect.x + self.ICON_OFFSET - warn_y = self.rect.y + (pill_h - warning_txt.height) / 2 - rl.draw_texture_ex(warning_txt, rl.Vector2(warn_x, warn_y), 0.0, 1.0, rl.WHITE) + warning_txt = self._alert_icon_callback() if self._alert_icon_callback else None + if warning_txt is not None: + scale = 36 / max(warning_txt.width, warning_txt.height) + warn_x = self.rect.x + self.ICON_OFFSET + warn_y = self.rect.y + (pill_h - warning_txt.height * scale) / 2 + rl.draw_texture_ex(warning_txt, rl.Vector2(warn_x, warn_y), 0.0, scale, rl.WHITE) count_rect = rl.Rectangle(self.rect.x + self.COUNT_OFFSET, self.rect.y, pill_w - self.COUNT_OFFSET, pill_h) gui_label(count_rect, str(alert_count), font_size=36, @@ -187,11 +181,11 @@ class MiciHomeLayout(Widget): def set_callbacks(self, on_settings: Callable | None = None, on_alerts: Callable | None = None, alert_count_callback: Callable[[], int] | None = None, - max_severity_callback: Callable[[], int | None] | None = None): + alert_icon_callback: Callable[[], rl.Texture | None] | None = None): self._on_settings_click = on_settings self._on_alerts_click = on_alerts self._alert_count_callback = alert_count_callback - self._alerts_pill.set_alert_count_callback(alert_count_callback, max_severity_callback) + self._alerts_pill.set_alert_count_callback(alert_count_callback, alert_icon_callback) def _handle_mouse_release(self, mouse_pos: MousePos): if not self._did_long_press: diff --git a/openpilot/selfdrive/ui/mici/layouts/main.py b/openpilot/selfdrive/ui/mici/layouts/main.py index 7b37747ef7..5364e36da1 100644 --- a/openpilot/selfdrive/ui/mici/layouts/main.py +++ b/openpilot/selfdrive/ui/mici/layouts/main.py @@ -74,7 +74,7 @@ class MiciMainLayout(Scroller): on_settings=lambda: gui_app.push_widget(self._settings_layout), on_alerts=lambda: self._scroll_to(self._alerts_layout), alert_count_callback=self._alerts_layout.active_alerts, - max_severity_callback=self._alerts_layout.max_severity, + alert_icon_callback=self._alerts_layout.highest_severity_icon, ) for layout in (self._car_onroad_layout, self._body_onroad_layout): layout.set_click_callback(lambda: self._scroll_to(self._home_layout)) diff --git a/openpilot/selfdrive/ui/mici/layouts/offroad_alerts.py b/openpilot/selfdrive/ui/mici/layouts/offroad_alerts.py index 40e074d455..6175a753bb 100644 --- a/openpilot/selfdrive/ui/mici/layouts/offroad_alerts.py +++ b/openpilot/selfdrive/ui/mici/layouts/offroad_alerts.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pyray as rl import re import threading @@ -29,6 +31,7 @@ class AlertData: text: str severity: int visible: bool = False + icon: str | None = None class AlertItem(Widget): @@ -56,10 +59,20 @@ class AlertItem(Widget): self._bg_big = gui_app.texture("icons_mici/offroad_alerts/big_alert.png", self.ALERT_WIDTH, self.ALERT_HEIGHT_BIG) self._bg_big_pressed = gui_app.texture("icons_mici/offroad_alerts/big_alert_pressed.png", self.ALERT_WIDTH, self.ALERT_HEIGHT_BIG) - # Load warning icons + # Load alert icons self._icon_orange = gui_app.texture("icons_mici/offroad_alerts/orange_warning.png", self.ICON_SIZE, self.ICON_SIZE) self._icon_red = gui_app.texture("icons_mici/offroad_alerts/red_warning.png", self.ICON_SIZE, self.ICON_SIZE) self._icon_green = gui_app.texture("icons_mici/offroad_alerts/green_wheel.png", self.ICON_SIZE, self.ICON_SIZE) + self._custom_icon = gui_app.texture(alert_data.icon, self.ICON_SIZE, self.ICON_SIZE) if alert_data.icon else None + + if self._custom_icon is not None: + self._icon = self._custom_icon + elif alert_data.severity == -1: + self._icon = self._icon_green + elif alert_data.severity > 0: + self._icon = self._icon_red + else: + self._icon = self._icon_orange self._title_label = UnifiedLabel(text="", font_size=32, font_weight=FontWeight.SEMI_BOLD, text_color=self.TEXT_COLOR, alignment=TextAlignment.LEFT, @@ -75,6 +88,10 @@ class AlertItem(Widget): self._update_content() + @property + def icon(self) -> rl.Texture: + return self._icon + def _split_text(self, text: str) -> tuple[str, str]: """Split text into title (first sentence) and body (remaining text).""" # Find the end of the first sentence (period, exclamation, or question mark followed by space or end) @@ -176,16 +193,9 @@ class AlertItem(Widget): self._body_label.render(body_rect) # Draw warning icon on the right side - # Use green icon for update alerts (severity = -1), red for high severity, orange for low severity - if self.alert_data.severity == -1: - icon_texture = self._icon_green - elif self.alert_data.severity > 0: - icon_texture = self._icon_red - else: - icon_texture = self._icon_orange icon_x = self._rect.x + self.ALERT_WIDTH - self.ALERT_PADDING - self.ICON_SIZE icon_y = self._rect.y + self.ALERT_PADDING - rl.draw_texture_ex(icon_texture, rl.Vector2(icon_x, icon_y), 0.0, 1.0, rl.WHITE) + rl.draw_texture_ex(self._icon, rl.Vector2(icon_x, icon_y), 0.0, 1.0, rl.WHITE) class MiciOffroadAlerts(Scroller): @@ -214,8 +224,10 @@ class MiciOffroadAlerts(Scroller): def active_alerts(self) -> int: return sum(alert.visible for alert in self.sorted_alerts) - def max_severity(self) -> int | None: - return max((alert.severity for alert in self.sorted_alerts if alert.visible), default=None) + def highest_severity_icon(self) -> rl.Texture | None: + item = max((item for item in self.alert_items if item.alert_data.visible), + key=lambda item: (item.alert_data.severity, bool(item.alert_data.icon)), default=None) + return item.icon if item is not None else None def scrolling(self): return self._scroller.scroll_panel.is_touch_valid() @@ -235,7 +247,7 @@ class MiciOffroadAlerts(Scroller): # Add regular alerts sorted by severity for key, config in sorted(OFFROAD_ALERTS.items(), key=lambda x: x[1].get("severity", 0), reverse=True): severity = config.get("severity", 0) - alert_data = AlertData(key=key, text="", severity=severity) + alert_data = AlertData(key=key, text="", severity=severity, icon=config.get("icon")) self.sorted_alerts.append(alert_data) # Create alert item widget