""" 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', ]