Files
Modus/modules/dashboard/tile.py
T

216 lines
7.1 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
content_box = Box(
style_classes="tile-type",
orientation="v",
v_expand=True,
h_expand=True,
v_align="center",
children=[self.tile_label, self.props],
)
# Make the type_box clickable using a button
self.type_box = Button(
style_classes="tile-content-button",
h_expand=True,
v_expand=True,
child=content_box,
on_clicked=self.handle_click,
)
# Add hover cursor to the content area
add_hover_cursor(self.type_box)
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.chevron_right),
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
)
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