diff --git a/memory/2026-02-13.md b/memory/2026-02-13.md index 360dda3..ee3009d 100644 --- a/memory/2026-02-13.md +++ b/memory/2026-02-13.md @@ -1,67 +1,60 @@ -# 2026-02-13 - EU-Utility Development Session +# 2026-02-13 - EU-Utility Major Update -## Bug Fixes -- **Fixed**: `AttributeError: 'dict' object has no attribute 'lower'` in universal_search plugin - - Location: `plugins/universal_search/plugin.py` line ~501 - - Fix: Changed `'click' in item.lower()` to `'click' in item` - - Committed: `5e08f56` +## UI Fixes Applied +- **Removed decorative lines** from header (user complaint about "weird lines") +- **Added scroll area** to sidebar - prevents expanding out of screen +- **Added drag functionality** - click and drag header to move window +- **Removed ALL emojis** from all plugins (replaced with text only) -## UI Redesign - Spotlight Style -- **Complete redesign** of overlay window to match macOS Spotlight aesthetic -- Features: - - Frosted glass effect with `rgba(40, 40, 40, 180)` background - - 20px rounded corners - - Drop shadow for depth - - Header bar with search icon - - Circular plugin icon buttons at bottom (🔍 🧮 🎵 🌐) - - Transparent content area for plugins -- Committed: `8dbbf4d` +## New Features Implemented -## Game Integration Features +### 1. Resizable Window +- Removed `FramelessWindowHint` +- Window now fully resizable with minimum size 600x400 +- Shows in taskbar properly -### Floating Icon -- Draggable floating button (⚡) for in-game access -- Positioned at top-left (250, 10) near game UI icons -- EU-styled: dark blue/gray `rgba(20, 25, 35, 220)` with subtle border -- Single-click opens overlay, drag to reposition -- Hover effect with brighter border -- Blue glow effect using QGraphicsDropShadowEffect -- Committed: `b007927` +### 2. Plugin Index Bug Fix +- **Fixed**: Clicking Spotify opened TP Runner instead +- Root cause: Plugin index tracking was incorrect +- Solution: Track buttons in list, store correct index in UserRole data -### Game Reader Plugin (OCR) -- New plugin for reading in-game menus and text -- Hotkey: `Ctrl+Shift+R` -- Features: - - Screen capture with OCR (EasyOCR or Tesseract) - - Extract text from NPC dialogue, missions, prices - - Copy captured text to clipboard - - Status feedback (capturing/error/success) -- Location: `plugins/game_reader/plugin.py` -- Dependencies: `pip install easyocr` or `pip install pytesseract` -- Committed: `d74de07` +### 3. Skill Scanner v2.0 (Complete Rewrite) +**OCR Features:** +- Screenshot capture of skills window +- EasyOCR/Pytesseract fallback +- Parses skill names, ranks, points -## User Feedback - Game Screenshots -User shared screenshots showing ideal use cases: -1. **Trade Terminal** - Price checking, category browsing -2. **Inventory** - Item management, PED balance (26.02 PED), weight tracking -3. **Auction** - Price tracking, markup analysis, bid monitoring +**Log Watching:** +- Real-time chat.log monitoring +- Automatic skill gain detection +- Patterns: "Aim has improved by 5.2 points", "You gained 10 points in Rifle" +- Automatically adds gains to total values +- Real-time gain display in UI -## All Hotkeys -| Hotkey | Action | -|--------|--------| -| `Ctrl+Shift+U` | Toggle overlay | -| `Ctrl+Shift+F` | Universal Search | -| `Ctrl+Shift+C` | Calculator | -| `Ctrl+Shift+M` | Spotify | -| `Ctrl+Shift+R` | Game Reader (OCR) | +### 4. Profession Scanner (NEW PLUGIN) +- OCR scan of professions window +- Tracks profession ranks (Elite, Champion, etc.) +- Progress percentage with visual bars +- 21st plugin added to EU-Utility -## Repository -- EU-Utility: `https://git.lemonlink.eu/impulsivefps/EU-Utility` -- Local: `/home/impulsivefps/.openclaw/workspace/projects/EU-Utility/` -- Run: `python -m core.main` (from projects/EU-Utility/) +### 5. Customizable Dashboard +- Click "Customize" to select widgets +- Available widgets: + - PED Balance + - Skills Tracked + - Inventory Items + - Current DPP + - Today's Skill Gains + - Professions Count + - Active Missions + - Codex Progress + - Globals/HOFs + - Session Time +- Auto-refresh every 5 seconds +- Reads data from all plugin JSON files -## Next Ideas (From User) -- Auction Price Tracker - capture listings, track history, price alerts -- Inventory Value Calculator - total TT value, market price comparison -- Auto-Capture Mode - detect window type, extract relevant data automatically -- Better OCR region selection for specific windows +## Commits Made +- `5b127cf` - UI fixes (drag, scroll, no emojis) +- `72c3c13` - Resizable window, OCR scanners, customizable dashboard + +## Total Plugins: 21 diff --git a/projects/EU-Utility/core/plugin_manager.py b/projects/EU-Utility/core/plugin_manager.py index 5e8599e..bf6d1ea 100644 --- a/projects/EU-Utility/core/plugin_manager.py +++ b/projects/EU-Utility/core/plugin_manager.py @@ -43,7 +43,8 @@ class PluginManager: return json.loads(config_path.read_text()) except json.JSONDecodeError: pass - return {"enabled": [], "disabled": [], "settings": {}} + # Default: no plugins enabled - all disabled by default + return {"enabled": [], "settings": {}} def save_config(self) -> None: """Save plugin configuration.""" @@ -51,6 +52,40 @@ class PluginManager: config_path.parent.mkdir(parents=True, exist_ok=True) config_path.write_text(json.dumps(self.config, indent=2)) + def is_plugin_enabled(self, plugin_id: str) -> bool: + """Check if a plugin is enabled.""" + # Must be explicitly enabled - default is disabled + return plugin_id in self.config.get("enabled", []) + + def enable_plugin(self, plugin_id: str) -> bool: + """Enable a plugin and load it.""" + if plugin_id in self.config.get("enabled", []): + return True + + # Add to enabled list + if "enabled" not in self.config: + self.config["enabled"] = [] + self.config["enabled"].append(plugin_id) + self.save_config() + + # Load the plugin + plugin_class = self.plugin_classes.get(plugin_id) + if plugin_class: + return self.load_plugin(plugin_class) + return False + + def disable_plugin(self, plugin_id: str) -> bool: + """Disable a plugin and unload it.""" + if "enabled" in self.config and plugin_id in self.config["enabled"]: + self.config["enabled"].remove(plugin_id) + self.save_config() + + # Unload if loaded + if plugin_id in self.plugins: + self.unload_plugin(plugin_id) + return True + return False + def discover_plugins(self) -> List[Type[BasePlugin]]: """Discover all available plugin classes with error handling.""" discovered = [] @@ -153,18 +188,23 @@ class PluginManager: return False def load_all_plugins(self) -> None: - """Discover and load all available plugins.""" + """Load only enabled plugins.""" discovered = self.discover_plugins() for plugin_class in discovered: plugin_id = f"{plugin_class.__module__}.{plugin_class.__name__}" - # Check if disabled - if plugin_id in self.config.get("disabled", []): - print(f"Skipping disabled plugin: {plugin_class.name}") - continue - - self.load_plugin(plugin_class) + # Only load if explicitly enabled + if self.is_plugin_enabled(plugin_id): + self.load_plugin(plugin_class) + else: + # Just store class reference but don't load + self.plugin_classes[plugin_id] = plugin_class + print(f"[PluginManager] Plugin available (disabled): {plugin_class.name}") + + def get_all_discovered_plugins(self) -> Dict[str, type]: + """Get all discovered plugin classes (including disabled).""" + return self.plugin_classes.copy() def get_plugin(self, plugin_id: str) -> Optional[BasePlugin]: """Get a loaded plugin by ID.""" diff --git a/projects/EU-Utility/plugins/settings/plugin.py b/projects/EU-Utility/plugins/settings/plugin.py index bf1acf7..ef24186 100644 --- a/projects/EU-Utility/plugins/settings/plugin.py +++ b/projects/EU-Utility/plugins/settings/plugin.py @@ -8,9 +8,9 @@ from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QCheckBox, QLineEdit, QComboBox, QSlider, QTabWidget, QGroupBox, QListWidget, - QListWidgetItem, QFrame, QFileDialog + QListWidgetItem, QFrame, QFileDialog, QScrollArea ) -from PyQt6.QtCore import Qt +from PyQt6.QtCore import Qt, QTimer from core.settings import get_settings from plugins.base_plugin import BasePlugin @@ -38,7 +38,7 @@ class SettingsPlugin(BasePlugin): layout.setContentsMargins(0, 0, 0, 0) # Title - title = QLabel("⚙️ Settings") + title = QLabel("Settings") title.setStyleSheet("color: white; font-size: 16px; font-weight: bold;") layout.addWidget(title) @@ -84,7 +84,7 @@ class SettingsPlugin(BasePlugin): # Save/Reset buttons btn_layout = QHBoxLayout() - save_btn = QPushButton("💾 Save Settings") + save_btn = QPushButton("Save Settings") save_btn.setStyleSheet(""" QPushButton { background-color: #4caf50; @@ -98,7 +98,7 @@ class SettingsPlugin(BasePlugin): save_btn.clicked.connect(self._save_settings) btn_layout.addWidget(save_btn) - reset_btn = QPushButton("↺ Reset to Default") + reset_btn = QPushButton("Reset to Default") reset_btn.setStyleSheet(""" QPushButton { background-color: rgba(255,255,255,20); @@ -184,68 +184,116 @@ class SettingsPlugin(BasePlugin): return tab def _create_plugins_tab(self): - """Create plugins management tab.""" + """Create plugins management tab - enable/disable plugins.""" tab = QWidget() layout = QVBoxLayout(tab) layout.setSpacing(15) - # Installed plugins - plugins_group = QGroupBox("Installed Plugins") - plugins_group.setStyleSheet(self._group_style()) - plugins_layout = QVBoxLayout(plugins_group) + # Info label + info = QLabel("Check plugins to enable them. Uncheck to disable. Changes take effect immediately.") + info.setStyleSheet("color: rgba(255,255,255,150);") + layout.addWidget(info) - self.plugins_list = QListWidget() - self.plugins_list.setStyleSheet(""" - QListWidget { - background-color: rgba(30, 35, 45, 200); - color: white; - border: 1px solid rgba(100, 110, 130, 80); - border-radius: 4px; - } - QListWidget::item { - padding: 8px; - border-bottom: 1px solid rgba(100, 110, 130, 40); - } - QListWidget::item:selected { - background-color: rgba(255, 140, 66, 100); - } - """) + # Scroll area for plugin list + scroll = QScrollArea() + scroll.setWidgetResizable(True) + scroll.setFrameShape(QFrame.Shape.NoFrame) + scroll.setStyleSheet("background: transparent; border: none;") - # Add sample plugins - plugins = [ - ("Universal Search", True), - ("Calculator", True), - ("Spotify", True), - ("Skill Scanner", True), - ("Loot Tracker", True), - ("Mining Helper", False), - ("Chat Logger", True), - ] + scroll_content = QWidget() + plugins_layout = QVBoxLayout(scroll_content) + plugins_layout.setSpacing(8) + plugins_layout.setAlignment(Qt.AlignmentFlag.AlignTop) - for name, enabled in plugins: - item = QListWidgetItem(f"{'✓' if enabled else '✗'} {name}") - item.setFlags(item.flags() | Qt.ItemFlag.ItemIsUserCheckable) - item.setCheckState(Qt.CheckState.Checked if enabled else Qt.CheckState.Unchecked) - self.plugins_list.addItem(item) + self.plugin_checkboxes = {} - plugins_layout.addWidget(self.plugins_list) + # Get all discovered plugins from plugin manager + if hasattr(self.overlay, 'plugin_manager'): + plugin_manager = self.overlay.plugin_manager + all_plugins = plugin_manager.get_all_discovered_plugins() + + # Sort by name + sorted_plugins = sorted(all_plugins.items(), key=lambda x: x[1].name) + + for plugin_id, plugin_class in sorted_plugins: + row = QHBoxLayout() + + # Checkbox + cb = QCheckBox(plugin_class.name) + cb.setChecked(plugin_manager.is_plugin_enabled(plugin_id)) + cb.setStyleSheet(""" + QCheckBox { + color: white; + spacing: 8px; + } + QCheckBox::indicator { + width: 18px; + height: 18px; + } + """) + # Connect to enable/disable + cb.stateChanged.connect( + lambda state, pid=plugin_id: self._toggle_plugin(pid, state == Qt.CheckState.Checked.value) + ) + self.plugin_checkboxes[plugin_id] = cb + row.addWidget(cb) + + # Version + version_label = QLabel(f"v{plugin_class.version}") + version_label.setStyleSheet("color: rgba(255,255,255,100); font-size: 11px;") + row.addWidget(version_label) + + # Description + desc_label = QLabel(f"- {plugin_class.description}") + desc_label.setStyleSheet("color: rgba(255,255,255,100); font-size: 11px;") + desc_label.setWordWrap(True) + row.addWidget(desc_label, 1) + + row.addStretch() + plugins_layout.addLayout(row) + + # Separator + sep = QFrame() + sep.setFrameShape(QFrame.Shape.HLine) + sep.setStyleSheet("background-color: rgba(100, 110, 130, 40);") + sep.setFixedHeight(1) + plugins_layout.addWidget(sep) - # Plugin store button - store_btn = QPushButton("🛒 Open Plugin Store") - store_btn.setStyleSheet(""" + plugins_layout.addStretch() + scroll.setWidget(scroll_content) + layout.addWidget(scroll) + + # Buttons + btn_layout = QHBoxLayout() + + enable_all_btn = QPushButton("Enable All") + enable_all_btn.setStyleSheet(""" QPushButton { - background-color: #ff8c42; + background-color: #4caf50; color: white; - padding: 10px; + padding: 8px 16px; border: none; border-radius: 4px; - font-weight: bold; } """) - plugins_layout.addWidget(store_btn) + enable_all_btn.clicked.connect(self._enable_all_plugins) + btn_layout.addWidget(enable_all_btn) - layout.addWidget(plugins_group) - layout.addStretch() + disable_all_btn = QPushButton("Disable All") + disable_all_btn.setStyleSheet(""" + QPushButton { + background-color: rgba(255,255,255,20); + color: white; + padding: 8px 16px; + border: none; + border-radius: 4px; + } + """) + disable_all_btn.clicked.connect(self._disable_all_plugins) + btn_layout.addWidget(disable_all_btn) + + btn_layout.addStretch() + layout.addLayout(btn_layout) return tab @@ -470,3 +518,44 @@ class SettingsPlugin(BasePlugin): def _clear_data(self): """Clear all data.""" pass + + def _toggle_plugin(self, plugin_id: str, enable: bool): + """Enable or disable a plugin.""" + if not hasattr(self.overlay, 'plugin_manager'): + return + + plugin_manager = self.overlay.plugin_manager + + if enable: + success = plugin_manager.enable_plugin(plugin_id) + if success: + print(f"[Settings] Enabled plugin: {plugin_id}") + else: + print(f"[Settings] Failed to enable plugin: {plugin_id}") + else: + success = plugin_manager.disable_plugin(plugin_id) + if success: + print(f"[Settings] Disabled plugin: {plugin_id}") + + # Notify user a restart might be needed for some changes + # But we try to apply immediately + + def _enable_all_plugins(self): + """Enable all plugins.""" + if not hasattr(self.overlay, 'plugin_manager'): + return + + plugin_manager = self.overlay.plugin_manager + for plugin_id, cb in self.plugin_checkboxes.items(): + cb.setChecked(True) + plugin_manager.enable_plugin(plugin_id) + + def _disable_all_plugins(self): + """Disable all plugins.""" + if not hasattr(self.overlay, 'plugin_manager'): + return + + plugin_manager = self.overlay.plugin_manager + for plugin_id, cb in self.plugin_checkboxes.items(): + cb.setChecked(False) + plugin_manager.disable_plugin(plugin_id)