diff --git a/selfdrive/assets/icons_mici/alerts_bell.png b/selfdrive/assets/icons_mici/alerts_bell.png new file mode 100644 index 00000000000000..5d775425ebfe7c --- /dev/null +++ b/selfdrive/assets/icons_mici/alerts_bell.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ce1d357acadd798939b398cce1761ceb05564b44f2a5bc6865c7842e60e79f2 +size 1474 diff --git a/selfdrive/assets/icons_mici/alerts_pill.png b/selfdrive/assets/icons_mici/alerts_pill.png new file mode 100644 index 00000000000000..29ab2ad5b3fa50 --- /dev/null +++ b/selfdrive/assets/icons_mici/alerts_pill.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b3fe73cd1a24c05346a9b4a02e4f900a314c83a422beb38b0f88f91389582cd4 +size 3960 diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 77b665f5d8b822..16a40b6d4ca69e 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -7,8 +7,8 @@ from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.layouts import HBoxLayout from openpilot.system.ui.widgets.icon_widget import IconWidget -from openpilot.system.ui.widgets.label import UnifiedLabel -from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos +from openpilot.system.ui.widgets.label import UnifiedLabel, gui_label +from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, DEFAULT_TEXT_COLOR from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.version import RELEASE_BRANCHES @@ -28,6 +28,34 @@ } +class AlertsPill(Widget): + def __init__(self): + super().__init__() + self.set_rect(rl.Rectangle(0, 0, 104, 52)) + + self._pill_bg_txt = gui_app.texture("icons_mici/alerts_pill.png", 104, 52) + self._bell_txt = gui_app.texture("icons_mici/alerts_bell.png", 28, 30) + self._alert_count_callback: Callable[[], int] | None = None + + def set_alert_count_callback(self, callback: Callable[[], int] | None): + self._alert_count_callback = callback + + def _render(self, _): + alert_count = self._alert_count_callback() if self._alert_count_callback else 0 + if alert_count > 0: + 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) + + bell_x = self.rect.x + 20 + bell_y = self.rect.y + (pill_h - self._bell_txt.height) / 2 + rl.draw_texture_ex(self._bell_txt, rl.Vector2(bell_x, bell_y), 0.0, 1.0, DEFAULT_TEXT_COLOR) + + count_rect = rl.Rectangle(self.rect.x, self.rect.y, pill_w - 20, pill_h) + gui_label(count_rect, str(alert_count), font_size=36, + alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) + + class NetworkIcon(Widget): def __init__(self): super().__init__() @@ -84,6 +112,7 @@ class MiciHomeLayout(Widget): def __init__(self): super().__init__() self._on_settings_click: Callable | None = None + self._alert_count_callback: Callable[[], int] | None = None self._last_refresh = 0 self._mouse_down_t: None | float = None @@ -96,6 +125,8 @@ def __init__(self): self._experimental_icon = IconWidget("icons_mici/experimental_mode.png", (48, 48)) self._mic_icon = IconWidget("icons_mici/microphone.png", (32, 46)) + self._alerts_pill = AlertsPill() + self._status_bar_layout = HBoxLayout([ IconWidget("icons_mici/settings.png", (48, 48), opacity=0.9), NetworkIcon(), @@ -141,8 +172,9 @@ def _update_state(self): self._last_refresh = rl.get_time() self._update_params() - def set_callbacks(self, on_settings: Callable | None = None): + def set_callbacks(self, on_settings: Callable | None = None, alert_count_callback: Callable[[], int] | None = None): self._on_settings_click = on_settings + self._alerts_pill.set_alert_count_callback(alert_count_callback) def _handle_mouse_release(self, mouse_pos: MousePos): if not self._did_long_press: @@ -203,3 +235,8 @@ def _render(self, _): footer_rect = rl.Rectangle(self.rect.x + HOME_PADDING, self.rect.y + self.rect.height - 48, self.rect.width - HOME_PADDING, 48) self._status_bar_layout.render(footer_rect) + + # TODO: add alignment to hboxlayout and add to there + self._alerts_pill.set_position(self.rect.x + self.rect.width - self._alerts_pill.rect.width - HOME_PADDING, + self.rect.y + self.rect.height - self._alerts_pill.rect.height) + self._alerts_pill.render() diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index 95258e2795d1f4..b25c4af7b2b504 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -58,7 +58,10 @@ def __init__(self): gui_app.push_widget(self._onboarding_window) def _setup_callbacks(self): - self._home_layout.set_callbacks(on_settings=lambda: gui_app.push_widget(self._settings_layout)) + self._home_layout.set_callbacks( + on_settings=lambda: gui_app.push_widget(self._settings_layout), + alert_count_callback=self._alerts_layout.active_alerts, + ) self._onroad_layout.set_click_callback(lambda: self._scroll_to(self._home_layout)) device.add_interactive_timeout_callback(self._on_interactive_timeout) @@ -66,6 +69,11 @@ def _scroll_to(self, layout: Widget): layout_x = int(layout.rect.x) self._scroller.scroll_to(layout_x, smooth=True) + def _update_state(self): + super()._update_state() + # TODO: Hack to run alert updates while not in view. Add a nav stack tick? + self._alerts_layout._update_state() + def _render(self, _): if not self._setup: if self._alerts_layout.active_alerts() > 0: