✨ feat(dashboard): add back button and hover effects to bluetooth and network tiles, improve icon updates
This commit is contained in:
2 files changed
+179
-40
No files matched your search
@@ -5,12 +5,18 @@ from fabric.widgets.centerbox import CenterBox
|
||||
from fabric.widgets.image import Image
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.scrolledwindow import ScrolledWindow
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
|
||||
from modules.dashboard.tile import Tile
|
||||
import utils.icons as icons
|
||||
|
||||
|
||||
def add_hover_cursor(widget):
|
||||
widget.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK)
|
||||
widget.connect("enter-notify-event", lambda w, e: w.get_window().set_cursor(Gdk.Cursor.new_from_name(w.get_display(), "pointer")) if w.get_window() else None)
|
||||
widget.connect("leave-notify-event", lambda w, e: w.get_window().set_cursor(None) if w.get_window() else None)
|
||||
|
||||
|
||||
class BluetoothDeviceSlot(CenterBox):
|
||||
"""A widget representing a single Bluetooth device in the dashboard."""
|
||||
|
||||
@@ -99,6 +105,8 @@ class Bluetooth(Tile):
|
||||
self.bluetooth_client = BluetoothClient(on_device_added=self.on_device_added)
|
||||
self.bluetooth_client.connect("notify::enabled", self._on_bluetooth_state_changed)
|
||||
self.bluetooth_client.connect("notify::scanning", self._update_scan_label)
|
||||
self.bluetooth_client.connect("device-added", self._on_device_connection_changed)
|
||||
self.bluetooth_client.connect("device-removed", self._on_device_connection_changed)
|
||||
|
||||
# Initialize state
|
||||
self._update_tile_state()
|
||||
@@ -115,20 +123,44 @@ class Bluetooth(Tile):
|
||||
self.add_style_class("off")
|
||||
self.label.set_label("Off")
|
||||
|
||||
# Update tile icon based on Bluetooth state
|
||||
self._update_tile_icon()
|
||||
|
||||
def _update_tile_icon(self):
|
||||
"""Update the tile icon based on Bluetooth status and connected devices."""
|
||||
if not self.bluetooth_client.enabled:
|
||||
# Bluetooth is disabled
|
||||
self.icon.set_markup(icons.bluetooth_off)
|
||||
return
|
||||
|
||||
# Bluetooth is enabled - check for connected devices
|
||||
connected_devices = self.bluetooth_client.connected_devices
|
||||
if connected_devices:
|
||||
# Has connected devices
|
||||
self.icon.set_markup(icons.bluetooth_connected)
|
||||
else:
|
||||
# Enabled but no connected devices
|
||||
if self.bluetooth_client.scanning:
|
||||
# Currently scanning - could add animation here if desired
|
||||
self.icon.set_markup(icons.bluetooth)
|
||||
else:
|
||||
# Idle state
|
||||
self.icon.set_markup(icons.bluetooth)
|
||||
|
||||
def create_content(self):
|
||||
"""Create the detailed bluetooth content for the dashboard."""
|
||||
# Create Bluetooth toggle switch (like notifications DND switch)
|
||||
self.bluetooth_switch = Gtk.Switch(name="bluetooth-switch")
|
||||
self.bluetooth_switch.set_vexpand(False)
|
||||
self.bluetooth_switch.set_valign(Gtk.Align.CENTER)
|
||||
self.bluetooth_switch.set_active(self.bluetooth_client.enabled)
|
||||
self.bluetooth_switch.connect("notify::active", self._on_bluetooth_toggle)
|
||||
|
||||
# Create Bluetooth status icon
|
||||
self.bluetooth_status_icon = Label(
|
||||
name="bluetooth-status-icon",
|
||||
markup=icons.bluetooth if self.bluetooth_client.enabled else icons.bluetooth_off
|
||||
# Create back button with chevron left icon
|
||||
self.back_button_icon = Label(
|
||||
name="bluetooth-back-label",
|
||||
markup=icons.chevron_left
|
||||
)
|
||||
self.back_button = Button(
|
||||
name="bluetooth-back",
|
||||
child=self.back_button_icon,
|
||||
tooltip_text="Back to notifications",
|
||||
on_clicked=self._on_back_clicked
|
||||
)
|
||||
add_hover_cursor(self.back_button)
|
||||
|
||||
# Create scan button with icon
|
||||
self.scan_label = Label(name="bluetooth-scan-label", markup=icons.radar)
|
||||
@@ -138,6 +170,7 @@ class Bluetooth(Tile):
|
||||
tooltip_text="Scan for Bluetooth devices",
|
||||
on_clicked=lambda *_: self.bluetooth_client.toggle_scan(),
|
||||
)
|
||||
add_hover_cursor(self.scan_button)
|
||||
|
||||
# Create device containers
|
||||
self.paired_box = Box(spacing=2, orientation="v")
|
||||
@@ -149,6 +182,14 @@ class Bluetooth(Tile):
|
||||
content_box.add(Label(name="bluetooth-section", label="Available"))
|
||||
content_box.add(self.available_box)
|
||||
|
||||
# Create header
|
||||
header_box = CenterBox(
|
||||
name="bluetooth-header",
|
||||
start_children=[self.back_button],
|
||||
center_children=[Label(name="bluetooth-title", label="Bluetooth Devices")],
|
||||
end_children=[self.scan_button]
|
||||
)
|
||||
|
||||
# Main container
|
||||
main_container = Box(
|
||||
name="bluetooth-content",
|
||||
@@ -159,12 +200,7 @@ class Bluetooth(Tile):
|
||||
h_align="fill",
|
||||
style_classes=["tile-content"],
|
||||
children=[
|
||||
CenterBox(
|
||||
name="bluetooth-header",
|
||||
start_children=[self.bluetooth_switch, self.bluetooth_status_icon],
|
||||
center_children=Label(name="bluetooth-text", label="Bluetooth Devices"),
|
||||
end_children=self.scan_button,
|
||||
),
|
||||
header_box,
|
||||
ScrolledWindow(
|
||||
name="bluetooth-devices",
|
||||
min_content_size=(-1, -1),
|
||||
@@ -186,6 +222,10 @@ class Bluetooth(Tile):
|
||||
"""Handle new device discovery."""
|
||||
if not (device := client.get_device(address)):
|
||||
return
|
||||
|
||||
# Connect to device changes to update tile icon when connection state changes
|
||||
device.connect("changed", self._on_device_connection_changed)
|
||||
|
||||
slot = BluetoothDeviceSlot(device)
|
||||
|
||||
if device.paired:
|
||||
@@ -203,25 +243,20 @@ class Bluetooth(Tile):
|
||||
self.scan_button.remove_style_class("scanning")
|
||||
self.scan_button.set_tooltip_text("Scan for Bluetooth devices")
|
||||
|
||||
def _on_bluetooth_toggle(self, switch, *_):
|
||||
"""Handle Bluetooth toggle switch."""
|
||||
enabled = switch.get_active()
|
||||
self.bluetooth_client.enabled = enabled
|
||||
self._update_content_state()
|
||||
# Update tile icon when scanning state changes
|
||||
self._update_tile_icon()
|
||||
|
||||
def _on_back_clicked(self, *_):
|
||||
"""Handle back button click - return to default (notifications) view."""
|
||||
if self.dashboard_instance:
|
||||
self.dashboard_instance.reset_to_default()
|
||||
|
||||
def _on_bluetooth_state_changed(self, *_):
|
||||
"""Handle bluetooth state change."""
|
||||
self._update_tile_state()
|
||||
self._update_content_state()
|
||||
|
||||
def _update_content_state(self):
|
||||
"""Update content area based on Bluetooth state."""
|
||||
if hasattr(self, 'bluetooth_switch'):
|
||||
self.bluetooth_switch.set_active(self.bluetooth_client.enabled)
|
||||
|
||||
if hasattr(self, 'bluetooth_status_icon'):
|
||||
self.bluetooth_status_icon.set_markup(
|
||||
icons.bluetooth if self.bluetooth_client.enabled else icons.bluetooth_off
|
||||
)
|
||||
def _on_device_connection_changed(self, *_):
|
||||
"""Handle device connection/disconnection changes."""
|
||||
self._update_tile_icon()
|
||||
|
||||
|
||||
@@ -5,13 +5,19 @@ from fabric.widgets.centerbox import CenterBox
|
||||
from fabric.widgets.image import Image
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.scrolledwindow import ScrolledWindow
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gtk, Gdk, GLib
|
||||
|
||||
from modules.dashboard.tile import Tile
|
||||
from services.network import NetworkClient
|
||||
import utils.icons as icons
|
||||
|
||||
|
||||
def add_hover_cursor(widget):
|
||||
widget.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK)
|
||||
widget.connect("enter-notify-event", lambda w, e: w.get_window().set_cursor(Gdk.Cursor.new_from_name(w.get_display(), "pointer")) if w.get_window() else None)
|
||||
widget.connect("leave-notify-event", lambda w, e: w.get_window().set_cursor(None) if w.get_window() else None)
|
||||
|
||||
|
||||
class WifiAccessPointSlot(CenterBox):
|
||||
"""A widget representing a single WiFi access point in the dashboard."""
|
||||
|
||||
@@ -37,10 +43,11 @@ class WifiAccessPointSlot(CenterBox):
|
||||
self.connect_button = Button(
|
||||
name="wifi-connect-button",
|
||||
label="Connected" if self.is_active else "Connect",
|
||||
sensitive=not self.is_active,
|
||||
sensitive=True, # Always clickable - can connect or disconnect
|
||||
on_clicked=self._on_connect_clicked,
|
||||
style_classes=["connected"] if self.is_active else None,
|
||||
)
|
||||
add_hover_cursor(self.connect_button)
|
||||
|
||||
self.set_start_children([
|
||||
Box(spacing=8, h_expand=True, h_align="fill", children=[
|
||||
@@ -52,10 +59,16 @@ class WifiAccessPointSlot(CenterBox):
|
||||
|
||||
def _on_connect_clicked(self, *_):
|
||||
"""Handle connect button click."""
|
||||
if not self.is_active and self.access_point.bssid:
|
||||
if self.is_active:
|
||||
# Disconnect from current network
|
||||
self.connect_button.set_label("Disconnecting...")
|
||||
self.connect_button.set_sensitive(False)
|
||||
self.wifi_service.disconnect_wifi()
|
||||
elif self.access_point.bssid:
|
||||
# Connect to this network
|
||||
self.connect_button.set_label("Connecting...")
|
||||
self.connect_button.set_sensitive(False)
|
||||
self.network_service.connect_wifi_bssid(self.access_point.bssid)
|
||||
self.wifi_service.connect_to_wifi(self.access_point)
|
||||
|
||||
|
||||
class Network(Tile):
|
||||
@@ -70,6 +83,11 @@ class Network(Tile):
|
||||
)
|
||||
self.state = False
|
||||
|
||||
# Animation properties
|
||||
self._animation_timeout_id = None
|
||||
self._animation_step = 0
|
||||
self._animation_direction = 1
|
||||
|
||||
super().__init__(
|
||||
label="Wi-Fi",
|
||||
props=self.label,
|
||||
@@ -98,15 +116,46 @@ class Network(Tile):
|
||||
"""Handle network connection state changes."""
|
||||
self._update_tile_state()
|
||||
|
||||
def _animate_searching(self):
|
||||
"""Animate wifi icon when searching for networks"""
|
||||
wifi_icons = [icons.wifi_0, icons.wifi_1, icons.wifi_2, icons.wifi_3, icons.wifi_2, icons.wifi_1]
|
||||
|
||||
wifi = self.network_client.wifi_device
|
||||
if not self.icon or not wifi or not wifi.wireless_enabled:
|
||||
self._stop_animation()
|
||||
return False
|
||||
|
||||
if wifi.active_access_point:
|
||||
self._stop_animation()
|
||||
return False
|
||||
|
||||
GLib.idle_add(self.icon.set_markup, wifi_icons[self._animation_step])
|
||||
|
||||
self._animation_step = (self._animation_step + 1) % len(wifi_icons)
|
||||
|
||||
return True
|
||||
|
||||
def _start_animation(self):
|
||||
if self._animation_timeout_id is None:
|
||||
self._animation_step = 0
|
||||
self._animation_direction = 1
|
||||
self._animation_timeout_id = GLib.timeout_add(500, self._animate_searching)
|
||||
|
||||
def _stop_animation(self):
|
||||
if self._animation_timeout_id is not None:
|
||||
GLib.source_remove(self._animation_timeout_id)
|
||||
self._animation_timeout_id = None
|
||||
|
||||
def _update_tile_state(self):
|
||||
"""Update tile visual state based on WiFi status."""
|
||||
connected = False
|
||||
con_label = "Disconnected"
|
||||
wifi = self.network_client.wifi_device
|
||||
|
||||
if self.network_client.wifi_device and self.network_client.wifi_device.active_access_point:
|
||||
if wifi and wifi.active_access_point:
|
||||
connected = True
|
||||
con_label = self.network_client.wifi_device.active_access_point.ssid
|
||||
elif self.network_client.wifi_device and not self.network_client.wifi_device.wireless_enabled:
|
||||
con_label = wifi.active_access_point.ssid
|
||||
elif wifi and not wifi.wireless_enabled:
|
||||
con_label = "Off"
|
||||
|
||||
if self.state != connected:
|
||||
@@ -121,6 +170,55 @@ class Network(Tile):
|
||||
|
||||
self.label.set_label(con_label)
|
||||
|
||||
# Update tile icon based on WiFi state
|
||||
self._update_tile_icon()
|
||||
|
||||
def _update_tile_icon(self):
|
||||
"""Update the tile icon based on network status"""
|
||||
wifi = self.network_client.wifi_device
|
||||
ethernet = self.network_client.ethernet_device
|
||||
|
||||
if wifi and not wifi.wireless_enabled:
|
||||
self._stop_animation()
|
||||
self.icon.set_markup(icons.wifi_off)
|
||||
return
|
||||
|
||||
if wifi and wifi.wireless_enabled:
|
||||
if wifi.active_access_point:
|
||||
self._stop_animation()
|
||||
# Show signal strength icon
|
||||
if wifi.active_access_point.strength > 0:
|
||||
strength = wifi.active_access_point.strength
|
||||
if strength < 25:
|
||||
self.icon.set_markup(icons.wifi_0)
|
||||
elif strength < 50:
|
||||
self.icon.set_markup(icons.wifi_1)
|
||||
elif strength < 75:
|
||||
self.icon.set_markup(icons.wifi_2)
|
||||
else:
|
||||
self.icon.set_markup(icons.wifi_3)
|
||||
else:
|
||||
self.icon.set_markup(icons.wifi_1)
|
||||
else:
|
||||
# WiFi is enabled but not connected - start animation
|
||||
self._start_animation()
|
||||
|
||||
try:
|
||||
primary_device = self.network_client.primary_device
|
||||
except AttributeError:
|
||||
primary_device = "wireless"
|
||||
|
||||
if primary_device == "wired":
|
||||
self._stop_animation()
|
||||
if ethernet and ethernet.internet == "activated":
|
||||
self.icon.set_markup(icons.world)
|
||||
else:
|
||||
self.icon.set_markup(icons.world_off)
|
||||
else:
|
||||
if not wifi:
|
||||
self._stop_animation()
|
||||
self.icon.set_markup(icons.wifi_off)
|
||||
|
||||
|
||||
def create_content(self):
|
||||
"""Create the detailed network content for the dashboard."""
|
||||
@@ -143,6 +241,7 @@ class Network(Tile):
|
||||
tooltip_text="Scan for Wi-Fi networks",
|
||||
on_clicked=self._refresh_access_points
|
||||
)
|
||||
add_hover_cursor(self.refresh_button)
|
||||
|
||||
# Create back button with chevron left icon
|
||||
self.back_button_icon = Label(
|
||||
@@ -155,6 +254,7 @@ class Network(Tile):
|
||||
tooltip_text="Back to notifications",
|
||||
on_clicked=self._on_back_clicked
|
||||
)
|
||||
add_hover_cursor(self.back_button)
|
||||
|
||||
# Create header
|
||||
header_box = CenterBox(
|
||||
@@ -212,10 +312,14 @@ class Network(Tile):
|
||||
if self.network_client.wifi_device:
|
||||
self.network_client.wifi_device.connect("changed", self._load_access_points)
|
||||
self.network_client.wifi_device.connect("notify::wireless-enabled", self._update_wifi_status_ui)
|
||||
self.network_client.wifi_device.connect("notify::active-access-point", self._update_tile_state)
|
||||
|
||||
# Only update UI if content has been created
|
||||
if self._content_created and hasattr(self, 'refresh_button'):
|
||||
self._update_wifi_status_ui()
|
||||
|
||||
# Update tile icon immediately
|
||||
self._update_tile_icon()
|
||||
else:
|
||||
# Only show error if content has been created
|
||||
if self._content_created and hasattr(self, 'status_label'):
|
||||
|
||||
Reference in new issue
Block a user