✨ feat(dashboard): implement Bluetooth and WiFi tiles with detailed content views
This commit is contained in:
7 files changed
+810
-488
No files matched your search
@@ -69,7 +69,6 @@ tooltip > * {
|
||||
box-shadow: 0 0 3px alpha(black, 0.7);
|
||||
}
|
||||
|
||||
|
||||
#dashboard-window {
|
||||
transition: margin 0.25s cubic-bezier(0.5, 0.25, 0, 1);
|
||||
}
|
||||
@@ -150,4 +149,179 @@ tooltip > * {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
#matugen-switcher,
|
||||
#bluetooth-switch,
|
||||
#wifi-switch,
|
||||
#dnd-switch {
|
||||
min-width: 40px;
|
||||
min-height: 20px;
|
||||
background-color: var(--surface);
|
||||
border-radius: 15px;
|
||||
padding: 2px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
/* Style the switch's slider */
|
||||
#matugen-switcher slider,
|
||||
#bluetooth-switch slider,
|
||||
#wifi-switch slider,
|
||||
#dnd-switch slider {
|
||||
background-color: var(--primary);
|
||||
border-radius: 16px;
|
||||
min-width: 16px;
|
||||
min-height: 8px;
|
||||
transition:
|
||||
background-color 0.1s cubic-bezier(0.5, 0.25, 0, 1.25),
|
||||
transform 0.25s cubic-bezier(0.5, 0.25, 0, 1.25);
|
||||
}
|
||||
|
||||
/* When the switch is active (checked) */
|
||||
#matugen-switcher:checked,
|
||||
#bluetooth-switch:checked,
|
||||
#wifi-switch:checked,
|
||||
#dnd-switch:checked {
|
||||
background-color: var(--primary);
|
||||
}
|
||||
|
||||
/* Optional: additional styling for the slider when active */
|
||||
#matugen-switcher:checked slider,
|
||||
#bluetooth-switch:checked slider,
|
||||
#wifi-switch:checked slider,
|
||||
#dnd-switch:checked slider {
|
||||
background-color: var(--shadow);
|
||||
}
|
||||
|
||||
#matugen-switcher:checked image,
|
||||
#bluetooth-switch:checked image,
|
||||
#wifi-switch:checked image,
|
||||
#dnd-switch:checked image {
|
||||
color: var(--shadow);
|
||||
}
|
||||
|
||||
|
||||
#bluetooth-header,
|
||||
#network-header {
|
||||
border: 2px solid var(--surface);
|
||||
padding: 4px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
#bluetooth-device,
|
||||
#wifi-ap-slot {
|
||||
border: 2px solid var(--surface);
|
||||
border-radius: 12px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
#bluetooth-scan,
|
||||
#bluetooth-back,
|
||||
#network-refresh,
|
||||
#network-back {
|
||||
background-color: var(--surface);
|
||||
border-radius: 8px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
#bluetooth-back-label,
|
||||
#network-back-label {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#bluetooth-scan:hover,
|
||||
#bluetooth-back:hover,
|
||||
#network-refresh:hover,
|
||||
#network-back:hover {
|
||||
background-color: var(--surface-bright);
|
||||
}
|
||||
|
||||
#bluetooth-scan label,
|
||||
#bluetooth-toggle label,
|
||||
#wifi-connect-button label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#bluetooth-text,
|
||||
#bluetooth-section {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#bluetooth-section {
|
||||
background-color: var(--surface);
|
||||
border-radius: 12px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#bluetooth-connection {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#bluetooth-connect,
|
||||
#wifi-connect-button {
|
||||
font-weight: bold;
|
||||
background-color: var(--surface);
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#wifi-connect-button.connected {
|
||||
background-color: var(--green);
|
||||
}
|
||||
|
||||
#wifi-connect-button.connected label {
|
||||
color: var(--shadow);
|
||||
}
|
||||
|
||||
#bluetooth-connect.connected {
|
||||
background-color: var(--blue);
|
||||
}
|
||||
|
||||
#bluetooth-connect.connected label {
|
||||
color: var(--shadow);
|
||||
}
|
||||
|
||||
#bluetooth-connect:hover {
|
||||
background-color: var(--surface-bright);
|
||||
}
|
||||
|
||||
#bluetooth-paired,
|
||||
#bluetooth-available {
|
||||
background-color: var(--surface);
|
||||
border-radius: 20px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
#bt-sep {
|
||||
/*padding: 1px;*/
|
||||
border-radius: 16px;
|
||||
/*background-color: var(--surface);*/
|
||||
/*margin: 0 16px;*/
|
||||
}
|
||||
|
||||
#bluetooth-scan-label,
|
||||
#network-refresh-label {
|
||||
font-size: 20px;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
#bluetooth-scan.scanning {
|
||||
animation: blink 0.5s ease infinite;
|
||||
}
|
||||
|
||||
#bluetooth-scan-label.scanning {
|
||||
animation: blink 0.5s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0% {
|
||||
background-color: var(--blue);
|
||||
color: var(--shadow);
|
||||
}
|
||||
50% {
|
||||
background-color: var(--shadow);
|
||||
color: var(--blue);
|
||||
}
|
||||
100% {
|
||||
background-color: var(--blue);
|
||||
color: var(--shadow);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,85 @@
|
||||
from fabric.bluetooth import BluetoothClient, BluetoothDevice
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.button import Button
|
||||
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 modules.dashboard.tile import Tile
|
||||
|
||||
import utils.icons as icons
|
||||
|
||||
|
||||
class BluetoothDeviceSlot(CenterBox):
|
||||
"""A widget representing a single Bluetooth device in the dashboard."""
|
||||
|
||||
def __init__(self, device: BluetoothDevice, **kwargs):
|
||||
super().__init__(name="bluetooth-device", **kwargs)
|
||||
self.device = device
|
||||
self.device.connect("changed", self.on_changed)
|
||||
self.device.connect(
|
||||
"notify::closed", lambda *_: self.device.closed and self.destroy()
|
||||
)
|
||||
|
||||
self.connection_label = Label(
|
||||
name="bluetooth-connection",
|
||||
markup=icons.bluetooth_disconnected
|
||||
)
|
||||
self.connect_button = Button(
|
||||
name="bluetooth-connect",
|
||||
label="Connect",
|
||||
on_clicked=lambda *_: self.device.set_connecting(not self.device.connected),
|
||||
style_classes=["connected"] if self.device.connected else None,
|
||||
)
|
||||
|
||||
self.start_children = [
|
||||
Box(
|
||||
spacing=8,
|
||||
h_expand=True,
|
||||
h_align="fill",
|
||||
children=[
|
||||
Image(icon_name=device.icon_name + "-symbolic", size=16),
|
||||
Label(
|
||||
label=device.name,
|
||||
h_expand=True,
|
||||
h_align="start",
|
||||
ellipsization="end",
|
||||
),
|
||||
self.connection_label,
|
||||
],
|
||||
)
|
||||
]
|
||||
self.end_children = self.connect_button
|
||||
|
||||
self.device.emit("changed")
|
||||
|
||||
def on_changed(self, *_):
|
||||
"""Handle device state changes."""
|
||||
self.connection_label.set_markup(
|
||||
icons.bluetooth_connected
|
||||
if self.device.connected
|
||||
else icons.bluetooth_disconnected
|
||||
)
|
||||
if self.device.connecting:
|
||||
self.connect_button.set_label(
|
||||
"Connecting..." if not self.device.connecting else "..."
|
||||
)
|
||||
else:
|
||||
self.connect_button.set_label(
|
||||
"Connect" if not self.device.connected else "Disconnect"
|
||||
)
|
||||
if self.device.connected:
|
||||
self.connect_button.add_style_class("connected")
|
||||
else:
|
||||
self.connect_button.remove_style_class("connected")
|
||||
|
||||
|
||||
class Bluetooth(Tile):
|
||||
"""Bluetooth tile for the dashboard that shows Bluetooth status and devices."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
# Create status label
|
||||
self.label = Label(
|
||||
style_classes=["desc-label", "off"],
|
||||
label="Off",
|
||||
@@ -22,7 +95,133 @@ class Bluetooth(Tile):
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# Initialize bluetooth client with device handling
|
||||
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)
|
||||
|
||||
# Initialize state
|
||||
self._update_tile_state()
|
||||
|
||||
def _update_tile_state(self):
|
||||
"""Update tile visual state based on Bluetooth status."""
|
||||
self.state = self.bluetooth_client.enabled
|
||||
if self.state:
|
||||
self.remove_style_class("off")
|
||||
self.add_style_class("on")
|
||||
self.label.set_label("On")
|
||||
else:
|
||||
self.remove_style_class("on")
|
||||
self.add_style_class("off")
|
||||
self.label.set_label("Off")
|
||||
|
||||
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 scan button with icon
|
||||
self.scan_label = Label(name="bluetooth-scan-label", markup=icons.radar)
|
||||
self.scan_button = Button(
|
||||
name="bluetooth-scan",
|
||||
child=self.scan_label,
|
||||
tooltip_text="Scan for Bluetooth devices",
|
||||
on_clicked=lambda *_: self.bluetooth_client.toggle_scan(),
|
||||
)
|
||||
|
||||
# Create device containers
|
||||
self.paired_box = Box(spacing=2, orientation="v")
|
||||
self.available_box = Box(spacing=2, orientation="v")
|
||||
|
||||
# Create content structure
|
||||
content_box = Box(spacing=4, orientation="v")
|
||||
content_box.add(self.paired_box)
|
||||
content_box.add(Label(name="bluetooth-section", label="Available"))
|
||||
content_box.add(self.available_box)
|
||||
|
||||
# Main container
|
||||
main_container = Box(
|
||||
name="bluetooth-content",
|
||||
orientation="v",
|
||||
spacing=8,
|
||||
h_expand=True,
|
||||
v_expand=True,
|
||||
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,
|
||||
),
|
||||
ScrolledWindow(
|
||||
name="bluetooth-devices",
|
||||
min_content_size=(-1, -1),
|
||||
child=content_box,
|
||||
v_expand=True,
|
||||
propagate_width=False,
|
||||
propagate_height=False,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
# Initialize notifications
|
||||
self.bluetooth_client.notify("scanning")
|
||||
self.bluetooth_client.notify("enabled")
|
||||
|
||||
return main_container
|
||||
|
||||
def on_device_added(self, client: BluetoothClient, address: str):
|
||||
"""Handle new device discovery."""
|
||||
if not (device := client.get_device(address)):
|
||||
return
|
||||
slot = BluetoothDeviceSlot(device)
|
||||
|
||||
if device.paired:
|
||||
return self.paired_box.add(slot)
|
||||
return self.available_box.add(slot)
|
||||
|
||||
def _update_scan_label(self, *_):
|
||||
"""Update scan button appearance based on scanning state."""
|
||||
if self.bluetooth_client.scanning:
|
||||
self.scan_label.add_style_class("scanning")
|
||||
self.scan_button.add_style_class("scanning")
|
||||
self.scan_button.set_tooltip_text("Stop scanning for Bluetooth devices")
|
||||
else:
|
||||
self.scan_label.remove_style_class("scanning")
|
||||
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()
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
+80
-33
@@ -1,5 +1,5 @@
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.revealer import Revealer
|
||||
from fabric.widgets.stack import Stack
|
||||
from fabric.core.service import Property
|
||||
|
||||
from modules.dashboard.bluetooth import Bluetooth
|
||||
@@ -21,8 +21,8 @@ class Dashboard(Box):
|
||||
all_visible=True,
|
||||
**kwargs,
|
||||
)
|
||||
self.wifi = Network()
|
||||
self.bluetooth = Bluetooth()
|
||||
self.wifi = Network(name="wifi-tile")
|
||||
self.bluetooth = Bluetooth(name="bluetooth-tile")
|
||||
self.mini_player = PlayerContainerMini()
|
||||
self.mini_player_tile = TileSpecial(
|
||||
props=self.mini_player, mini_props=self.mini_player.get_mini_view()
|
||||
@@ -35,6 +35,9 @@ class Dashboard(Box):
|
||||
# Create real notifications component
|
||||
self.dashboard_notifications = DashboardNotifications()
|
||||
|
||||
# Track currently active tile
|
||||
self.active_tile = None
|
||||
|
||||
self.tiles = Box(
|
||||
children=[
|
||||
self.wifi,
|
||||
@@ -43,61 +46,103 @@ class Dashboard(Box):
|
||||
],
|
||||
)
|
||||
|
||||
self.notification_container = Revealer(
|
||||
transition_duration=250,
|
||||
transition_type="slide-down",
|
||||
# Create content containers for each tile
|
||||
self.wifi_content = self.wifi.create_content()
|
||||
self.bluetooth_content = self.bluetooth.create_content()
|
||||
|
||||
# Default content (notifications)
|
||||
self.default_content = Box(
|
||||
name="notification-history-popup-content",
|
||||
orientation="v",
|
||||
spacing=8,
|
||||
h_expand=True,
|
||||
child=Box(
|
||||
name="notification-history-popup-content",
|
||||
orientation="v",
|
||||
spacing=8,
|
||||
h_expand=True,
|
||||
v_expand=True,
|
||||
children=[self.dashboard_notifications],
|
||||
style_classes=["notification-history-popup"],
|
||||
),
|
||||
child_revealed=True,
|
||||
v_expand=True,
|
||||
children=[self.dashboard_notifications],
|
||||
style_classes=["notification-history-popup"],
|
||||
)
|
||||
|
||||
# Content stack to switch between different tile contents
|
||||
self.content_stack = Stack(
|
||||
transition_type="crossfade",
|
||||
transition_duration=200,
|
||||
h_expand=True,
|
||||
v_expand=True,
|
||||
h_align="fill",
|
||||
children=[
|
||||
self.default_content,
|
||||
self.wifi_content,
|
||||
self.bluetooth_content,
|
||||
],
|
||||
)
|
||||
|
||||
# Set default visible child
|
||||
self.content_stack.set_visible_child(self.default_content)
|
||||
|
||||
self.children = [
|
||||
Box(name="inner", children=self.tiles),
|
||||
Box(
|
||||
children=[
|
||||
self.notification_container,
|
||||
self.content_stack,
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
def handle_tile_menu_expand(self, tile: str, toggle: bool):
|
||||
print(f"Tile menu expand: {tile}, toggle: {toggle}, active_tile: {self.active_tile}")
|
||||
|
||||
# If clicking the same tile that's already active, deactivate it
|
||||
if self.active_tile == tile and toggle:
|
||||
toggle = False
|
||||
# Update the tile's toggle state
|
||||
for i in self.tiles:
|
||||
if i.get_name() == tile:
|
||||
i.toggle = False
|
||||
break
|
||||
|
||||
if toggle:
|
||||
self.notification_container.set_reveal_child(False)
|
||||
# Show the specific tile's content in the content area
|
||||
self.active_tile = tile
|
||||
if tile == "wifi-tile":
|
||||
self.content_stack.set_visible_child(self.wifi_content)
|
||||
print("Showing WiFi content")
|
||||
elif tile == "bluetooth-tile":
|
||||
self.content_stack.set_visible_child(self.bluetooth_content)
|
||||
print("Showing Bluetooth content")
|
||||
else:
|
||||
self.notification_container.set_reveal_child(True)
|
||||
for i in self.tiles:
|
||||
if i.get_name() == tile:
|
||||
print("found")
|
||||
else:
|
||||
if toggle:
|
||||
i.mini_view()
|
||||
i.icon.add_style_class("mini")
|
||||
i.icon.remove_style_class("maxi")
|
||||
else:
|
||||
i.maxi_view()
|
||||
i.icon.add_style_class("maxi")
|
||||
i.icon.remove_style_class("mini")
|
||||
print("search complete")
|
||||
# Show default content (notifications)
|
||||
self.active_tile = None
|
||||
self.content_stack.set_visible_child(self.default_content)
|
||||
print("Showing default content")
|
||||
|
||||
# Don't change tile visual states - keep all tiles unchanged
|
||||
print("Tile handling complete")
|
||||
|
||||
def reset_to_default(self):
|
||||
"""Reset dashboard to default state (notifications visible, no active tile)"""
|
||||
print("Resetting dashboard to default state")
|
||||
self.active_tile = None
|
||||
self.content_stack.set_visible_child(self.default_content)
|
||||
|
||||
# Reset all tile toggle states
|
||||
for tile in self.tiles:
|
||||
if hasattr(tile, 'toggle'):
|
||||
tile.toggle = False
|
||||
|
||||
|
||||
class DashboardWindow(Window):
|
||||
visible = Property(bool, flags="read-write", default_value=False)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
# Create dashboard instance and store reference
|
||||
self.dashboard = Dashboard()
|
||||
|
||||
super().__init__(
|
||||
name="dashboard-window",
|
||||
layer="top",
|
||||
anchor="bottom",
|
||||
exclusivity="none",
|
||||
keyboard_mode="on-demand",
|
||||
child=Dashboard(),
|
||||
child=self.dashboard,
|
||||
visible=False,
|
||||
all_visible=False,
|
||||
**kwargs,
|
||||
@@ -112,6 +157,8 @@ class DashboardWindow(Window):
|
||||
self.visible = True
|
||||
|
||||
def close_dashboard(self):
|
||||
# Reset dashboard to default state when closing
|
||||
self.dashboard.reset_to_default()
|
||||
self.hide()
|
||||
self.visible = False
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.button import Button
|
||||
from fabric.widgets.scrolledwindow import ScrolledWindow
|
||||
from gi.repository import Gtk
|
||||
|
||||
from modules.dashboard.tile import Tile
|
||||
from services.network import NetworkClient
|
||||
@@ -26,7 +30,7 @@ class Network(Tile):
|
||||
self.nm.connect("changed", self.handle_connection_change)
|
||||
self.nm.connect("ready", self.handle_connection_change)
|
||||
|
||||
def handle_connection_change(self, *args):
|
||||
def handle_connection_change(self, *_):
|
||||
# Get current network state from NetworkClient properties
|
||||
connected = False
|
||||
con_label = "Disconnected"
|
||||
@@ -48,3 +52,204 @@ class Network(Tile):
|
||||
print("State change:", connected)
|
||||
|
||||
self.label.set_label(con_label)
|
||||
|
||||
|
||||
def create_content(self):
|
||||
"""Create the detailed network content for the dashboard"""
|
||||
content_box = Box(
|
||||
name="network-content",
|
||||
orientation="v",
|
||||
spacing=8,
|
||||
h_expand=True,
|
||||
v_expand=True,
|
||||
h_align="fill",
|
||||
style_classes=["tile-content"],
|
||||
)
|
||||
|
||||
# Header with WiFi toggle
|
||||
header_box = Box(
|
||||
orientation="h",
|
||||
spacing=12,
|
||||
h_expand=True,
|
||||
h_align="fill",
|
||||
)
|
||||
|
||||
wifi_label = Label(
|
||||
markup="<b>Wi-Fi</b>",
|
||||
h_align="start",
|
||||
h_expand=True,
|
||||
)
|
||||
|
||||
self.wifi_switch = Gtk.Switch(
|
||||
name="wifi-switch",
|
||||
active=self.nm.wireless_enabled if self.nm.wifi_device else False,
|
||||
)
|
||||
self.wifi_switch.connect("notify::active", self._on_wifi_toggle)
|
||||
|
||||
header_box.children = [wifi_label, self.wifi_switch]
|
||||
content_box.add(header_box)
|
||||
|
||||
# Networks list
|
||||
self.networks_container = Box(
|
||||
orientation="v",
|
||||
spacing=4,
|
||||
h_expand=True,
|
||||
)
|
||||
|
||||
# Scrolled window for networks
|
||||
networks_scroll = ScrolledWindow(
|
||||
h_scrollbar_policy="never",
|
||||
v_scrollbar_policy="automatic",
|
||||
h_expand=True,
|
||||
v_expand=True,
|
||||
child=self.networks_container,
|
||||
)
|
||||
networks_scroll.set_size_request(-1, 200)
|
||||
|
||||
content_box.add(networks_scroll)
|
||||
|
||||
# Scan button
|
||||
scan_button = Button(
|
||||
label="Scan for Networks",
|
||||
h_align="center",
|
||||
on_clicked=self._on_scan_clicked,
|
||||
)
|
||||
content_box.add(scan_button)
|
||||
|
||||
# Populate networks initially
|
||||
self._refresh_networks()
|
||||
|
||||
return content_box
|
||||
|
||||
def _on_wifi_toggle(self, *_):
|
||||
"""Handle WiFi toggle"""
|
||||
if self.nm.wifi_device:
|
||||
self.nm.wifi_device.toggle_wifi()
|
||||
self._refresh_networks()
|
||||
|
||||
def _on_scan_clicked(self, *_):
|
||||
"""Handle scan button click"""
|
||||
if self.nm.wifi_device:
|
||||
self.nm.wifi_device.scan()
|
||||
self._refresh_networks()
|
||||
|
||||
def _refresh_networks(self):
|
||||
"""Refresh the networks list"""
|
||||
# Clear existing networks
|
||||
for child in self.networks_container.get_children():
|
||||
self.networks_container.remove(child)
|
||||
|
||||
if not self.nm.wifi_device or not self.nm.wifi_device.wireless_enabled:
|
||||
no_wifi_label = Label(
|
||||
label="WiFi is disabled",
|
||||
h_align="center",
|
||||
style_classes=["dim-label"],
|
||||
)
|
||||
self.networks_container.add(no_wifi_label)
|
||||
return
|
||||
|
||||
access_points = self.nm.wifi_device.access_points
|
||||
if not access_points:
|
||||
no_networks_label = Label(
|
||||
label="No networks found\nClick 'Scan for Networks' to refresh",
|
||||
h_align="center",
|
||||
style_classes=["dim-label"],
|
||||
)
|
||||
self.networks_container.add(no_networks_label)
|
||||
return
|
||||
|
||||
# Sort access points by signal strength (connected first, then by strength)
|
||||
sorted_aps = sorted(
|
||||
access_points, key=lambda ap: (not ap.is_active, -ap.strength)
|
||||
)
|
||||
|
||||
for ap in sorted_aps:
|
||||
self._create_network_item(ap)
|
||||
|
||||
self.networks_container.show_all()
|
||||
|
||||
def _create_network_item(self, ap):
|
||||
"""Create a network item widget"""
|
||||
network_box = Box(
|
||||
orientation="h",
|
||||
spacing=8,
|
||||
h_expand=True,
|
||||
style_classes=["network-item"],
|
||||
)
|
||||
|
||||
# Signal strength icon
|
||||
signal_strength = min(100, max(0, ap.strength))
|
||||
if signal_strength >= 75:
|
||||
signal_icon = icons.wifi_3
|
||||
elif signal_strength >= 50:
|
||||
signal_icon = icons.wifi_2
|
||||
elif signal_strength >= 25:
|
||||
signal_icon = icons.wifi_1
|
||||
else:
|
||||
signal_icon = icons.wifi_0
|
||||
|
||||
signal_label = Label(
|
||||
markup=signal_icon,
|
||||
style_classes=["network-signal"],
|
||||
)
|
||||
network_box.add(signal_label)
|
||||
|
||||
# Network name and status
|
||||
info_box = Box(
|
||||
orientation="v",
|
||||
h_expand=True,
|
||||
h_align="start",
|
||||
)
|
||||
|
||||
if ap.is_active:
|
||||
name_markup = f"<b>{ap.ssid}</b>"
|
||||
status_label = Label(
|
||||
label="Connected",
|
||||
h_align="start",
|
||||
style_classes=["network-status", "connected"],
|
||||
)
|
||||
info_box.add(Label(markup=name_markup, h_align="start"))
|
||||
info_box.add(status_label)
|
||||
else:
|
||||
name_label = Label(
|
||||
label=ap.ssid,
|
||||
h_align="start",
|
||||
style_classes=["network-name"],
|
||||
)
|
||||
info_box.add(name_label)
|
||||
|
||||
network_box.add(info_box)
|
||||
|
||||
# Lock icon for secured networks
|
||||
if ap.requires_password:
|
||||
lock_label = Label(
|
||||
markup=icons.lock,
|
||||
style_classes=["network-lock"],
|
||||
)
|
||||
network_box.add(lock_label)
|
||||
|
||||
# Make the whole item clickable
|
||||
network_button = Button(
|
||||
child=network_box,
|
||||
style_classes=["network-button"],
|
||||
on_clicked=lambda *_: self._on_network_clicked(ap),
|
||||
)
|
||||
|
||||
self.networks_container.add(network_button)
|
||||
|
||||
def _on_network_clicked(self, ap):
|
||||
"""Handle network item click"""
|
||||
if ap.is_active:
|
||||
# Disconnect from current network
|
||||
if self.nm.wifi_device:
|
||||
self.nm.wifi_device.disconnect_wifi()
|
||||
else:
|
||||
# Connect to network (simplified - would need password handling for secured networks)
|
||||
if self.nm.wifi_device:
|
||||
try:
|
||||
self.nm.wifi_device.connect_to_access_point(ap)
|
||||
except Exception as e:
|
||||
print(f"Failed to connect to {ap.ssid}: {e}")
|
||||
|
||||
# Refresh networks after action
|
||||
self._refresh_networks()
|
||||
+146
-54
@@ -1,26 +1,82 @@
|
||||
"""
|
||||
Dashboard Tile Components
|
||||
|
||||
This module provides tile widgets for the dashboard interface.
|
||||
|
||||
Overview:
|
||||
- Tiles are interactive components that display an icon, label, and status
|
||||
- When clicked, tiles show their detailed content in the dashboard's content area
|
||||
- Tiles can switch between mini (compact) and maxi (full) views
|
||||
- Two tile types are available: Tile (standard) and TileSpecial (custom layouts)
|
||||
|
||||
Architecture:
|
||||
- Tile: Standard tile with icon, label, properties, and optional menu button
|
||||
- TileSpecial: Custom tile that can switch between different view layouts
|
||||
- Both tiles communicate with the parent dashboard to show content
|
||||
|
||||
Usage:
|
||||
- Create tiles with appropriate properties and add them to the dashboard
|
||||
- The dashboard handles content switching and tile state management
|
||||
- Tiles automatically notify the dashboard when clicked
|
||||
"""
|
||||
|
||||
from fabric.widgets.box import Box
|
||||
from fabric.widgets.button import Button
|
||||
from fabric.widgets.label import Label
|
||||
from fabric.widgets.stack import Stack
|
||||
from fabric.widgets.revealer import Revealer
|
||||
from fabric.widgets.centerbox import CenterBox
|
||||
import utils.icons as icons
|
||||
|
||||
|
||||
class Tile(Box):
|
||||
"""
|
||||
A dashboard tile widget that displays an icon, label, and properties.
|
||||
|
||||
When clicked, the tile notifies the dashboard to show its detailed content
|
||||
in the dashboard's content area instead of expanding inline.
|
||||
|
||||
Args:
|
||||
menu (bool): Whether this tile has a menu button
|
||||
markup (str): Icon markup to display
|
||||
label (str): Text label for the tile
|
||||
props (Label): Properties widget to show tile status
|
||||
**kwargs: Additional widget arguments
|
||||
"""
|
||||
|
||||
def __init__(self, *, menu: bool, markup: str, label: str, props: Label, **kwargs):
|
||||
# Setup CSS classes
|
||||
default_classes = ["tile"]
|
||||
extra_classes = kwargs.pop("style_classes", [])
|
||||
merged_classes = default_classes + extra_classes
|
||||
super().__init__(style_classes=merged_classes, v_align="start", **kwargs)
|
||||
self.props = props
|
||||
self.icon = Label(style_classes="tile-icon", markup=markup)
|
||||
self.tile_label = Label(
|
||||
style_classes="tile-label", label=label, h_align="start"
|
||||
)
|
||||
self.toggle = False
|
||||
self.dashboard_instance = None # Will be set by the dashboard
|
||||
all_classes = default_classes + extra_classes
|
||||
|
||||
super().__init__(style_classes=all_classes, v_align="start", **kwargs)
|
||||
|
||||
# Store tile properties
|
||||
self.props = props
|
||||
self.toggle = False # Track if tile is active
|
||||
self.dashboard_instance = None # Reference to parent dashboard
|
||||
|
||||
# Create tile components
|
||||
self._create_tile_icon(markup)
|
||||
self._create_tile_content(label)
|
||||
self._create_tile_layout(menu)
|
||||
|
||||
def _create_tile_icon(self, markup: str):
|
||||
"""Create the tile's icon label."""
|
||||
self.icon = Label(
|
||||
style_classes="tile-icon",
|
||||
markup=markup
|
||||
)
|
||||
|
||||
def _create_tile_content(self, label: str):
|
||||
"""Create the tile's text content area."""
|
||||
self.tile_label = Label(
|
||||
style_classes="tile-label",
|
||||
label=label,
|
||||
h_align="start"
|
||||
)
|
||||
|
||||
# Container for label and properties
|
||||
self.type_box = Box(
|
||||
style_classes="tile-type",
|
||||
orientation="v",
|
||||
@@ -30,41 +86,39 @@ class Tile(Box):
|
||||
children=[self.tile_label, self.props],
|
||||
)
|
||||
|
||||
self.menu_button = Button(
|
||||
style_classes="tile-button",
|
||||
h_expand=True,
|
||||
child=Label(style_classes="tile-icon", markup=icons.arrow_head),
|
||||
on_clicked=self.handle_click,
|
||||
)
|
||||
|
||||
self.content_button = None
|
||||
|
||||
if menu:
|
||||
self.content_button = Revealer(
|
||||
transition_duration=150,
|
||||
transition_type="slide-left",
|
||||
def _create_tile_layout(self, has_menu: bool):
|
||||
"""Create the tile's layout structure."""
|
||||
# Create menu button if needed
|
||||
if has_menu:
|
||||
self.menu_button = Button(
|
||||
style_classes="tile-button",
|
||||
h_expand=True,
|
||||
child=Box(children=[self.type_box, self.menu_button]),
|
||||
child_revealed=True,
|
||||
child=Label(style_classes="tile-icon", markup=icons.arrow_head),
|
||||
on_clicked=self.handle_click,
|
||||
)
|
||||
content_children = [self.type_box, self.menu_button]
|
||||
else:
|
||||
self.content_button = Revealer(
|
||||
transition_duration=150,
|
||||
transition_type="slide-left",
|
||||
child=Box(children=[self.type_box]),
|
||||
child_revealed=True,
|
||||
)
|
||||
content_children = [self.type_box]
|
||||
|
||||
self.normal_view = Box(
|
||||
children=[self.icon, self.content_button],
|
||||
# Create revealer for smooth transitions
|
||||
self.content_button = Revealer(
|
||||
transition_duration=150,
|
||||
transition_type="slide-left",
|
||||
h_expand=True,
|
||||
child=Box(children=content_children),
|
||||
child_revealed=True,
|
||||
)
|
||||
|
||||
self.menu = Button(
|
||||
# Create the main tile view
|
||||
self.normal_view = Box(children=[self.icon, self.content_button])
|
||||
|
||||
# Create placeholder menu (content is shown in dashboard content area)
|
||||
self.menu = Box(
|
||||
style_classes="tile-menu",
|
||||
child=Label(label="hi!"),
|
||||
on_clicked=self.handle_click,
|
||||
children=[Label(label="Tile content will be shown in dashboard content area")],
|
||||
)
|
||||
|
||||
# Stack to switch between normal and menu views
|
||||
self.stack = Stack(
|
||||
transition_type="crossfade",
|
||||
transition_duration=150,
|
||||
@@ -74,30 +128,39 @@ class Tile(Box):
|
||||
|
||||
self.children = self.stack
|
||||
|
||||
def handle_click(self, source):
|
||||
if self.toggle:
|
||||
self.toggle = False
|
||||
self.menu.add_style_class("contract")
|
||||
self.menu.remove_style_class("expand")
|
||||
self.stack.set_visible_child(self.normal_view)
|
||||
else:
|
||||
self.toggle = True
|
||||
self.stack.set_visible_child(self.menu)
|
||||
self.menu.add_style_class("expand")
|
||||
self.menu.remove_style_class("contract")
|
||||
print(self.toggle, self.get_name())
|
||||
def handle_click(self, *_):
|
||||
"""
|
||||
Handle tile click events.
|
||||
|
||||
# Call dashboard method directly instead of using fabric-cli
|
||||
When clicked, the tile toggles its active state and notifies the dashboard
|
||||
to show/hide its content in the dashboard's content area.
|
||||
"""
|
||||
self.toggle = not self.toggle
|
||||
print(f"Tile {self.get_name()} clicked, toggle: {self.toggle}")
|
||||
|
||||
# Notify dashboard to handle content switching
|
||||
if self.dashboard_instance:
|
||||
self.dashboard_instance.handle_tile_menu_expand(self.get_name(), self.toggle)
|
||||
|
||||
def mini_view(self):
|
||||
"""
|
||||
Switch tile to mini (compact) view.
|
||||
|
||||
In mini view, the tile content is hidden and only the icon is shown.
|
||||
This is used when other tiles are active.
|
||||
"""
|
||||
self.content_button.set_reveal_child(False)
|
||||
self.icon.set_h_expand(True)
|
||||
self.content_button.set_h_expand(False)
|
||||
self.add_style_class("mini")
|
||||
|
||||
def maxi_view(self):
|
||||
"""
|
||||
Switch tile to maxi (full) view.
|
||||
|
||||
In maxi view, the tile shows its full content including label and properties.
|
||||
This is the default state when no tiles are active.
|
||||
"""
|
||||
self.content_button.set_reveal_child(True)
|
||||
self.icon.set_h_expand(False)
|
||||
self.content_button.set_h_expand(True)
|
||||
@@ -105,19 +168,37 @@ class Tile(Box):
|
||||
|
||||
|
||||
class TileSpecial(Box):
|
||||
"""
|
||||
A special tile widget that can switch between normal and mini views.
|
||||
|
||||
This tile type is used for components that need different layouts
|
||||
in normal vs compact modes (e.g., media player).
|
||||
|
||||
Args:
|
||||
mini_props: Widget(s) to show in mini view
|
||||
props: Widget(s) to show in normal view
|
||||
**kwargs: Additional widget arguments
|
||||
"""
|
||||
|
||||
def __init__(self, *, mini_props, props, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# Store view components
|
||||
self.props = props
|
||||
self.mini_props = mini_props
|
||||
self.toggle = False
|
||||
|
||||
self.content_button = None
|
||||
# Create view containers
|
||||
self._create_views()
|
||||
self._setup_layout()
|
||||
|
||||
self.normal_view = Box(
|
||||
children=self.props,
|
||||
)
|
||||
def _create_views(self):
|
||||
"""Create the normal and collapsed view containers."""
|
||||
self.normal_view = Box(children=self.props)
|
||||
self.collapsed_view = Box(children=self.mini_props)
|
||||
|
||||
def _setup_layout(self):
|
||||
"""Setup the stack layout for switching between views."""
|
||||
self.stack = Stack(
|
||||
transition_type="crossfade",
|
||||
transition_duration=150,
|
||||
@@ -125,15 +206,26 @@ class TileSpecial(Box):
|
||||
v_align="start",
|
||||
children=[self.normal_view, self.collapsed_view],
|
||||
)
|
||||
|
||||
self.children = self.stack
|
||||
|
||||
def mini_view(self):
|
||||
"""
|
||||
Switch to mini (collapsed) view.
|
||||
|
||||
Hides the normal view and shows the compact mini view.
|
||||
Uses negative margins to hide the normal view completely.
|
||||
"""
|
||||
self.normal_view.set_style("margin:-999px")
|
||||
self.collapsed_view.set_style("margin:0px;")
|
||||
self.stack.set_visible_child(self.collapsed_view)
|
||||
|
||||
def maxi_view(self):
|
||||
"""
|
||||
Switch to maxi (normal) view.
|
||||
|
||||
Hides the mini view and shows the full normal view.
|
||||
Uses negative margins to hide the collapsed view completely.
|
||||
"""
|
||||
self.collapsed_view.set_style("margin:-999px")
|
||||
self.normal_view.set_style("margin:0px;")
|
||||
self.stack.set_visible_child(self.normal_view)
|
||||
@@ -1,395 +0,0 @@
|
||||
.player {
|
||||
/* background-color: aqua; */
|
||||
min-height: 100px;
|
||||
min-width: 400px;
|
||||
box-shadow:
|
||||
inset 0 100px 100px black,
|
||||
inset 10px 0px 10px rgba(0, 0, 0, 0.5),
|
||||
inset -10px 0px 10px rgba(0, 0, 0, 0.5);
|
||||
border-bottom-right-radius: 20px;
|
||||
border-bottom-left-radius: 20px;
|
||||
padding: 20px;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
.player.vertical {
|
||||
box-shadow:
|
||||
inset 100px 0px 150px black,
|
||||
inset 0px 10px 10px rgba(0, 0, 0, 0.5),
|
||||
inset 0px -10px 10px rgba(0, 0, 0, 0.5);
|
||||
border-top-right-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
min-width: 400px;
|
||||
min-height: 150px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.player-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
#artist {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
#song {
|
||||
color: var(--surface);
|
||||
font-size: 16px;
|
||||
margin-top: 5px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
#wiggle-bar {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
#source {
|
||||
/* background-color: antiquewhite; */
|
||||
margin-top: -10px;
|
||||
}
|
||||
|
||||
#controls {
|
||||
margin-bottom: -10px;
|
||||
}
|
||||
|
||||
#play-next,
|
||||
#play-previous {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
#progress {
|
||||
/* min-height: 1px; */
|
||||
background-color: antiquewhite;
|
||||
}
|
||||
|
||||
#pause-button {
|
||||
background-color: var(--surface);
|
||||
border-radius: 20px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#pause-button.pause-track {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
#pause-label,
|
||||
#play-label {
|
||||
color: var(--primary);
|
||||
font-size: 20px;
|
||||
padding: 2px;
|
||||
border-radius: 999px;
|
||||
min-width: 25px;
|
||||
}
|
||||
|
||||
#shuffle-button,
|
||||
#next-button,
|
||||
#prev-button {
|
||||
border: none;
|
||||
padding: 0px;
|
||||
padding-left: 11px;
|
||||
padding-right: 11px;
|
||||
}
|
||||
|
||||
#shuffle-button:hover,
|
||||
#next-button:hover,
|
||||
#prev-button:hover {
|
||||
background-color: var(--surface-bright);
|
||||
}
|
||||
|
||||
#shuffle {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.player-button {
|
||||
border-radius: 5px;
|
||||
/* background-color: var(--secondary); */
|
||||
background-color: white;
|
||||
opacity: 0.5;
|
||||
min-width: 10px;
|
||||
min-height: 10px;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
border: none;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.player-button.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#player-switch-container.vertical-player {
|
||||
margin-top: -23px;
|
||||
padding-bottom: 13px;
|
||||
}
|
||||
|
||||
#player-switch-container {
|
||||
margin-top: -18px;
|
||||
padding-bottom: 13px;
|
||||
}
|
||||
|
||||
#controls.vertical {
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
#disable-shuffle {
|
||||
font-size: 27px;
|
||||
}
|
||||
|
||||
#progress-slider {
|
||||
margin-left: -3px;
|
||||
margin-right: -2px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
#progress-other-end {
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
.player-mini {
|
||||
min-height: 60px;
|
||||
min-width: 220px;
|
||||
padding-right: 20px;
|
||||
/* box-shadow: inset 0 0 100px rgb(148, 148, 148),
|
||||
inset 60px 0px 10px rgba(255, 255, 255, 1); */
|
||||
|
||||
box-shadow:
|
||||
inset 0 0 100px rgba(148, 148, 148, 0.2),
|
||||
inset 70px 0 20px rgba(255, 255, 255, 0.2),
|
||||
inset 0px 0px 20px 5px rgba(255, 255, 255, 0.2);
|
||||
border-top-right-radius: 15px;
|
||||
border-bottom-right-radius: 15px;
|
||||
border-top-left-radius: 20px;
|
||||
border-bottom-left-radius: 20px;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
#player-container.mini {
|
||||
min-height: 60px;
|
||||
border-radius: 15px;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.album-image {
|
||||
min-width: 65px;
|
||||
min-height: 60px;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
/* border-top-left-radius: 15px;
|
||||
border-bottom-left-radius: 15px; */
|
||||
border-radius: 15px;
|
||||
/* margin: 2px; */
|
||||
}
|
||||
|
||||
.hide-player {
|
||||
margin: -350px;
|
||||
transition: margin 0.25s cubic-bezier(0.5, 0.25, 0, 1);
|
||||
/* animation: hidePlayer 0.5s ease; */
|
||||
}
|
||||
|
||||
@keyframes hidePlayer {
|
||||
0% {
|
||||
margin: 0px;
|
||||
}
|
||||
100% {
|
||||
margin: -999px;
|
||||
}
|
||||
}
|
||||
|
||||
.reveal-player {
|
||||
margin: 0px;
|
||||
transition: margin 0.25s cubic-bezier(0.5, 0.25, 0, 1);
|
||||
/* animation: revealPlayer 0.5s ease; */
|
||||
}
|
||||
|
||||
#artist {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
#song {
|
||||
color: whitesmoke;
|
||||
font-size: 16px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
#song.mini {
|
||||
color: var(--surface);
|
||||
font-size: 15px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#source {
|
||||
/* background-color: antiquewhite; */
|
||||
margin-top: -10px;
|
||||
}
|
||||
#source.mini {
|
||||
margin-left: -20px;
|
||||
}
|
||||
|
||||
#controls {
|
||||
margin-bottom: -10px;
|
||||
}
|
||||
#play-next,
|
||||
#play-previous {
|
||||
text-shadow: none;
|
||||
font-size: 30px;
|
||||
}
|
||||
#play-next.mini,
|
||||
#play-previous.mini {
|
||||
text-shadow: none;
|
||||
font-size: 30px;
|
||||
color: var(--surface);
|
||||
}
|
||||
#play-pause-label {
|
||||
color: var(--surface);
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
#progress {
|
||||
/* min-height: 1px; */
|
||||
background-color: antiquewhite;
|
||||
}
|
||||
|
||||
#pause-button {
|
||||
/* background-color: var(--surface); */
|
||||
background-color: whitesmoke;
|
||||
border-radius: 16px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.vertical #pause-button {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#pause-button.pause-track {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
#pause-label,
|
||||
#play-label {
|
||||
color: var(--shadow);
|
||||
/* background-color: var(--error-dim); */
|
||||
font-size: 20px;
|
||||
padding: 2px;
|
||||
border-radius: 999px;
|
||||
min-width: 25px;
|
||||
}
|
||||
|
||||
#pause-label.mini,
|
||||
#play-label.mini {
|
||||
color: white;
|
||||
/* background-color: var(--error-dim); */
|
||||
font-size: 20px;
|
||||
padding: 2px;
|
||||
border-radius: 999px;
|
||||
min-width: 25px;
|
||||
}
|
||||
|
||||
#shuffle-button,
|
||||
#next-button,
|
||||
#prev-button,
|
||||
#play-pause-button {
|
||||
border: none;
|
||||
padding: 0px;
|
||||
padding-left: 11px;
|
||||
padding-right: 11px;
|
||||
}
|
||||
|
||||
#shuffle-button:hover,
|
||||
#next-button:hover,
|
||||
#prev-button:hover,
|
||||
#play-pause-button:hover {
|
||||
border-radius: 100%;
|
||||
/* background-color: white; */
|
||||
/* opacity: .3; */
|
||||
box-shadow: inset rgba(255, 255, 255, 0.5) 0 0 10px;
|
||||
}
|
||||
|
||||
.player-button {
|
||||
border-radius: 5px;
|
||||
/* background-color: var(--secondary); */
|
||||
background-color: white;
|
||||
opacity: 0.5;
|
||||
min-width: 10px;
|
||||
min-height: 10px;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
border: none;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.player-button.mini {
|
||||
border-radius: 5px;
|
||||
background-color: var(--surface);
|
||||
/* background-color: white; */
|
||||
opacity: 0.5;
|
||||
min-width: 10px;
|
||||
min-height: 10px;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
border: none;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.player-button.active {
|
||||
opacity: 1;
|
||||
/* min-width: 40px; */
|
||||
/* border-radius: 5px; */
|
||||
}
|
||||
|
||||
#player-switch-container.vertical-player {
|
||||
margin-top: -23px;
|
||||
padding-bottom: 13px;
|
||||
}
|
||||
|
||||
#player-switch-container {
|
||||
margin-top: -17px;
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
|
||||
#player-switch-container.mini {
|
||||
margin-top: 0px;
|
||||
margin-left: -20px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
#controls.vertical {
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.player-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.player-icon.mini {
|
||||
font-size: 20px;
|
||||
color: black;
|
||||
text-shadow: 0 0 5px white;
|
||||
}
|
||||
|
||||
#shuffle {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
#disable-shuffle {
|
||||
font-size: 27px;
|
||||
}
|
||||
|
||||
#progress-slider {
|
||||
background-color: white;
|
||||
margin-left: -3px;
|
||||
margin-right: -2px;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
#progress-other-end {
|
||||
min-width: 50px;
|
||||
background-color: white;
|
||||
}
|
||||
+3
-3
@@ -76,21 +76,21 @@ bat_full: str = ""
|
||||
# Applets
|
||||
|
||||
# Applets
|
||||
wifi: str = chr(0xF1EB)
|
||||
wifi: str = ""
|
||||
wifi_0: str = ""
|
||||
wifi_1: str = ""
|
||||
wifi_2: str = ""
|
||||
wifi_3: str = ""
|
||||
world: str = ""
|
||||
world_off: str = ""
|
||||
bluetooth: str = chr(0xF294)
|
||||
bluetooth: str = ""
|
||||
night: str = ""
|
||||
coffee: str = ""
|
||||
notifications: str = ""
|
||||
notifications_dots: str = ""
|
||||
|
||||
blur: str = chr(0xF00B5)
|
||||
arrow_head: str = chr(0xE285)
|
||||
arrow_head: str = ""
|
||||
wifi_off: str = ""
|
||||
bluetooth_off: str = ""
|
||||
night_off: str = ""
|
||||
|
||||
Reference in new issue
Block a user