EU-Utility/premium/widgets/__init__.py

379 lines
11 KiB
Python

"""
EU-Utility Premium - Widget System
===================================
Dashboard widget system for creating plugin UIs.
Example:
from premium.widgets import Widget, WidgetConfig
class MyWidget(Widget):
def __init__(self):
super().__init__(WidgetConfig(
name="My Widget",
icon="📊",
size=(300, 200)
))
def create_ui(self, parent):
# Create and return your widget
label = QLabel("Hello World", parent)
return label
"""
from __future__ import annotations
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum, auto
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
# =============================================================================
# WIDGET CONFIG
# =============================================================================
class WidgetSize(Enum):
"""Standard widget sizes."""
SMALL = (150, 100)
MEDIUM = (300, 200)
LARGE = (450, 300)
WIDE = (600, 200)
TALL = (300, 400)
FULL = (600, 400)
@dataclass
class WidgetConfig:
"""Configuration for a widget.
Attributes:
name: Display name of the widget
icon: Emoji or icon name
size: Widget size tuple (width, height)
resizable: Whether widget can be resized
collapsible: Whether widget can be collapsed
refresh_interval: Auto-refresh interval in seconds (0 = disabled)
settings: Widget-specific settings
"""
name: str = "Widget"
icon: str = "📦"
size: Tuple[int, int] = (300, 200)
resizable: bool = True
collapsible: bool = True
refresh_interval: float = 0.0
settings: Dict[str, Any] = field(default_factory=dict)
category: str = "General"
description: str = ""
author: str = ""
version: str = "1.0.0"
# =============================================================================
# WIDGET BASE CLASS
# =============================================================================
class Widget(ABC):
"""Base class for dashboard widgets.
Widgets are the UI components that plugins can create.
They are displayed in the dashboard overlay.
Example:
class StatsWidget(Widget):
def __init__(self):
super().__init__(WidgetConfig(
name="Player Stats",
icon="👤",
size=WidgetSize.MEDIUM.value
))
def create_ui(self, parent):
self.widget = QWidget(parent)
layout = QVBoxLayout(self.widget)
self.label = QLabel("Loading...")
layout.addWidget(self.label)
return self.widget
def update(self, data):
self.label.setText(f"Level: {data['level']}")
"""
def __init__(self, config: WidgetConfig):
self.config = config
self._ui: Optional[Any] = None
self._visible = True
self._collapsed = False
self._data: Dict[str, Any] = {}
self._refresh_timer = None
self._callbacks: Dict[str, List[Callable]] = {
'update': [],
'resize': [],
'collapse': [],
'close': [],
}
# ========== Abstract Methods ==========
@abstractmethod
def create_ui(self, parent: Any) -> Any:
"""Create the widget UI.
Args:
parent: Parent widget (QWidget)
Returns:
The created widget
"""
pass
# ========== Lifecycle Methods ==========
def initialize(self, parent: Any) -> Any:
"""Initialize the widget with a parent."""
self._ui = self.create_ui(parent)
if self.config.refresh_interval > 0:
self._start_refresh_timer()
return self._ui
def destroy(self) -> None:
"""Destroy the widget and clean up resources."""
self._stop_refresh_timer()
self._emit('close')
if self._ui:
self._ui.deleteLater()
self._ui = None
# ========== Update Methods ==========
def update(self, data: Dict[str, Any]) -> None:
"""Update widget with new data.
Override this to update your widget's display.
"""
self._data.update(data)
self._emit('update', data)
self.on_update(data)
def on_update(self, data: Dict[str, Any]) -> None:
"""Called when widget receives new data.
Override this instead of update() for custom behavior.
"""
pass
def refresh(self) -> None:
"""Manually trigger a refresh."""
self.on_refresh()
def on_refresh(self) -> None:
"""Called when widget should refresh its display."""
pass
# ========== State Methods ==========
def show(self) -> None:
"""Show the widget."""
self._visible = True
if self._ui:
self._ui.show()
def hide(self) -> None:
"""Hide the widget."""
self._visible = False
if self._ui:
self._ui.hide()
def collapse(self) -> None:
"""Collapse the widget."""
self._collapsed = True
self._emit('collapse', True)
if self._ui:
self._ui.setMaximumHeight(40)
def expand(self) -> None:
"""Expand the widget."""
self._collapsed = False
self._emit('collapse', False)
if self._ui:
self._ui.setMaximumHeight(self.config.size[1])
def toggle_collapse(self) -> None:
"""Toggle collapsed state."""
if self._collapsed:
self.expand()
else:
self.collapse()
# ========== Property Methods ==========
@property
def visible(self) -> bool:
"""Whether widget is visible."""
return self._visible
@property
def collapsed(self) -> bool:
"""Whether widget is collapsed."""
return self._collapsed
@property
def data(self) -> Dict[str, Any]:
"""Current widget data."""
return self._data.copy()
@property
def ui(self) -> Optional[Any]:
"""The UI widget."""
return self._ui
# ========== Event Handling ==========
def on(self, event: str, callback: Callable) -> Callable:
"""Subscribe to a widget event."""
if event in self._callbacks:
self._callbacks[event].append(callback)
return callback
def _emit(self, event: str, *args, **kwargs) -> None:
"""Emit an event to subscribers."""
for callback in self._callbacks.get(event, []):
try:
callback(*args, **kwargs)
except Exception as e:
print(f"Error in widget callback: {e}")
# ========== Timer Methods ==========
def _start_refresh_timer(self) -> None:
"""Start auto-refresh timer."""
if self.config.refresh_interval <= 0:
return
try:
from PyQt6.QtCore import QTimer
self._refresh_timer = QTimer()
self._refresh_timer.timeout.connect(self.refresh)
self._refresh_timer.start(int(self.config.refresh_interval * 1000))
except ImportError:
pass
def _stop_refresh_timer(self) -> None:
"""Stop auto-refresh timer."""
if self._refresh_timer:
self._refresh_timer.stop()
self._refresh_timer = None
# =============================================================================
# DASHBOARD
# =============================================================================
class Dashboard:
"""Dashboard for managing widgets.
The dashboard is the container for all plugin widgets.
It manages layout, visibility, and widget lifecycle.
"""
def __init__(self, parent: Any = None):
self.parent = parent
self._widgets: Dict[str, Widget] = {}
self._layout: Optional[Any] = None
self._visible = False
self._position: Tuple[int, int] = (100, 100)
def initialize(self, parent: Any) -> Any:
"""Initialize the dashboard."""
try:
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QScrollArea
self.container = QWidget(parent)
self.container.setWindowTitle("EU-Utility Dashboard")
self.container.setGeometry(*self._position, 350, 600)
scroll = QScrollArea(self.container)
scroll.setWidgetResizable(True)
self.widget_container = QWidget()
self._layout = QVBoxLayout(self.widget_container)
self._layout.setSpacing(10)
self._layout.addStretch()
scroll.setWidget(self.widget_container)
layout = QVBoxLayout(self.container)
layout.addWidget(scroll)
return self.container
except ImportError:
return None
def add_widget(self, widget_id: str, widget: Widget) -> bool:
"""Add a widget to the dashboard."""
if widget_id in self._widgets:
return False
self._widgets[widget_id] = widget
if self._layout:
ui = widget.initialize(self.widget_container)
if ui:
# Insert before the stretch
self._layout.insertWidget(self._layout.count() - 1, ui)
return True
def remove_widget(self, widget_id: str) -> bool:
"""Remove a widget from the dashboard."""
widget = self._widgets.pop(widget_id, None)
if widget:
widget.destroy()
return True
return False
def get_widget(self, widget_id: str) -> Optional[Widget]:
"""Get a widget by ID."""
return self._widgets.get(widget_id)
def show(self) -> None:
"""Show the dashboard."""
self._visible = True
if hasattr(self, 'container') and self.container:
self.container.show()
def hide(self) -> None:
"""Hide the dashboard."""
self._visible = False
if hasattr(self, 'container') and self.container:
self.container.hide()
def toggle(self) -> None:
"""Toggle dashboard visibility."""
if self._visible:
self.hide()
else:
self.show()
def get_all_widgets(self) -> Dict[str, Widget]:
"""Get all widgets."""
return self._widgets.copy()
# =============================================================================
# EXPORTS
# =============================================================================
__all__ = [
'WidgetSize',
'WidgetConfig',
'Widget',
'Dashboard',
]