From 2d6fd6c84b97cd12fab8e78859b1f079eab73fb9 Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Wed, 24 Jun 2026 12:53:30 +0545 Subject: [PATCH] refactor(controlcenter): simplify refresh handling and add cancellation hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The changes replace timeout‑based refreshes with animation‑aware callbacks, improving responsiveness and preventing stale pending operations. --- src/window/controlcenter/bluetooth.py | 71 ++++++++++++++++++--------- src/window/controlcenter/wifi.py | 52 ++++++++++++++------ src/window/launcher/main.py | 10 +++- 3 files changed, 92 insertions(+), 41 deletions(-) diff --git a/src/window/controlcenter/bluetooth.py b/src/window/controlcenter/bluetooth.py index ce95413..e072638 100644 --- a/src/window/controlcenter/bluetooth.py +++ b/src/window/controlcenter/bluetooth.py @@ -382,7 +382,6 @@ class BluetoothConnections(Box): on_clicked=self.toggle_other_devices, ) self.other_devices = Box(spacing=4, orientation="vertical") - self.other_devices.set_size_request(-1, 150) self.other_devices_scrolled = AnimatedScrollable( min_content_size=(303, 0), @@ -441,6 +440,7 @@ class BluetoothConnections(Box): def on_hide(self, *_): """Called when the widget is hidden (popup closed)""" self.stop_device_monitoring() + self._cancel_pending_refresh() if self.other_devices.get_visible(): self.other_devices.set_visible(False) self.other_devices_scrolled.snap_to_size(0) @@ -456,25 +456,51 @@ class BluetoothConnections(Box): def toggle_other_devices(self, *_): """Toggle the visibility of other devices section""" current_state = self.other_devices.get_visible() - self.other_devices.set_visible(not current_state) - self.update_scan_label() + self._cancel_pending_refresh() + if current_state: + self.other_devices.set_visible(False) + self.other_devices_scrolled.snap_to_size(0) + self.update_scan_label() + if ( + self.client + and self.client.scanning + and hasattr(self.client, "stop_scan") + ): + self.client.stop_scan() + else: + self.other_devices.set_visible(True) + self.update_scan_label() + if ( + self.client + and not self.client.scanning + and hasattr(self.client, "scan") + ): + self.client.scan() + # Defer refresh until after the height animation completes + GLib.idle_add(self._refresh_after_animation) - if self.client: - if self.other_devices.get_visible(): - if not self.client.scanning and hasattr(self.client, "scan"): - GLib.timeout_add(100, self.client.scan) - if hasattr(self, "_refresh_timeout_id") and self._refresh_timeout_id: - GLib.source_remove(self._refresh_timeout_id) + def _cancel_pending_refresh(self): + if hasattr(self, "_anim_finished_handler") and self._anim_finished_handler: + try: + self.other_devices_scrolled.height_animator.disconnect( + self._anim_finished_handler + ) + except Exception: + pass + self._anim_finished_handler = None - def _do_refresh(): - self.force_device_refresh() - self._refresh_timeout_id = None - return False - - self._refresh_timeout_id = GLib.timeout_add(150, _do_refresh) - else: - if self.client.scanning and hasattr(self.client, "stop_scan"): - self.client.stop_scan() + def _refresh_after_animation(self): + if self._destroyed: + return False + anim = self.other_devices_scrolled.height_animator + if anim.playing: + self._anim_finished_handler = anim.connect( + "finished", + lambda *_: self.force_device_refresh() if not self._destroyed else None, + ) + else: + self.force_device_refresh() + return False def open_bluetooth_settings(self, *_): """Open Blueman bluetooth manager""" @@ -650,6 +676,7 @@ class BluetoothConnections(Box): """Cleanup when widget is destroyed""" self._destroyed = True self.stop_device_monitoring() + self._cancel_pending_refresh() for sig_id in self._client_signal_ids: try: self.client.disconnect(sig_id) @@ -658,14 +685,12 @@ class BluetoothConnections(Box): f"[Bluetooth] Failed to disconnect client signal {sig_id}: {e}" ) self._client_signal_ids.clear() - try: - self.other_devices_revealer.child_revealed = False - except Exception as e: - logger.error(f"[Bluetooth] Failed to hide other devices revealer: {e}") def close_bluetooth(self): """Called when Bluetooth panel is being closed""" - self.other_devices_revealer.child_revealed = False + if self.other_devices.get_visible(): + self.other_devices.set_visible(False) + self.other_devices_scrolled.snap_to_size(0) def setup_pull_to_refresh(self): """Setup pull-to-refresh gesture for the scrolled window""" diff --git a/src/window/controlcenter/wifi.py b/src/window/controlcenter/wifi.py index c7d497c..8398382 100644 --- a/src/window/controlcenter/wifi.py +++ b/src/window/controlcenter/wifi.py @@ -342,7 +342,6 @@ class WifiConnections(Box): on_clicked=self.toggle_other_networks, ) self.other_networks = Box(spacing=4, orientation="vertical") - self.other_networks.set_size_request(-1, 150) # Create scrolled window for other networks self.other_networks_scrolled = AnimatedScrollable( @@ -388,6 +387,7 @@ class WifiConnections(Box): def on_hide(self, *_): """Called when the widget is hidden (popup closed)""" self.stop_network_monitoring() + self._cancel_pending_refresh() if self.other_networks.get_visible(): self.other_networks.set_visible(False) self.other_networks_scrolled.snap_to_size(0) @@ -395,20 +395,41 @@ class WifiConnections(Box): def toggle_other_networks(self, *_): """Toggle the visibility of other networks section""" current_state = self.other_networks.get_visible() - self.other_networks.set_visible(not current_state) - - if self.other_networks.get_visible(): + self._cancel_pending_refresh() + if current_state: + self.other_networks.set_visible(False) + self.other_networks_scrolled.snap_to_size(0) + else: + self.other_networks.set_visible(True) if self.wifi_service: self.wifi_service.scan() - if hasattr(self, "_refresh_timeout_id") and self._refresh_timeout_id: - GLib.source_remove(self._refresh_timeout_id) + # Defer refresh until after the height animation completes + GLib.idle_add(self._refresh_after_animation) - def _do_refresh(): - self.force_network_refresh() - self._refresh_timeout_id = None - return False + def _cancel_pending_refresh(self): + if hasattr(self, "_anim_finished_handler") and self._anim_finished_handler: + try: + self.other_networks_scrolled.height_animator.disconnect( + self._anim_finished_handler + ) + except Exception: + pass + self._anim_finished_handler = None - self._refresh_timeout_id = GLib.timeout_add(150, _do_refresh) + def _refresh_after_animation(self): + if self._destroyed: + return False + anim = self.other_networks_scrolled.height_animator + if anim.playing: + self._anim_finished_handler = anim.connect( + "finished", + lambda *_: ( + self.force_network_refresh() if not self._destroyed else None + ), + ) + else: + self.force_network_refresh() + return False def on_network_ready(self, *_): """Called when network service is ready""" @@ -720,6 +741,7 @@ class WifiConnections(Box): """Cleanup when widget is destroyed""" self._destroyed = True self.stop_network_monitoring() + self._cancel_pending_refresh() for obj, sig_id in self._signal_ids: try: obj.disconnect(sig_id) @@ -728,11 +750,9 @@ class WifiConnections(Box): self._signal_ids.clear() self.wifi_service = None self.network_service = None - try: - self.other_networks_revealer.child_revealed = False - except Exception as e: - logger.error(f"[WiFi] Failed to hide other networks revealer: {e}") def close_wifi(self): """Called when WiFi panel is being closed""" - self.other_networks_revealer.child_revealed = False + if self.other_networks.get_visible(): + self.other_networks.set_visible(False) + self.other_networks_scrolled.snap_to_size(0) diff --git a/src/window/launcher/main.py b/src/window/launcher/main.py index 8c65aef..74d2298 100644 --- a/src/window/launcher/main.py +++ b/src/window/launcher/main.py @@ -172,6 +172,9 @@ class Launcher(Window): self.show_all() + if not self.results: + self.results_scroll.hide() + if trigger_keyword: # Set flag to track that launcher was opened with a trigger keyword self.opened_with_trigger = True @@ -764,7 +767,10 @@ class Launcher(Window): self.results_box.show_all() - self.results_scroll.show() + if self.results: + self.results_scroll.show() + else: + self.results_scroll.hide() def _update_input_action_text(self): """Update the input field with action text (Spotlight-style).""" @@ -810,7 +816,7 @@ class Launcher(Window): child.destroy() else: self.results_box.remove(child) - # Keep the results scroll visible even when empty + self.results_scroll.hide() def _handle_escape_key(self) -> bool: """Handle escape key press."""