From cfdf21ea6d04c402fa403c061afeca42935c302d Mon Sep 17 00:00:00 2001 From: LemonNexus Date: Mon, 16 Feb 2026 00:30:23 +0000 Subject: [PATCH] fix: UI improvements based on feedback 1. Sidebar toggle: Now uses arrows (chevron-right when collapsed, chevron-left when expanded) 2. Dashboard: Quick actions filtered to show only installed plugins 3. Plugins page: New tabs - 'Installed' (enable/disable plugins) and 'Store' (browse/install) 4. Widgets page: Now shows registered widgets from plugins with enable/disable 5. Settings page: Removed Plugin Store and My Plugins tabs (moved to Plugins page) 6. Settings: Changed 'Overlay Opacity' to 'Background Opacity' for clarity 7. Status bar: Shows plugin count, EU window status (Not Running/Running/Focused), version 8. New files: plugins_view.py, widgets_view.py with proper functionality --- core/perfect_ux.py | 82 +++++++++++-- core/ui/plugins_view.py | 242 +++++++++++++++++++++++++++++++++++++++ core/ui/settings_view.py | 10 +- core/ui/widgets_view.py | 210 +++++++++++++++++++++++++++++++++ 4 files changed, 528 insertions(+), 16 deletions(-) create mode 100644 core/ui/plugins_view.py create mode 100644 core/ui/widgets_view.py diff --git a/core/perfect_ux.py b/core/perfect_ux.py index a852449..86f34eb 100644 --- a/core/perfect_ux.py +++ b/core/perfect_ux.py @@ -62,6 +62,12 @@ try: except ImportError: PLUGINS_VIEW_AVAILABLE = False +try: + from core.ui.widgets_view import WidgetsView + WIDGETS_VIEW_AVAILABLE = True +except ImportError: + WIDGETS_VIEW_AVAILABLE = False + # ============================================================ # DESIGN TOKENS - Material Design 3 Inspired @@ -381,12 +387,12 @@ class NavigationRail(QFrame): self.main_layout.setContentsMargins(0, 12, 0, 12) self.main_layout.setSpacing(0) - # Top toggle button + # Top toggle button - use arrow pointing right when collapsed self.toggle_btn = QPushButton() self.toggle_btn.setFixedSize(56, 32) self.toggle_btn.setCursor(QCursor(Qt.CursorShape.PointingHandCursor)) self.toggle_btn.setToolTip("Expand sidebar") - self.toggle_btn.setIcon(self.icon_manager.get_icon("menu")) + self.toggle_btn.setIcon(self.icon_manager.get_icon("chevron-right")) # Arrow pointing right when collapsed self.toggle_btn.setIconSize(QSize(20, 20)) self.toggle_btn.setStyleSheet(f""" QPushButton {{ @@ -429,13 +435,13 @@ class NavigationRail(QFrame): # Animate width change self._animate_width() - # Update toggle button icon + # Update toggle button icon - use arrows instead of menu/close if self._expanded: self.toggle_btn.setToolTip("Collapse sidebar") - self.toggle_btn.setIcon(self.icon_manager.get_icon("close")) # Or "collapse" icon + self.toggle_btn.setIcon(self.icon_manager.get_icon("chevron-left")) # Arrow pointing left else: self.toggle_btn.setToolTip("Expand sidebar") - self.toggle_btn.setIcon(self.icon_manager.get_icon("menu")) + self.toggle_btn.setIcon(self.icon_manager.get_icon("chevron-right")) # Arrow pointing right # Update all destinations to show/hide labels for dest in self.destinations: @@ -1034,6 +1040,14 @@ class PerfectMainWindow(QMainWindow): def _create_widgets_view(self) -> QWidget: """Create the Widgets view.""" + # Try to use the actual WidgetsView if available + if WIDGETS_VIEW_AVAILABLE and self.plugin_manager: + try: + return WidgetsView(self) + except Exception as e: + print(f"[PerfectUX] Failed to create WidgetsView: {e}") + + # Fallback to placeholder container = QWidget() layout = QVBoxLayout(container) layout.setContentsMargins(32, 32, 32, 32) @@ -1081,7 +1095,7 @@ class PerfectMainWindow(QMainWindow): return container def _create_status_bar(self): - """Create bottom status bar.""" + """Create bottom status bar with useful info.""" self.status_bar = QFrame() self.status_bar.setFixedHeight(32) self.status_bar.setStyleSheet(""" @@ -1094,17 +1108,61 @@ class PerfectMainWindow(QMainWindow): layout = QHBoxLayout(self.status_bar) layout.setContentsMargins(16, 0, 16, 0) - self.status_text = QLabel("Ready") - self.status_text.setStyleSheet("color: rgba(255, 255, 255, 0.5); font-size: 12px;") - layout.addWidget(self.status_text) + # Left side - Plugin count + try: + plugin_count = len(self.plugin_manager.get_all_plugins()) if self.plugin_manager else 0 + except: + plugin_count = 0 + + self.plugin_count_label = QLabel(f"{plugin_count} Plugins") + self.plugin_count_label.setStyleSheet("color: rgba(255, 255, 255, 0.5); font-size: 12px;") + layout.addWidget(self.plugin_count_label) + + layout.addSpacing(20) + + # Middle - EU Status + self.eu_status_label = QLabel("EU: Not Running") + self.eu_status_label.setStyleSheet("color: rgba(255, 255, 255, 0.4); font-size: 12px;") + layout.addWidget(self.eu_status_label) layout.addStretch() + # Right side - Version self.version_text = QLabel("EU-Utility v2.2.0") self.version_text.setStyleSheet("color: rgba(255, 255, 255, 0.3); font-size: 11px;") layout.addWidget(self.version_text) self.centralWidget().layout().addWidget(self.status_bar) + + # Start EU status timer + self._start_eu_status_timer() + + def _start_eu_status_timer(self): + """Start timer to update EU status in status bar.""" + from PyQt6.QtCore import QTimer + self._status_timer = QTimer(self) + self._status_timer.timeout.connect(self._update_eu_status) + self._status_timer.start(2000) # Update every 2 seconds + + def _update_eu_status(self): + """Update EU status in status bar.""" + try: + from core.window_manager import get_window_manager + wm = get_window_manager() + if wm and wm.is_available(): + eu_window = wm.find_eu_window() + if eu_window: + if eu_window.is_focused: + self.eu_status_label.setText("EU: Focused") + self.eu_status_label.setStyleSheet("color: #4ecca3; font-size: 12px;") # Green + else: + self.eu_status_label.setText("EU: Running") + self.eu_status_label.setStyleSheet("color: #ffd93d; font-size: 12px;") # Yellow + else: + self.eu_status_label.setText("EU: Not Running") + self.eu_status_label.setStyleSheet("color: rgba(255, 255, 255, 0.4); font-size: 12px;") + except: + pass def _setup_shortcuts(self): """Setup keyboard shortcuts.""" @@ -1125,7 +1183,11 @@ class PerfectMainWindow(QMainWindow): if destination_id in view_map: self._animate_transition(view_map[destination_id]) - self.status_text.setText(f"View: {destination_id.title()}") + # Update status bar view indicator + if hasattr(self, 'plugin_count_label'): + view_name = destination_id.replace('_', ' ').title() + # Don't overwrite the plugin count, just update the view context + # The status bar now shows plugins, EU status, and version def _animate_transition(self, index: int): """Animate view transition.""" diff --git a/core/ui/plugins_view.py b/core/ui/plugins_view.py new file mode 100644 index 0000000..b28e287 --- /dev/null +++ b/core/ui/plugins_view.py @@ -0,0 +1,242 @@ +""" +EU-Utility - Plugins View (Core Framework Component) + +Built-in plugins interface with Installed and Store tabs. +""" + +from PyQt6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QPushButton, QCheckBox, QTabWidget, QFrame, + QScrollArea, QGridLayout, QSizePolicy +) +from PyQt6.QtCore import Qt + +from core.icon_manager import get_icon_manager + + +class PluginsView(QWidget): + """Main plugins interface - built into the framework. + + Features: + - Installed tab: Manage installed plugins (enable/disable) + - Store tab: Browse and install new plugins + """ + + def __init__(self, overlay_window, parent=None): + super().__init__(parent) + self.overlay = overlay_window + self.plugin_manager = overlay_window.plugin_manager if hasattr(overlay_window, 'plugin_manager') else None + self.icon_manager = get_icon_manager() + + self._setup_ui() + + def _setup_ui(self): + """Create the plugins UI.""" + layout = QVBoxLayout(self) + layout.setSpacing(16) + layout.setContentsMargins(24, 24, 24, 24) + + # Header with icon + header_layout = QHBoxLayout() + header_layout.setSpacing(12) + + header_icon = QLabel() + header_pixmap = self.icon_manager.get_pixmap("plugins", size=28) + header_icon.setPixmap(header_pixmap) + header_layout.addWidget(header_icon) + + header = QLabel("Plugins") + header.setStyleSheet("font-size: 24px; font-weight: bold; color: white;") + header_layout.addWidget(header) + header_layout.addStretch() + + layout.addLayout(header_layout) + + # Tabs - Installed and Store + self.tabs = QTabWidget() + self.tabs.setStyleSheet(""" + QTabBar::tab { + background-color: rgba(20, 31, 35, 0.95); + color: rgba(255,255,255,150); + padding: 10px 20px; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + QTabBar::tab:selected { + background-color: #ff8c42; + color: white; + font-weight: bold; + } + """) + + # Add tabs + self.tabs.addTab(self._create_installed_tab(), "Installed") + self.tabs.addTab(self._create_store_tab(), "Store") + + layout.addWidget(self.tabs) + + def _create_installed_tab(self): + """Create installed plugins tab.""" + tab = QWidget() + layout = QVBoxLayout(tab) + layout.setSpacing(16) + + # Info label + info = QLabel("Manage your installed plugins. Enable or disable as needed.") + info.setStyleSheet("color: rgba(255,255,255,150); font-size: 13px;") + info.setWordWrap(True) + layout.addWidget(info) + + # Scroll area for plugin list + scroll = QScrollArea() + scroll.setWidgetResizable(True) + scroll.setStyleSheet("background: transparent; border: none;") + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + + self.installed_container = QWidget() + self.installed_layout = QVBoxLayout(self.installed_container) + self.installed_layout.setAlignment(Qt.AlignmentFlag.AlignTop) + self.installed_layout.setSpacing(8) + + self._populate_installed_plugins() + + scroll.setWidget(self.installed_container) + layout.addWidget(scroll) + + return tab + + def _populate_installed_plugins(self): + """Populate the installed plugins list.""" + # Clear existing + while self.installed_layout.count(): + item = self.installed_layout.takeAt(0) + if item.widget(): + item.widget().deleteLater() + + if not self.plugin_manager: + error = QLabel("Plugin Manager not available") + error.setStyleSheet("color: #ff4757;") + self.installed_layout.addWidget(error) + return + + # Get discovered plugins + try: + all_plugins = self.plugin_manager.get_all_discovered_plugins() + except AttributeError: + # Fallback - try different method + try: + all_plugins = {p.plugin_id: p for p in self.plugin_manager.plugins.values()} + except: + all_plugins = {} + + if not all_plugins: + no_plugins = QLabel("No plugins installed.\n\nVisit the Store tab to install plugins.") + no_plugins.setStyleSheet("color: rgba(255,255,255,100); padding: 40px;") + no_plugins.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.installed_layout.addWidget(no_plugins) + return + + # Sort by name + sorted_plugins = sorted(all_plugins.items(), key=lambda x: x[1].name if hasattr(x[1], 'name') else str(x[0])) + + for plugin_id, plugin_class in sorted_plugins: + row = QHBoxLayout() + row.setSpacing(12) + + # Enable/Disable checkbox + cb = QCheckBox() + try: + is_enabled = self.plugin_manager.is_plugin_enabled(plugin_id) + except: + is_enabled = True + cb.setChecked(is_enabled) + cb.stateChanged.connect(lambda state, pid=plugin_id: self._toggle_plugin(pid, state)) + row.addWidget(cb) + + # Plugin info + info_layout = QVBoxLayout() + info_layout.setSpacing(2) + + name_layout = QHBoxLayout() + + # Name + name = QLabel(getattr(plugin_class, 'name', str(plugin_id))) + name.setStyleSheet("color: white; font-size: 14px; font-weight: 500;") + name_layout.addWidget(name) + + # Version + version = getattr(plugin_class, 'version', '?.?.?') + version_label = QLabel(f"v{version}") + version_label.setStyleSheet("color: rgba(255,255,255,100); font-size: 11px;") + name_layout.addWidget(version_label) + name_layout.addStretch() + + info_layout.addLayout(name_layout) + + # Description + desc = getattr(plugin_class, 'description', 'No description available') + desc_label = QLabel(desc) + desc_label.setStyleSheet("color: rgba(255,255,255,120); font-size: 12px;") + desc_label.setWordWrap(True) + info_layout.addWidget(desc_label) + + row.addLayout(info_layout, 1) + + self.installed_layout.addLayout(row) + + # Separator + sep = QFrame() + sep.setFrameShape(QFrame.Shape.HLine) + sep.setStyleSheet("background-color: rgba(255, 140, 66, 0.1);") + sep.setFixedHeight(1) + self.installed_layout.addWidget(sep) + + self.installed_layout.addStretch() + + def _toggle_plugin(self, plugin_id: str, state: int): + """Enable or disable a plugin.""" + if not self.plugin_manager: + return + + try: + if state == Qt.CheckState.Checked.value: + self.plugin_manager.enable_plugin(plugin_id) + print(f"[PluginsView] Enabled plugin: {plugin_id}") + else: + self.plugin_manager.disable_plugin(plugin_id) + print(f"[PluginsView] Disabled plugin: {plugin_id}") + except Exception as e: + print(f"[PluginsView] Error toggling plugin {plugin_id}: {e}") + + def _create_store_tab(self): + """Create plugin store tab.""" + try: + from core.plugin_store import PluginStoreUI + + if self.plugin_manager: + return PluginStoreUI(self.plugin_manager) + except ImportError: + pass + + # Fallback + tab = QWidget() + layout = QVBoxLayout(tab) + + info = QLabel("Plugin Store") + info.setStyleSheet("font-size: 18px; font-weight: bold; color: white;") + layout.addWidget(info) + + desc = QLabel("Browse and install plugins from the community repository.") + desc.setStyleSheet("color: rgba(255,255,255,150);") + desc.setWordWrap(True) + layout.addWidget(desc) + + # Placeholder + placeholder = QLabel("Plugin Store interface will appear here.") + placeholder.setStyleSheet("color: rgba(255,255,255,100); padding: 40px;") + placeholder.setAlignment(Qt.AlignmentFlag.AlignCenter) + layout.addWidget(placeholder) + + layout.addStretch() + + return tab diff --git a/core/ui/settings_view.py b/core/ui/settings_view.py index 8ba88f6..f20b375 100644 --- a/core/ui/settings_view.py +++ b/core/ui/settings_view.py @@ -49,7 +49,7 @@ class SettingsView(QWidget): layout.addLayout(header_layout) - # Tabs + # Tabs - removed Plugin Store and My Plugins (moved to Plugins page) self.tabs = QTabWidget() self.tabs.setStyleSheet(""" QTabBar::tab { @@ -66,10 +66,8 @@ class SettingsView(QWidget): } """) - # Add tabs + # Add tabs - Plugin Store and My Plugins moved to Plugins page self.tabs.addTab(self._create_general_tab(), "General") - self.tabs.addTab(self._create_plugin_store_tab(), "Plugin Store") - self.tabs.addTab(self._create_plugins_tab(), "My Plugins") self.tabs.addTab(self._create_hotkeys_tab(), "Hotkeys") self.tabs.addTab(self._create_data_tab(), "Data & Backup") self.tabs.addTab(self._create_updates_tab(), "Updates") @@ -96,9 +94,9 @@ class SettingsView(QWidget): theme_layout.addStretch() appear_layout.addLayout(theme_layout) - # Opacity + # Opacity - changed from "Overlay" to "Background" opacity_layout = QHBoxLayout() - opacity_layout.addWidget(QLabel("Overlay Opacity:")) + opacity_layout.addWidget(QLabel("Background Opacity:")) self.opacity_slider = QSlider(Qt.Orientation.Horizontal) self.opacity_slider.setMinimum(50) self.opacity_slider.setMaximum(100) diff --git a/core/ui/widgets_view.py b/core/ui/widgets_view.py new file mode 100644 index 0000000..851c2d7 --- /dev/null +++ b/core/ui/widgets_view.py @@ -0,0 +1,210 @@ +""" +EU-Utility - Widgets View (Core Framework Component) + +Shows registered widgets from plugins. +""" + +from PyQt6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QPushButton, QFrame, QScrollArea, QGridLayout +) +from PyQt6.QtCore import Qt + +from core.icon_manager import get_icon_manager + + +class WidgetsView(QWidget): + """Widgets management interface - shows registered widgets from plugins.""" + + def __init__(self, overlay_window, parent=None): + super().__init__(parent) + self.overlay = overlay_window + self.plugin_manager = overlay_window.plugin_manager if hasattr(overlay_window, 'plugin_manager') else None + self.icon_manager = get_icon_manager() + + self._setup_ui() + + def _setup_ui(self): + """Create the widgets UI.""" + layout = QVBoxLayout(self) + layout.setSpacing(16) + layout.setContentsMargins(24, 24, 24, 24) + + # Header with icon + header_layout = QHBoxLayout() + header_layout.setSpacing(12) + + header_icon = QLabel() + header_pixmap = self.icon_manager.get_pixmap("widgets", size=28) + header_icon.setPixmap(header_pixmap) + header_layout.addWidget(header_icon) + + header = QLabel("Widgets") + header.setStyleSheet("font-size: 24px; font-weight: bold; color: white;") + header_layout.addWidget(header) + header_layout.addStretch() + + layout.addLayout(header_layout) + + # Info label + info = QLabel("Manage overlay widgets for in-game use. Enable widgets to show them on the activity bar.") + info.setStyleSheet("color: rgba(255,255,255,150); font-size: 13px;") + info.setWordWrap(True) + layout.addWidget(info) + + # Scroll area for widgets + scroll = QScrollArea() + scroll.setWidgetResizable(True) + scroll.setStyleSheet("background: transparent; border: none;") + scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) + + self.widgets_container = QWidget() + self.widgets_layout = QVBoxLayout(self.widgets_container) + self.widgets_layout.setAlignment(Qt.AlignmentFlag.AlignTop) + self.widgets_layout.setSpacing(12) + + self._populate_widgets() + + scroll.setWidget(self.widgets_container) + layout.addWidget(scroll) + + def _populate_widgets(self): + """Populate the widgets list from registered widgets.""" + # Clear existing + while self.widgets_layout.count(): + item = self.widgets_layout.takeAt(0) + if item.widget(): + item.widget().deleteLater() + + # Get widgets from widget registry + widgets = [] + try: + from core.widget_registry import get_widget_registry + registry = get_widget_registry() + widgets = registry.get_all_widgets() + except ImportError: + pass + + # Also check plugins for widgets + plugin_widgets = [] + if self.plugin_manager: + try: + all_plugins = self.plugin_manager.get_all_plugins() + for plugin in all_plugins: + if hasattr(plugin, 'get_widgets'): + plugin_widgets.extend(plugin.get_widgets()) + except: + pass + + # Hardcoded widgets for now until registry is fully implemented + builtin_widgets = [ + { + 'id': 'clock', + 'name': 'Clock Widget', + 'description': 'Digital clock with date display for activity bar', + 'plugin': 'Clock Widget', + 'enabled': True + }, + { + 'id': 'system_monitor', + 'name': 'System Monitor', + 'description': 'CPU and RAM usage display', + 'plugin': 'System Tools', + 'enabled': False + }, + { + 'id': 'skill_tracker', + 'name': 'Skill Tracker Mini', + 'description': 'Quick view of skill gains', + 'plugin': 'Skill Scanner', + 'enabled': False + }, + ] + + all_widgets = widgets + plugin_widgets + builtin_widgets + + if not all_widgets: + no_widgets = QLabel("No widgets available.\n\nInstall plugins that provide widgets to see them here.") + no_widgets.setStyleSheet("color: rgba(255,255,255,100); padding: 40px;") + no_widgets.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.widgets_layout.addWidget(no_widgets) + return + + for widget_info in all_widgets: + widget_card = self._create_widget_card(widget_info) + self.widgets_layout.addWidget(widget_card) + + self.widgets_layout.addStretch() + + def _create_widget_card(self, widget_info) -> QFrame: + """Create a widget card.""" + card = QFrame() + card.setStyleSheet(""" + QFrame { + background: rgba(255, 255, 255, 0.03); + border-radius: 12px; + padding: 4px; + } + QFrame:hover { + background: rgba(255, 255, 255, 0.05); + } + """) + + layout = QHBoxLayout(card) + layout.setContentsMargins(16, 12, 16, 12) + layout.setSpacing(12) + + # Enable checkbox + enabled = widget_info.get('enabled', False) + cb = QCheckBox() + cb.setChecked(enabled) + cb.setToolTip("Enable/disable widget on activity bar") + layout.addWidget(cb) + + # Widget info + info_layout = QVBoxLayout() + info_layout.setSpacing(4) + + # Name and plugin + name_row = QHBoxLayout() + + name = QLabel(widget_info.get('name', 'Unknown Widget')) + name.setStyleSheet("color: white; font-size: 14px; font-weight: 500;") + name_row.addWidget(name) + + plugin = widget_info.get('plugin', '') + if plugin: + plugin_label = QLabel(f"via {plugin}") + plugin_label.setStyleSheet("color: rgba(255,140,66,0.8); font-size: 11px;") + name_row.addWidget(plugin_label) + + name_row.addStretch() + info_layout.addLayout(name_row) + + # Description + desc = widget_info.get('description', 'No description') + desc_label = QLabel(desc) + desc_label.setStyleSheet("color: rgba(255,255,255,120); font-size: 12px;") + desc_label.setWordWrap(True) + info_layout.addWidget(desc_label) + + layout.addLayout(info_layout, 1) + + # Preview button + preview_btn = QPushButton("Preview") + preview_btn.setStyleSheet(""" + QPushButton { + background: rgba(255, 140, 66, 0.2); + color: #ff8c42; + border: 1px solid rgba(255, 140, 66, 0.3); + border-radius: 6px; + padding: 6px 12px; + font-size: 11px; + } + QPushButton:hover { + background: rgba(255, 140, 66, 0.3); + } + """) + layout.addWidget(preview_btn) + + return card