242 lines
8.2 KiB
Python
242 lines
8.2 KiB
Python
"""
|
|
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 gi.repository import Gdk
|
|
import utils.icons as icons
|
|
|
|
|
|
def add_hover_cursor(widget):
|
|
"""Add hover cursor functionality to a 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 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", [])
|
|
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",
|
|
v_expand=True,
|
|
h_expand=True,
|
|
v_align="center",
|
|
children=[self.tile_label, self.props],
|
|
)
|
|
|
|
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=Label(style_classes="tile-icon", markup=icons.arrow_head),
|
|
on_clicked=self.handle_click,
|
|
)
|
|
# Add hover cursor to menu button
|
|
add_hover_cursor(self.menu_button)
|
|
content_children = [self.type_box, self.menu_button]
|
|
else:
|
|
content_children = [self.type_box]
|
|
|
|
# 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,
|
|
)
|
|
|
|
# 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",
|
|
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,
|
|
h_expand=True,
|
|
children=[self.normal_view, self.menu],
|
|
)
|
|
|
|
self.children = self.stack
|
|
|
|
def handle_click(self, *_):
|
|
"""
|
|
Handle tile click events.
|
|
|
|
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)
|
|
self.remove_style_class("mini")
|
|
|
|
|
|
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
|
|
|
|
# Create view containers
|
|
self._create_views()
|
|
self._setup_layout()
|
|
|
|
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,
|
|
h_expand=True,
|
|
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)
|