ui: custom alert icons (#38917)

* custom alerts and alert pill better

* add comment back

* fix
This commit is contained in:
stef
2026-09-14 23:03:25 -04:00
committed by GitHub
parent e6aa405e08
commit ffef0e6d3b
3 changed files with 38 additions and 32 deletions
+13 -19
View File
@@ -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:
+1 -1
View File
@@ -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))
@@ -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