diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 955dc4bfa..2b5c6be6e 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -36,20 +36,29 @@ class WifiIcon(Widget): super().__init__() self.set_rect(rl.Rectangle(0, 0, 86, 64)) + self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 86, 64) self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 86, 64) self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 86, 64) self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 86, 64) self._lock_txt = gui_app.texture("icons_mici/settings/network/new/lock.png", 22, 32) self._network: Network | None = None + self._network_missing = False # if network disappeared from scan results self._scale = 1.0 + self._opacity = 1.0 def set_current_network(self, network: Network): self._network = network + def set_network_missing(self, missing: bool): + self._network_missing = missing + def set_scale(self, scale: float): self._scale = scale + def set_opacity(self, opacity: float): + self._opacity = opacity + @staticmethod def get_strength_icon_idx(strength: int) -> int: return round(strength / 100 * 2) @@ -60,23 +69,26 @@ class WifiIcon(Widget): # Determine which wifi strength icon to use strength = self.get_strength_icon_idx(self._network.strength) - if strength == 2: + if self._network_missing: + strength_icon = self._wifi_slash_txt + elif strength == 2: strength_icon = self._wifi_full_txt elif strength == 1: strength_icon = self._wifi_medium_txt else: strength_icon = self._wifi_low_txt + tint = rl.Color(255, 255, 255, int(255 * self._opacity)) icon_x = int(self._rect.x + (self._rect.width - strength_icon.width * self._scale) // 2) icon_y = int(self._rect.y + (self._rect.height - strength_icon.height * self._scale) // 2) - rl.draw_texture_ex(strength_icon, (icon_x, icon_y), 0.0, self._scale, rl.WHITE) + rl.draw_texture_ex(strength_icon, (icon_x, icon_y), 0.0, self._scale, tint) # Render lock icon at lower right of wifi icon if secured if self._network.security_type not in (SecurityType.OPEN, SecurityType.UNSUPPORTED): lock_scale = self._scale * 1.1 lock_x = int(icon_x + 1 + strength_icon.width * self._scale - self._lock_txt.width * lock_scale / 2) lock_y = int(icon_y + 1 + strength_icon.height * self._scale - self._lock_txt.height * lock_scale / 2) - rl.draw_texture_ex(self._lock_txt, (lock_x, lock_y), 0.0, lock_scale, rl.WHITE) + rl.draw_texture_ex(self._lock_txt, (lock_x, lock_y), 0.0, lock_scale, tint) class WifiItem(BigDialogOptionButton): @@ -93,16 +105,26 @@ class WifiItem(BigDialogOptionButton): self._wifi_icon = WifiIcon() self._wifi_icon.set_current_network(network) + def set_network_missing(self, missing: bool): + self._wifi_icon.set_network_missing(missing) + def set_current_network(self, network: Network): self._network = network self._wifi_icon.set_current_network(network) + # reset if we see the network again + self.set_enabled(True) + self.set_network_missing(False) + def _render(self, _): + disabled_alpha = 0.35 if not self.enabled else 1.0 + if self._network.is_connected: selected_x = int(self._rect.x - self._selected_txt.width / 2) selected_y = int(self._rect.y + (self._rect.height - self._selected_txt.height) / 2) rl.draw_texture(self._selected_txt, selected_x, selected_y, rl.WHITE) + self._wifi_icon.set_opacity(disabled_alpha) self._wifi_icon.set_scale((1.0 if self._selected else 0.65) * 0.7) self._wifi_icon.render(rl.Rectangle( self._rect.x + self.LEFT_MARGIN, @@ -113,11 +135,11 @@ class WifiItem(BigDialogOptionButton): if self._selected: self._label.set_font_size(self.SELECTED_HEIGHT) - self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.9))) + self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.9 * disabled_alpha))) self._label.set_font_weight(FontWeight.DISPLAY) else: self._label.set_font_size(self.HEIGHT) - self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.58))) + self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.58 * disabled_alpha))) self._label.set_font_weight(FontWeight.DISPLAY_REGULAR) label_offset = self.LEFT_MARGIN + self._wifi_icon.rect.width + 20 @@ -314,9 +336,6 @@ class NetworkInfoPage(NavWidget): class WifiUIMici(BigMultiOptionDialog): - # Wait this long after user interacts with widget to update network list - INACTIVITY_TIMEOUT = 1 - def __init__(self, wifi_manager: WifiManager, back_callback: Callable): super().__init__([], None) @@ -332,10 +351,6 @@ class WifiUIMici(BigMultiOptionDialog): self._connecting: str | None = None self._networks: dict[str, Network] = {} - # widget state - self._last_interaction_time = -float('inf') - self._restore_selection = False - self._wifi_manager.add_callbacks( need_auth=self._on_need_auth, activated=self._on_activated, @@ -348,11 +363,12 @@ class WifiUIMici(BigMultiOptionDialog): # Call super to prepare scroller; selection scroll is handled dynamically super().show_event() self._wifi_manager.set_active(True) - self._last_interaction_time = -float('inf') def hide_event(self): super().hide_event() self._wifi_manager.set_active(False) + # clear scroller items to remove old networks on next show + self._scroller._items.clear() def _open_network_manage_page(self, result=None): self._network_info_page.update_networks(self._networks) @@ -372,27 +388,28 @@ class WifiUIMici(BigMultiOptionDialog): self._network_info_page.update_networks(self._networks) def _update_buttons(self): - # Don't update buttons while user is actively interacting - if rl.get_time() - self._last_interaction_time < self.INACTIVITY_TIMEOUT: - return + # Only add new buttons to the end. Update existing buttons without re-sorting so user can freely scroll around for network in self._networks.values(): - # pop and re-insert to eliminate stuttering on update (prevents position lost for a frame) network_button_idx = next((i for i, btn in enumerate(self._scroller._items) if btn.option == network.ssid), None) if network_button_idx is not None: - network_button = self._scroller._items.pop(network_button_idx) # Update network on existing button - network_button.set_current_network(network) + self._scroller._items[network_button_idx].set_current_network(network) else: network_button = WifiItem(network) + self._scroller.add_widget(network_button) - self._scroller.add_widget(network_button) + # Move connected network to the start + connected_btn_idx = next((i for i, btn in enumerate(self._scroller._items) if btn._network.is_connected), None) + if connected_btn_idx is not None and connected_btn_idx > 0: + self._scroller._items.insert(0, self._scroller._items.pop(connected_btn_idx)) + self._scroller._layout() # fixes selected style single frame stutter - # remove networks no longer present - self._scroller._items[:] = [btn for btn in self._scroller._items if btn.option in self._networks] - - # try to restore previous selection to prevent jumping from adding/removing/reordering buttons - self._restore_selection = True + # Disable networks no longer present + for btn in self._scroller._items: + if btn.option not in self._networks: + btn.set_enabled(False) + btn.set_network_missing(True) def _connect_with_password(self, ssid: str, password: str): if password: @@ -440,19 +457,7 @@ class WifiUIMici(BigMultiOptionDialog): def _on_disconnected(self): self._connecting = None - def _update_state(self): - super()._update_state() - if self.is_pressed: - self._last_interaction_time = rl.get_time() - def _render(self, _): - # Update Scroller layout and restore current selection whenever buttons are updated, before first render - current_selection = self.get_selected_option() - if self._restore_selection and current_selection in self._networks: - self._scroller._layout() - BigMultiOptionDialog._on_option_selected(self, current_selection) - self._restore_selection = None - super()._render(_) if not self._networks: