feat: Blish HUD-style overlay mode - Activity Bar only in EU game window
Changes: - Default mode now OVERLAY_GAME_FOCUSED (Blish HUD style) - Activity bar ONLY shows when EU window is focused - Completely hidden on desktop and other windows - Non-blocking focus detection with QTimer.singleShot - Auto-disables if window detection is too slow (>500ms) - Added Overlay Mode dropdown in Settings > General - Options: Game Focused, Always Visible, Hotkey Toggle, Temporary, Desktop Only Settings: - activity_bar.overlay_mode: 'overlay_game' (default) - Game Focused: Overlay strictly tied to EU window focus
This commit is contained in:
parent
3bf2d9d5b1
commit
0f1c41b58d
11
core/main.py
11
core/main.py
|
|
@ -183,19 +183,22 @@ class EUUtilityApp:
|
||||||
getattr(self, 'window_manager', None)
|
getattr(self, 'window_manager', None)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set mode from settings (default: hotkey toggle)
|
# Set mode from settings (default: game focused - Blish HUD style)
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
mode_str = settings.get('activity_bar.overlay_mode', 'overlay_toggle')
|
mode_str = settings.get('activity_bar.overlay_mode', 'overlay_game')
|
||||||
try:
|
try:
|
||||||
mode = OverlayMode(mode_str)
|
mode = OverlayMode(mode_str)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
mode = OverlayMode.OVERLAY_HOTKEY_TOGGLE
|
mode = OverlayMode.OVERLAY_GAME_FOCUSED # Default to game focused
|
||||||
|
|
||||||
self.overlay_controller.set_mode(mode)
|
self.overlay_controller.set_mode(mode)
|
||||||
self.overlay_controller.start()
|
self.overlay_controller.start()
|
||||||
|
|
||||||
print(f"[Core] Activity Bar created (mode: {mode.value})")
|
print(f"[Core] Activity Bar created (mode: {mode.value})")
|
||||||
print("[Core] Press Ctrl+Shift+B to toggle overlay")
|
|
||||||
|
if mode.value == 'overlay_game':
|
||||||
|
print("[Core] Overlay will ONLY show when EU game window is focused (Blish HUD style)")
|
||||||
|
print("[Core] Overlay is hidden on desktop - switch to EU to see it")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print("[Core] Activity Bar not available")
|
print("[Core] Activity Bar not available")
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,10 @@ class OverlayMode(Enum):
|
||||||
@dataclass
|
@dataclass
|
||||||
class OverlayConfig:
|
class OverlayConfig:
|
||||||
"""Configuration for activity bar overlay behavior."""
|
"""Configuration for activity bar overlay behavior."""
|
||||||
mode: OverlayMode = OverlayMode.OVERLAY_HOTKEY_TOGGLE
|
# Default to game-focused mode (Blish HUD style)
|
||||||
|
mode: OverlayMode = OverlayMode.OVERLAY_GAME_FOCUSED
|
||||||
temporary_duration: int = 8000 # milliseconds (8 seconds default)
|
temporary_duration: int = 8000 # milliseconds (8 seconds default)
|
||||||
game_focus_poll_interval: int = 2000 # ms (2 seconds)
|
game_focus_poll_interval: int = 1000 # ms (1 second - faster response)
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {
|
return {
|
||||||
|
|
@ -162,29 +163,78 @@ class OverlayController:
|
||||||
self.visibility_changed.emit(False)
|
self.visibility_changed.emit(False)
|
||||||
|
|
||||||
def _check_game_focus(self):
|
def _check_game_focus(self):
|
||||||
"""Check if EU game window is focused."""
|
"""Check if EU game window is focused - non-blocking using QTimer.singleShot."""
|
||||||
|
# Run the actual check in next event loop iteration to not block
|
||||||
|
from PyQt6.QtCore import QTimer
|
||||||
|
QTimer.singleShot(0, self._do_check_game_focus)
|
||||||
|
|
||||||
|
def _do_check_game_focus(self):
|
||||||
|
"""Actual focus check (non-blocking)."""
|
||||||
if not self.window_manager:
|
if not self.window_manager:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Track time for debugging
|
||||||
|
import time
|
||||||
|
start = time.perf_counter()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Quick check without blocking
|
# Set a flag to prevent re-entrancy
|
||||||
eu_window = self.window_manager.find_eu_window()
|
if getattr(self, '_checking_focus', False):
|
||||||
|
return
|
||||||
|
self._checking_focus = True
|
||||||
|
|
||||||
|
# Quick check - limit window enumeration time
|
||||||
|
eu_window = self._quick_find_eu_window()
|
||||||
|
|
||||||
if eu_window:
|
if eu_window:
|
||||||
is_focused = eu_window.is_focused
|
is_focused = eu_window.is_focused
|
||||||
if is_focused != self._game_focused:
|
if is_focused != self._game_focused:
|
||||||
self._game_focused = is_focused
|
self._game_focused = is_focused
|
||||||
if is_focused:
|
if is_focused:
|
||||||
|
print("[OverlayController] EU focused - showing overlay")
|
||||||
self._show()
|
self._show()
|
||||||
else:
|
else:
|
||||||
|
print("[OverlayController] EU unfocused - hiding overlay")
|
||||||
self._hide()
|
self._hide()
|
||||||
else:
|
else:
|
||||||
# No EU window - hide if visible
|
# No EU window found - hide if visible
|
||||||
if self._is_visible:
|
if self._is_visible:
|
||||||
|
print("[OverlayController] EU window not found - hiding overlay")
|
||||||
self._hide()
|
self._hide()
|
||||||
self._game_focused = False
|
self._game_focused = False
|
||||||
except Exception:
|
|
||||||
# Silently ignore errors
|
# Log if slow
|
||||||
pass
|
elapsed = (time.perf_counter() - start) * 1000
|
||||||
|
if elapsed > 100:
|
||||||
|
print(f"[OverlayController] Warning: Focus check took {elapsed:.1f}ms")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[OverlayController] Error checking focus: {e}")
|
||||||
|
finally:
|
||||||
|
self._checking_focus = False
|
||||||
|
|
||||||
|
def _quick_find_eu_window(self):
|
||||||
|
"""Quick window find with timeout protection."""
|
||||||
|
import time
|
||||||
|
start = time.perf_counter()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Try window manager first
|
||||||
|
if hasattr(self.window_manager, 'find_eu_window'):
|
||||||
|
# Wrap in timeout check
|
||||||
|
result = self.window_manager.find_eu_window()
|
||||||
|
|
||||||
|
# If taking too long, skip future checks
|
||||||
|
elapsed = (time.perf_counter() - start) * 1000
|
||||||
|
if elapsed > 500: # More than 500ms is too slow
|
||||||
|
print(f"[OverlayController] Window find too slow ({elapsed:.1f}ms), disabling focus detection")
|
||||||
|
self._game_focus_timer.stop()
|
||||||
|
return None
|
||||||
|
|
||||||
|
return result
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[OverlayController] Window find error: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
# Singleton instance
|
# Singleton instance
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,37 @@ class SettingsView(QWidget):
|
||||||
self.minimize_cb = QCheckBox("Minimize to tray on close")
|
self.minimize_cb = QCheckBox("Minimize to tray on close")
|
||||||
behavior_layout.addWidget(self.minimize_cb)
|
behavior_layout.addWidget(self.minimize_cb)
|
||||||
|
|
||||||
|
# Activity Bar Overlay Mode
|
||||||
|
overlay_group = QGroupBox("Activity Bar Overlay Mode")
|
||||||
|
overlay_group.setStyleSheet(self._group_style())
|
||||||
|
overlay_layout = QVBoxLayout(overlay_group)
|
||||||
|
|
||||||
|
overlay_info = QLabel("Choose how the in-game activity bar behaves:")
|
||||||
|
overlay_info.setStyleSheet("color: rgba(255,255,255,150); font-size: 12px;")
|
||||||
|
overlay_layout.addWidget(overlay_info)
|
||||||
|
|
||||||
|
self.overlay_mode_combo = QComboBox()
|
||||||
|
self.overlay_mode_combo.addItems([
|
||||||
|
"Game Focused Only (Blish HUD style)",
|
||||||
|
"Always Visible",
|
||||||
|
"Hotkey Toggle",
|
||||||
|
"Temporary (8 seconds)",
|
||||||
|
"Desktop App Only"
|
||||||
|
])
|
||||||
|
self.overlay_mode_combo.setCurrentIndex(0) # Default to game focused
|
||||||
|
self.overlay_mode_combo.currentIndexChanged.connect(self._on_overlay_mode_changed)
|
||||||
|
overlay_layout.addWidget(self.overlay_mode_combo)
|
||||||
|
|
||||||
|
overlay_desc = QLabel(
|
||||||
|
"Game Focused: Activity bar only appears when EU window is active. "
|
||||||
|
"Hidden on desktop."
|
||||||
|
)
|
||||||
|
overlay_desc.setStyleSheet("color: rgba(255,255,255,100); font-size: 11px;")
|
||||||
|
overlay_desc.setWordWrap(True)
|
||||||
|
overlay_layout.addWidget(overlay_desc)
|
||||||
|
|
||||||
|
behavior_layout.addWidget(overlay_group)
|
||||||
|
|
||||||
layout.addWidget(behavior_group)
|
layout.addWidget(behavior_group)
|
||||||
layout.addStretch()
|
layout.addStretch()
|
||||||
|
|
||||||
|
|
@ -334,6 +365,33 @@ class SettingsView(QWidget):
|
||||||
|
|
||||||
return tab
|
return tab
|
||||||
|
|
||||||
|
def _on_overlay_mode_changed(self, index: int):
|
||||||
|
"""Handle overlay mode change."""
|
||||||
|
mode_map = {
|
||||||
|
0: "overlay_game", # Game Focused Only
|
||||||
|
1: "overlay_always", # Always Visible
|
||||||
|
2: "overlay_toggle", # Hotkey Toggle
|
||||||
|
3: "overlay_temp", # Temporary
|
||||||
|
4: "desktop_app" # Desktop App Only
|
||||||
|
}
|
||||||
|
|
||||||
|
mode = mode_map.get(index, "overlay_game")
|
||||||
|
|
||||||
|
# Save to settings
|
||||||
|
if hasattr(self.overlay, 'settings'):
|
||||||
|
self.overlay.settings['activity_bar.overlay_mode'] = mode
|
||||||
|
self.overlay.settings.save()
|
||||||
|
|
||||||
|
print(f"[Settings] Activity Bar mode changed to: {mode}")
|
||||||
|
|
||||||
|
# Show confirmation
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"Mode Changed",
|
||||||
|
f"Activity Bar mode changed to: {self.overlay_mode_combo.currentText()}\n\n"
|
||||||
|
"Restart EU-Utility for changes to take effect."
|
||||||
|
)
|
||||||
|
|
||||||
def _export_data(self):
|
def _export_data(self):
|
||||||
"""Export all data."""
|
"""Export all data."""
|
||||||
path, _ = QFileDialog.getSaveFileName(
|
path, _ = QFileDialog.getSaveFileName(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue