mici ui: wifi scanning card (#37564)

* start

* yes

* no more show

* clean up
This commit is contained in:
Shane Smiskol
2026-03-04 23:07:37 -08:00
committed by GitHub
parent e59f675715
commit 3cc9d89d45
@@ -3,7 +3,6 @@ import numpy as np
import pyray as rl
from collections.abc import Callable
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationDialogV2
from openpilot.selfdrive.ui.mici.widgets.button import BigButton, LABEL_COLOR
@@ -14,39 +13,26 @@ from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, SecurityT
class LoadingAnimation(Widget):
HIDE_TIME = 4
RADIUS = 8
SPACING = 24 # center-to-center: diameter (16) + gap (8)
Y_MAG = 11.2
def __init__(self):
super().__init__()
self._opacity_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps)
self._opacity_target = 1.0
self._hide_time = 0.0
def show_event(self):
self._opacity_target = 1.0
self._hide_time = rl.get_time()
w = self.SPACING * 2 + self.RADIUS * 2
h = self.RADIUS * 2 + int(self.Y_MAG)
self.set_rect(rl.Rectangle(0, 0, w, h))
def _render(self, _):
if rl.get_time() - self._hide_time > self.HIDE_TIME:
self._opacity_target = 0.0
self._opacity_filter.update(self._opacity_target)
if self._opacity_filter.x < 0.01:
return
cx = int(self._rect.x + self._rect.width / 2)
cy = int(self._rect.y + self._rect.height / 2)
y_mag = 7
anim_scale = 4
spacing = 14
# Balls rest at bottom center; bounce upward
base_x = int(self._rect.x + self._rect.width / 2)
base_y = int(self._rect.y + self._rect.height - self.RADIUS)
for i in range(3):
x = cx - spacing + i * spacing
y = int(cy + min(math.sin((rl.get_time() - i * 0.2) * anim_scale) * y_mag, 0))
alpha = int(np.interp(cy - y, [0, y_mag], [255 * 0.45, 255 * 0.9]) * self._opacity_filter.x)
rl.draw_circle(x, y, 5, rl.Color(255, 255, 255, alpha))
x = base_x + (i - 1) * self.SPACING
y = int(base_y + min(math.sin((rl.get_time() - i * 0.2) * 4) * self.Y_MAG, 0))
alpha = int(np.interp(base_y - y, [0, self.Y_MAG], [255 * 0.45, 255 * 0.9]))
rl.draw_circle(x, y, self.RADIUS, rl.Color(255, 255, 255, alpha))
class WifiIcon(Widget):
@@ -270,11 +256,26 @@ class ForgetButton(Widget):
rl.draw_texture_ex(self._trash_txt, (trash_x, trash_y), 0, 1.0, rl.WHITE)
class ScanningButton(BigButton):
def __init__(self):
super().__init__("", "searching for networks")
self.set_enabled(False)
self._loading_animation = LoadingAnimation()
def _draw_content(self, btn_y: float):
super()._draw_content(btn_y)
anim = self._loading_animation
x = self._rect.x + self._rect.width - anim.rect.width - 40
y = btn_y + self._rect.height - anim.rect.height - 30
anim.set_position(x, y)
anim.render()
class WifiUIMici(NavScroller):
def __init__(self, wifi_manager: WifiManager):
super().__init__()
self._loading_animation = LoadingAnimation()
self._scanning_btn = ScanningButton()
self._wifi_manager = wifi_manager
self._networks: dict[str, Network] = {}
@@ -288,9 +289,9 @@ class WifiUIMici(NavScroller):
def show_event(self):
# Clear scroller items and update from latest scan results
super().show_event()
self._loading_animation.show_event()
self._wifi_manager.set_active(True)
self._scroller.items.clear()
self._scroller.add_widget(self._scanning_btn)
# trigger button update on latest sorted networks
self._on_network_updated(self._wifi_manager.networks)
@@ -310,6 +311,11 @@ class WifiUIMici(NavScroller):
btn.set_click_callback(lambda ssid=network.ssid: self._connect_to_network(ssid))
self._scroller.add_widget(btn)
# Keep scanning button at the end
items = self._scroller.items
if self._scanning_btn in items:
items.append(items.pop(items.index(self._scanning_btn)))
# Mark networks no longer in scan results (display handled by _update_state)
for btn in self._scroller.items:
if isinstance(btn, WifiButton) and btn.network.ssid not in self._networks:
@@ -371,16 +377,4 @@ class WifiUIMici(NavScroller):
self._move_network_to_front(self._wifi_manager.wifi_state.ssid)
# Show loading animation near end
max_scroll = max(self._scroller.content_size - self._scroller.rect.width, 1)
progress = -self._scroller.scroll_panel.get_offset() / max_scroll
if progress > 0.8 or len(self._scroller.items) <= 1:
self._loading_animation.show_event()
def _render(self, _):
super()._render(self._rect)
anim_w = 90
anim_x = self._rect.x + self._rect.width - anim_w
anim_y = self._rect.y + self._rect.height - 25 + 2
self._loading_animation.render(rl.Rectangle(anim_x, anim_y, anim_w, 20))