From defb83d213db62b398d73dff38500d456900f204 Mon Sep 17 00:00:00 2001 From: LemonNexus Date: Sun, 15 Feb 2026 16:16:07 +0000 Subject: [PATCH] feat: Widget Registry - plugins register widgets dynamically --- core/overlay_window.py | 136 ++++++++++++++++------------------------ core/widget_registry.py | 99 +++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 81 deletions(-) create mode 100644 core/widget_registry.py diff --git a/core/overlay_window.py b/core/overlay_window.py index 8d11291..8f14ee4 100644 --- a/core/overlay_window.py +++ b/core/overlay_window.py @@ -1590,55 +1590,55 @@ class OverlayWindow(QMainWindow): layout.addWidget(header) # Description - desc = QLabel("Add overlay widgets to your game. Drag to position, resize, and configure.") + desc = QLabel("Add overlay widgets to your game. Install plugins to get more widgets.") desc.setStyleSheet(f"color: {c['text_secondary']}; font-size: 12px;") desc.setWordWrap(True) layout.addWidget(desc) - # Built-in widgets section - builtin_header = QLabel("Built-in Widgets") - builtin_header.setStyleSheet(f""" - color: {c['text_primary']}; - font-weight: {EU_TYPOGRAPHY['weight_bold']}; - font-size: 14px; - margin-top: 16px; - padding-bottom: 8px; - border-bottom: 1px solid {c['border_default']}; - """) - layout.addWidget(builtin_header) + # Get registered widgets + from core.widget_registry import get_widget_registry + registry = get_widget_registry() + widgets = registry.get_all_widgets() - # Clock widget button - clock_btn = self._create_widget_button( - "⏰ Clock", - "A customizable clock with 12/24h format and date display", - lambda: self._add_clock_widget() - ) - layout.addWidget(clock_btn) - - # System monitor widget button - monitor_btn = self._create_widget_button( - "📊 System Monitor", - "Monitor CPU and RAM usage in real-time", - lambda: self._add_system_monitor_widget() - ) - layout.addWidget(monitor_btn) - - # Plugin widgets section - plugin_header = QLabel("Plugin Widgets") - plugin_header.setStyleSheet(f""" - color: {c['text_primary']}; - font-weight: {EU_TYPOGRAPHY['weight_bold']}; - font-size: 14px; - margin-top: 24px; - padding-bottom: 8px; - border-bottom: 1px solid {c['border_default']}; - """) - layout.addWidget(plugin_header) - - plugin_info = QLabel("Install plugins from the Plugin Store to add more widgets here.") - plugin_info.setStyleSheet(f"color: {c['text_muted']}; font-size: 12px; font-style: italic;") - plugin_info.setWordWrap(True) - layout.addWidget(plugin_info) + if widgets: + # Available widgets section + available_header = QLabel("Available Widgets") + available_header.setStyleSheet(f""" + color: {c['text_primary']}; + font-weight: {EU_TYPOGRAPHY['weight_bold']}; + font-size: 14px; + margin-top: 16px; + padding-bottom: 8px; + border-bottom: 1px solid {c['border_default']}; + """) + layout.addWidget(available_header) + + # Create buttons for each registered widget + for widget_info in widgets: + widget_btn = self._create_widget_button( + f"{widget_info.icon} {widget_info.name}", + widget_info.description, + lambda wid=widget_info.id: self._add_registered_widget(wid) + ) + layout.addWidget(widget_btn) + else: + # No widgets available + no_widgets = QLabel("No widgets available") + no_widgets.setStyleSheet(f""" + color: {c['text_muted']}; + font-size: 14px; + font-style: italic; + margin-top: 24px; + """) + no_widgets.setAlignment(Qt.AlignmentFlag.AlignCenter) + layout.addWidget(no_widgets) + + # Install info + install_info = QLabel("Install plugins from the Plugin Store to add widgets here.") + install_info.setStyleSheet(f"color: {c['text_muted']}; font-size: 12px;") + install_info.setWordWrap(True) + install_info.setAlignment(Qt.AlignmentFlag.AlignCenter) + layout.addWidget(install_info) layout.addStretch() @@ -1732,13 +1732,17 @@ class OverlayWindow(QMainWindow): return tab - def _add_clock_widget(self): - """Add a clock widget to the overlay.""" + def _add_registered_widget(self, widget_id: str): + """Add a registered widget to the overlay.""" try: - from core.widget_system import ClockWidget + from core.widget_registry import get_widget_registry + registry = get_widget_registry() - # Create widget with self as parent - widget = ClockWidget(parent=self) + # Create widget using registry + widget = registry.create_widget(widget_id) + if not widget: + print(f"[Overlay] Failed to create widget: {widget_id}") + return # Position near center of screen screen = QApplication.primaryScreen().geometry() @@ -1756,38 +1760,8 @@ class OverlayWindow(QMainWindow): self._active_widgets = [] self._active_widgets.append(widget) - print(f"[Overlay] Clock widget added at ({x}, {y})") + print(f"[Overlay] Widget {widget_id} added at ({x}, {y})") except Exception as e: - print(f"[Overlay] Error adding clock widget: {e}") - import traceback - traceback.print_exc() - - def _add_system_monitor_widget(self): - """Add a system monitor widget to the overlay.""" - try: - from core.widget_system import SystemMonitorWidget - - # Create widget with self as parent - widget = SystemMonitorWidget(parent=self) - - # Position below the clock - screen = QApplication.primaryScreen().geometry() - x = (screen.width() - 200) // 2 - y = (screen.height() - 100) // 2 + 120 - widget.move(x, y) - - # Show and raise - widget.show() - widget.raise_() - widget.activateWindow() - - # Store reference to prevent garbage collection - if not hasattr(self, '_active_widgets'): - self._active_widgets = [] - self._active_widgets.append(widget) - - print(f"[Overlay] System monitor widget added at ({x}, {y})") - except Exception as e: - print(f"[Overlay] Error adding system monitor widget: {e}") + print(f"[Overlay] Error adding widget {widget_id}: {e}") import traceback traceback.print_exc() diff --git a/core/widget_registry.py b/core/widget_registry.py new file mode 100644 index 0000000..47c161d --- /dev/null +++ b/core/widget_registry.py @@ -0,0 +1,99 @@ +""" +EU-Utility - Widget Registry + +Allows plugins to register widgets that appear in the Widgets tab. +""" + +from typing import Dict, List, Callable, Optional +from dataclasses import dataclass +from PyQt6.QtWidgets import QWidget + + +@dataclass +class WidgetInfo: + """Information about a registered widget.""" + id: str + name: str + description: str + icon: str # emoji or icon name + creator: Callable[[], QWidget] # Function that creates the widget + plugin_id: str # Which plugin registered this + + +class WidgetRegistry: + """Registry for plugin-provided widgets.""" + + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super().__new__(cls) + cls._instance._widgets: Dict[str, WidgetInfo] = {} + return cls._instance + + def register_widget( + self, + widget_id: str, + name: str, + description: str, + icon: str, + creator: Callable[[], QWidget], + plugin_id: str = "unknown" + ): + """Register a widget. + + Args: + widget_id: Unique identifier for this widget type + name: Display name + description: Short description shown in UI + icon: Emoji or icon name + creator: Function that returns a QWidget instance + plugin_id: ID of the plugin registering this widget + """ + self._widgets[widget_id] = WidgetInfo( + id=widget_id, + name=name, + description=description, + icon=icon, + creator=creator, + plugin_id=plugin_id + ) + print(f"[WidgetRegistry] Registered widget: {name} (from {plugin_id})") + + def unregister_widget(self, widget_id: str): + """Unregister a widget.""" + if widget_id in self._widgets: + del self._widgets[widget_id] + print(f"[WidgetRegistry] Unregistered widget: {widget_id}") + + def get_widget(self, widget_id: str) -> Optional[WidgetInfo]: + """Get widget info by ID.""" + return self._widgets.get(widget_id) + + def get_all_widgets(self) -> List[WidgetInfo]: + """Get all registered widgets.""" + return list(self._widgets.values()) + + def get_widgets_by_plugin(self, plugin_id: str) -> List[WidgetInfo]: + """Get all widgets registered by a specific plugin.""" + return [w for w in self._widgets.values() if w.plugin_id == plugin_id] + + def create_widget(self, widget_id: str) -> Optional[QWidget]: + """Create an instance of a registered widget.""" + info = self._widgets.get(widget_id) + if info: + try: + return info.creator() + except Exception as e: + print(f"[WidgetRegistry] Error creating widget {widget_id}: {e}") + return None + + def clear(self): + """Clear all registered widgets.""" + self._widgets.clear() + + +# Global registry instance +def get_widget_registry() -> WidgetRegistry: + """Get the global widget registry.""" + return WidgetRegistry()