785 lines
28 KiB
Python
785 lines
28 KiB
Python
"""
|
|
EU-Utility - Windows Taskbar Style Activity Bar
|
|
===============================================
|
|
|
|
Windows 11-style taskbar for in-game overlay.
|
|
Features: Transparent background, search box, pinned plugins,
|
|
clean minimal design that expands as needed.
|
|
"""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional, Callable
|
|
from dataclasses import dataclass, asdict
|
|
|
|
from PyQt6.QtWidgets import (
|
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
|
|
QFrame, QLineEdit, QMenu, QDialog, QSlider, QComboBox,
|
|
QCheckBox, QSpinBox, QApplication, QSizePolicy
|
|
)
|
|
from PyQt6.QtCore import Qt, QPoint, QSize, QTimer, pyqtSignal, QPropertyAnimation, QEasingCurve
|
|
from PyQt6.QtGui import QMouseEvent, QPainter, QColor, QFont, QIcon, QPixmap
|
|
|
|
from core.icon_manager import get_icon_manager
|
|
|
|
|
|
@dataclass
|
|
class ActivityBarConfig:
|
|
"""Activity bar configuration."""
|
|
enabled: bool = True
|
|
position: str = "bottom" # top, bottom, custom
|
|
x: int = 100 # custom X position
|
|
y: int = 800 # custom Y position
|
|
width: int = 800 # custom width
|
|
icon_size: int = 32
|
|
auto_hide: bool = False # Disabled by default for easier use
|
|
auto_hide_delay: int = 3000 # milliseconds
|
|
auto_show_on_focus: bool = False
|
|
background_opacity: int = 90 # 0-100
|
|
show_search: bool = True
|
|
show_clock: bool = True
|
|
pinned_plugins: List[str] = None
|
|
|
|
def __post_init__(self):
|
|
if self.pinned_plugins is None:
|
|
self.pinned_plugins = []
|
|
|
|
def to_dict(self):
|
|
return {
|
|
'enabled': self.enabled,
|
|
'position': self.position,
|
|
'x': self.x,
|
|
'y': self.y,
|
|
'width': self.width,
|
|
'icon_size': self.icon_size,
|
|
'auto_hide': self.auto_hide,
|
|
'auto_hide_delay': self.auto_hide_delay,
|
|
'auto_show_on_focus': self.auto_show_on_focus,
|
|
'background_opacity': self.background_opacity,
|
|
'show_search': self.show_search,
|
|
'show_clock': self.show_clock,
|
|
'pinned_plugins': self.pinned_plugins
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, data):
|
|
return cls(
|
|
enabled=data.get('enabled', True),
|
|
position=data.get('position', 'bottom'),
|
|
x=data.get('x', 100),
|
|
y=data.get('y', 800),
|
|
width=data.get('width', 800),
|
|
icon_size=data.get('icon_size', 32),
|
|
auto_hide=data.get('auto_hide', False),
|
|
auto_hide_delay=data.get('auto_hide_delay', 3000),
|
|
auto_show_on_focus=data.get('auto_show_on_focus', False),
|
|
background_opacity=data.get('background_opacity', 90),
|
|
show_search=data.get('show_search', True),
|
|
show_clock=data.get('show_clock', True),
|
|
pinned_plugins=data.get('pinned_plugins', [])
|
|
)
|
|
|
|
|
|
class WindowsTaskbar(QFrame):
|
|
"""
|
|
Windows 11-style taskbar for in-game overlay.
|
|
|
|
Features:
|
|
- Transparent background (no background visible)
|
|
- Windows-style start button with proper icon
|
|
- Search box for quick access
|
|
- Pinned plugins expand the bar
|
|
- Clean, minimal design
|
|
"""
|
|
|
|
widget_requested = pyqtSignal(str)
|
|
search_requested = pyqtSignal(str)
|
|
|
|
def __init__(self, plugin_manager, parent=None):
|
|
super().__init__(parent)
|
|
|
|
self.plugin_manager = plugin_manager
|
|
self.icon_manager = get_icon_manager()
|
|
self.config = self._load_config()
|
|
|
|
# State
|
|
self.pinned_buttons: Dict[str, QPushButton] = {}
|
|
self.is_expanded = False
|
|
self.search_text = ""
|
|
|
|
# Auto-hide timer (must be created before _apply_config)
|
|
self.hide_timer = QTimer(self)
|
|
self.hide_timer.timeout.connect(self._auto_hide)
|
|
|
|
self._setup_window()
|
|
self._setup_ui()
|
|
self._apply_config()
|
|
|
|
# Show if enabled
|
|
if self.config.enabled:
|
|
self.show()
|
|
|
|
def _setup_window(self):
|
|
"""Setup frameless overlay window."""
|
|
self.setWindowFlags(
|
|
Qt.WindowType.FramelessWindowHint |
|
|
Qt.WindowType.WindowStaysOnTopHint |
|
|
Qt.WindowType.Tool |
|
|
Qt.WindowType.WindowDoesNotAcceptFocus
|
|
)
|
|
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
|
|
|
# Draggable state
|
|
self._dragging = False
|
|
self._drag_offset = QPoint()
|
|
|
|
def _setup_ui(self):
|
|
"""Setup Windows taskbar-style UI with draggable and resizable features."""
|
|
# Main layout with minimal margins
|
|
self.main_layout = QHBoxLayout(self)
|
|
self.main_layout.setContentsMargins(8, 4, 8, 4)
|
|
self.main_layout.setSpacing(4)
|
|
|
|
# Apply opacity from config
|
|
self._apply_opacity()
|
|
|
|
# === DRAG HANDLE (invisible, left side) ===
|
|
self.drag_handle = QFrame()
|
|
self.drag_handle.setFixedSize(8, 40)
|
|
self.drag_handle.setStyleSheet("background: transparent; cursor: move;")
|
|
self.drag_handle.setToolTip("Drag to move")
|
|
self.main_layout.addWidget(self.drag_handle)
|
|
|
|
# === START BUTTON (Windows-style icon) ===
|
|
self.start_btn = QPushButton()
|
|
self.start_btn.setFixedSize(40, 40)
|
|
self.start_btn.setIcon(self.icon_manager.get_icon("grid"))
|
|
self.start_btn.setIconSize(QSize(20, 20))
|
|
self.start_btn.setStyleSheet("""
|
|
QPushButton {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
color: white;
|
|
border: none;
|
|
border-radius: 8px;
|
|
}
|
|
QPushButton:hover {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
}
|
|
QPushButton:pressed {
|
|
background: rgba(255, 255, 255, 0.15);
|
|
}
|
|
""")
|
|
self.start_btn.setToolTip("Open App Drawer")
|
|
self.start_btn.clicked.connect(self._toggle_drawer)
|
|
self.main_layout.addWidget(self.start_btn)
|
|
|
|
# === SEARCH BOX (Windows 11 style) ===
|
|
self.search_box = QLineEdit()
|
|
self.search_box.setFixedSize(200, 36)
|
|
self.search_box.setPlaceholderText("Search plugins...")
|
|
self.search_box.setStyleSheet("""
|
|
QLineEdit {
|
|
background: rgba(255, 255, 255, 0.08);
|
|
color: white;
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
border-radius: 18px;
|
|
padding: 0 16px;
|
|
font-size: 13px;
|
|
}
|
|
QLineEdit:hover {
|
|
background: rgba(255, 255, 255, 0.12);
|
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
}
|
|
QLineEdit:focus {
|
|
background: rgba(255, 255, 255, 0.15);
|
|
border: 1px solid rgba(255, 140, 66, 0.5);
|
|
}
|
|
QLineEdit::placeholder {
|
|
color: rgba(255, 255, 255, 0.4);
|
|
}
|
|
""")
|
|
self.search_box.returnPressed.connect(self._on_search)
|
|
self.search_box.textChanged.connect(self._on_search_text_changed)
|
|
self.main_layout.addWidget(self.search_box)
|
|
|
|
# Search visibility
|
|
self.search_box.setVisible(self.config.show_search)
|
|
|
|
# Separator
|
|
separator = QFrame()
|
|
separator.setFixedSize(1, 24)
|
|
separator.setStyleSheet("background: rgba(255, 255, 255, 0.1);")
|
|
self.main_layout.addWidget(separator)
|
|
|
|
# === PINNED PLUGINS AREA (expandable) ===
|
|
self.pinned_container = QWidget()
|
|
self.pinned_layout = QHBoxLayout(self.pinned_container)
|
|
self.pinned_layout.setContentsMargins(0, 0, 0, 0)
|
|
self.pinned_layout.setSpacing(4)
|
|
self.main_layout.addWidget(self.pinned_container)
|
|
|
|
# Spacer to push icons together
|
|
self.main_layout.addStretch()
|
|
|
|
# === CLOCK AREA ===
|
|
self.clock_widget = QWidget()
|
|
clock_layout = QHBoxLayout(self.clock_widget)
|
|
clock_layout.setContentsMargins(0, 0, 0, 0)
|
|
clock_layout.setSpacing(4)
|
|
|
|
# Clock icon
|
|
self.clock_icon = QLabel()
|
|
clock_pixmap = self.icon_manager.get_pixmap("clock", size=14)
|
|
self.clock_icon.setPixmap(clock_pixmap)
|
|
self.clock_icon.setStyleSheet("padding-right: 4px;")
|
|
clock_layout.addWidget(self.clock_icon)
|
|
|
|
# Clock time
|
|
self.clock_label = QLabel("12:00")
|
|
self.clock_label.setStyleSheet("""
|
|
color: rgba(255, 255, 255, 0.7);
|
|
font-size: 12px;
|
|
padding: 0 8px;
|
|
""")
|
|
self.clock_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
clock_layout.addWidget(self.clock_label)
|
|
|
|
self.main_layout.addWidget(self.clock_widget)
|
|
|
|
# Clock visibility
|
|
self.clock_widget.setVisible(self.config.show_clock)
|
|
|
|
# === RESIZE HANDLE (right side) ===
|
|
self.resize_handle = QFrame()
|
|
self.resize_handle.setFixedSize(8, 40)
|
|
self.resize_handle.setStyleSheet("background: transparent; cursor: size-hor-cursor;")
|
|
self.resize_handle.setToolTip("Drag to resize")
|
|
self.main_layout.addWidget(self.resize_handle)
|
|
|
|
# Start clock update timer
|
|
self.clock_timer = QTimer(self)
|
|
self.clock_timer.timeout.connect(self._update_clock)
|
|
self.clock_timer.start(60000) # Update every minute
|
|
self._update_clock()
|
|
|
|
# Setup context menu
|
|
self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
|
self.customContextMenuRequested.connect(self._show_context_menu)
|
|
|
|
# Refresh pinned plugins
|
|
self._refresh_pinned_plugins()
|
|
|
|
# Set initial size and position
|
|
self._apply_size_and_position()
|
|
|
|
def _create_plugin_button(self, plugin_id: str, plugin_class) -> QPushButton:
|
|
"""Create a pinned plugin button (taskbar icon style)."""
|
|
btn = QPushButton()
|
|
size = self.config.icon_size
|
|
btn.setFixedSize(size + 8, size + 8)
|
|
|
|
# Get plugin icon or use default
|
|
icon_name = getattr(plugin_class, 'icon_name', 'grid')
|
|
btn.setIcon(self.icon_manager.get_icon(icon_name))
|
|
btn.setIconSize(QSize(size - 8, size - 8))
|
|
|
|
btn.setStyleSheet(f"""
|
|
QPushButton {{
|
|
background: transparent;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 6px;
|
|
}}
|
|
QPushButton:hover {{
|
|
background: rgba(255, 255, 255, 0.1);
|
|
}}
|
|
QPushButton:pressed {{
|
|
background: rgba(255, 255, 255, 0.05);
|
|
}}
|
|
""")
|
|
|
|
btn.setToolTip(plugin_class.name)
|
|
btn.clicked.connect(lambda: self._on_plugin_clicked(plugin_id))
|
|
|
|
return btn
|
|
|
|
def _refresh_pinned_plugins(self):
|
|
"""Refresh pinned plugin buttons."""
|
|
# Clear existing
|
|
for btn in self.pinned_buttons.values():
|
|
btn.deleteLater()
|
|
self.pinned_buttons.clear()
|
|
|
|
if not self.plugin_manager:
|
|
return
|
|
|
|
# Get all enabled plugins
|
|
all_plugins = self.plugin_manager.get_all_discovered_plugins()
|
|
|
|
# Add pinned plugins
|
|
for plugin_id in self.config.pinned_plugins:
|
|
if plugin_id in all_plugins:
|
|
plugin_class = all_plugins[plugin_id]
|
|
btn = self._create_plugin_button(plugin_id, plugin_class)
|
|
self.pinned_buttons[plugin_id] = btn
|
|
self.pinned_layout.addWidget(btn)
|
|
|
|
def _toggle_drawer(self):
|
|
"""Toggle the app drawer (like Windows Start menu)."""
|
|
# Create drawer if not exists
|
|
if not hasattr(self, 'drawer') or self.drawer is None:
|
|
self._create_drawer()
|
|
|
|
if self.drawer.isVisible():
|
|
self.drawer.hide()
|
|
else:
|
|
# Position above the taskbar
|
|
bar_pos = self.pos()
|
|
self.drawer.move(bar_pos.x(), bar_pos.y() - self.drawer.height())
|
|
self.drawer.show()
|
|
self.drawer.raise_()
|
|
|
|
def _create_drawer(self):
|
|
"""Create the app drawer popup."""
|
|
self.drawer = QFrame(self.parent())
|
|
self.drawer.setWindowFlags(
|
|
Qt.WindowType.FramelessWindowHint |
|
|
Qt.WindowType.WindowStaysOnTopHint |
|
|
Qt.WindowType.Tool
|
|
)
|
|
self.drawer.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
|
self.drawer.setFixedSize(400, 500)
|
|
|
|
# Drawer style: subtle frosted glass
|
|
self.drawer.setStyleSheet("""
|
|
QFrame {
|
|
background: rgba(20, 31, 35, 0.95);
|
|
border: 1px solid rgba(255, 140, 66, 0.15);
|
|
border-radius: 12px;
|
|
}
|
|
""")
|
|
|
|
layout = QVBoxLayout(self.drawer)
|
|
layout.setContentsMargins(16, 16, 16, 16)
|
|
layout.setSpacing(12)
|
|
|
|
# Header
|
|
header = QLabel("All Plugins")
|
|
header.setStyleSheet("color: white; font-size: 16px; font-weight: bold;")
|
|
layout.addWidget(header)
|
|
|
|
# Search in drawer
|
|
drawer_search = QLineEdit()
|
|
drawer_search.setPlaceholderText("Search...")
|
|
drawer_search.setStyleSheet("""
|
|
QLineEdit {
|
|
background: rgba(255, 255, 255, 0.08);
|
|
color: white;
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
border-radius: 8px;
|
|
padding: 8px 12px;
|
|
}
|
|
""")
|
|
layout.addWidget(drawer_search)
|
|
|
|
# Plugin grid
|
|
plugins_widget = QWidget()
|
|
plugins_layout = QVBoxLayout(plugins_widget)
|
|
plugins_layout.setSpacing(8)
|
|
|
|
if self.plugin_manager:
|
|
all_plugins = self.plugin_manager.get_all_discovered_plugins()
|
|
for plugin_id, plugin_class in all_plugins.items():
|
|
item = self._create_drawer_item(plugin_id, plugin_class)
|
|
plugins_layout.addWidget(item)
|
|
|
|
plugins_layout.addStretch()
|
|
layout.addWidget(plugins_widget)
|
|
|
|
def mousePressEvent(self, event: QMouseEvent):
|
|
"""Handle mouse press for dragging."""
|
|
if event.button() == Qt.MouseButton.LeftButton:
|
|
# Check if clicking on drag handle
|
|
if self.drag_handle.geometry().contains(event.pos()):
|
|
self._dragging = True
|
|
self._drag_offset = event.globalPosition().toPoint() - self.frameGeometry().topLeft()
|
|
event.accept()
|
|
# Check if clicking on resize handle
|
|
elif self.resize_handle.geometry().contains(event.pos()):
|
|
self._resizing = True
|
|
self._resize_start_x = event.globalPosition().x()
|
|
self._resize_start_width = self.width()
|
|
event.accept()
|
|
else:
|
|
super().mousePressEvent(event)
|
|
|
|
def mouseMoveEvent(self, event: QMouseEvent):
|
|
"""Handle mouse move for dragging and resizing."""
|
|
if self._dragging:
|
|
new_pos = event.globalPosition().toPoint() - self._drag_offset
|
|
self.move(new_pos)
|
|
# Save position
|
|
self.config.x = new_pos.x()
|
|
self.config.y = new_pos.y()
|
|
self._save_config()
|
|
event.accept()
|
|
elif getattr(self, '_resizing', False):
|
|
delta = event.globalPosition().x() - self._resize_start_x
|
|
new_width = max(400, self._resize_start_width + delta)
|
|
self.setFixedWidth(int(new_width))
|
|
self.config.width = int(new_width)
|
|
self._save_config()
|
|
event.accept()
|
|
else:
|
|
super().mouseMoveEvent(event)
|
|
|
|
def mouseReleaseEvent(self, event: QMouseEvent):
|
|
"""Handle mouse release."""
|
|
if event.button() == Qt.MouseButton.LeftButton:
|
|
self._dragging = False
|
|
self._resizing = False
|
|
super().mouseReleaseEvent(event)
|
|
|
|
def _apply_opacity(self):
|
|
"""Apply background opacity from config."""
|
|
opacity = self.config.background_opacity / 100.0
|
|
# Create a background widget with opacity
|
|
bg_color = f"rgba(20, 31, 35, {opacity})"
|
|
self.setStyleSheet(f"""
|
|
WindowsTaskbar {{
|
|
background: {bg_color};
|
|
border: 1px solid rgba(255, 140, 66, 0.1);
|
|
border-radius: 12px;
|
|
}}
|
|
""")
|
|
|
|
def _apply_size_and_position(self):
|
|
"""Apply saved size and position."""
|
|
self.setFixedHeight(56)
|
|
self.setFixedWidth(self.config.width)
|
|
|
|
if self.config.position == "custom":
|
|
self.move(self.config.x, self.config.y)
|
|
elif self.config.position == "bottom":
|
|
screen = QApplication.primaryScreen().geometry()
|
|
self.move(
|
|
(screen.width() - self.config.width) // 2,
|
|
screen.height() - 80
|
|
)
|
|
elif self.config.position == "top":
|
|
screen = QApplication.primaryScreen().geometry()
|
|
self.move(
|
|
(screen.width() - self.config.width) // 2,
|
|
20
|
|
)
|
|
|
|
def _create_drawer_item(self, plugin_id: str, plugin_class) -> QPushButton:
|
|
"""Create a drawer item (like Start menu app)."""
|
|
icon_name = getattr(plugin_class, 'icon_name', 'grid')
|
|
btn = QPushButton(f" {plugin_class.name}")
|
|
btn.setIcon(self.icon_manager.get_icon(icon_name))
|
|
btn.setIconSize(QSize(20, 20))
|
|
btn.setFixedHeight(44)
|
|
btn.setStyleSheet("""
|
|
QPushButton {
|
|
background: transparent;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 8px;
|
|
text-align: left;
|
|
font-size: 13px;
|
|
padding-left: 12px;
|
|
}
|
|
QPushButton:hover {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
}
|
|
""")
|
|
btn.clicked.connect(lambda: self._on_drawer_item_clicked(plugin_id))
|
|
return btn
|
|
|
|
def _on_plugin_clicked(self, plugin_id: str):
|
|
"""Handle pinned plugin click."""
|
|
print(f"[Taskbar] Plugin clicked: {plugin_id}")
|
|
self.widget_requested.emit(plugin_id)
|
|
self._pulse_animation()
|
|
|
|
def _on_drawer_item_clicked(self, plugin_id: str):
|
|
"""Handle drawer item click."""
|
|
self.drawer.hide()
|
|
self._on_plugin_clicked(plugin_id)
|
|
|
|
def _on_search(self):
|
|
"""Handle search box return."""
|
|
text = self.search_box.text().strip()
|
|
if text:
|
|
self.search_requested.emit(text)
|
|
print(f"[Taskbar] Search: {text}")
|
|
|
|
def _on_search_text_changed(self, text: str):
|
|
"""Handle search text changes."""
|
|
self.search_text = text
|
|
# Could implement live filtering here
|
|
|
|
def _pulse_animation(self):
|
|
"""Subtle pulse animation on interaction."""
|
|
anim = QPropertyAnimation(self, b"minimumHeight")
|
|
anim.setDuration(150)
|
|
anim.setStartValue(self.height())
|
|
anim.setEndValue(self.height() + 2)
|
|
anim.setEasingCurve(QEasingCurve.Type.OutQuad)
|
|
anim.start()
|
|
|
|
def _update_clock(self):
|
|
"""Update clock display."""
|
|
from datetime import datetime
|
|
self.clock_label.setText(datetime.now().strftime("%H:%M"))
|
|
|
|
def _show_context_menu(self, position):
|
|
"""Show right-click context menu."""
|
|
menu = QMenu(self)
|
|
menu.setStyleSheet("""
|
|
QMenu {
|
|
background: rgba(20, 31, 35, 0.95);
|
|
color: white;
|
|
border: 1px solid rgba(255, 140, 66, 0.15);
|
|
border-radius: 8px;
|
|
padding: 8px;
|
|
}
|
|
QMenu::item {
|
|
padding: 8px 24px;
|
|
border-radius: 4px;
|
|
}
|
|
QMenu::item:selected {
|
|
background: rgba(255, 140, 66, 0.2);
|
|
}
|
|
""")
|
|
|
|
# Toggle search
|
|
search_action = menu.addAction("☑ Search" if self.config.show_search else "☐ Search")
|
|
search_action.triggered.connect(self._toggle_search)
|
|
|
|
# Toggle clock
|
|
clock_action = menu.addAction("☑ Clock" if self.config.show_clock else "☐ Clock")
|
|
clock_action.triggered.connect(self._toggle_clock)
|
|
|
|
menu.addSeparator()
|
|
|
|
# Settings
|
|
settings_action = menu.addAction("Settings...")
|
|
settings_action.triggered.connect(self._show_settings)
|
|
|
|
# Reset position
|
|
reset_action = menu.addAction("Reset Position")
|
|
reset_action.triggered.connect(self._reset_position)
|
|
|
|
menu.addSeparator()
|
|
|
|
# Hide
|
|
hide_action = menu.addAction("Hide")
|
|
hide_action.triggered.connect(self.hide)
|
|
|
|
menu.exec(self.mapToGlobal(position))
|
|
|
|
def _toggle_search(self):
|
|
"""Toggle search box visibility."""
|
|
self.config.show_search = not self.config.show_search
|
|
self.search_box.setVisible(self.config.show_search)
|
|
self._save_config()
|
|
|
|
def _toggle_clock(self):
|
|
"""Toggle clock visibility."""
|
|
self.config.show_clock = not self.config.show_clock
|
|
self.clock_widget.setVisible(self.config.show_clock)
|
|
self._save_config()
|
|
|
|
def _reset_position(self):
|
|
"""Reset bar position to default."""
|
|
self.config.position = "bottom"
|
|
self.config.x = 100
|
|
self.config.y = 800
|
|
self._apply_size_and_position()
|
|
self._save_config()
|
|
|
|
def _show_settings(self):
|
|
"""Show settings dialog."""
|
|
dialog = TaskbarSettingsDialog(self.config, self)
|
|
if dialog.exec():
|
|
self.config = dialog.get_config()
|
|
self._save_config()
|
|
self._apply_config()
|
|
self._refresh_pinned_plugins()
|
|
|
|
def _apply_config(self):
|
|
"""Apply configuration."""
|
|
# Set auto-hide timer
|
|
self.hide_timer.setInterval(self.config.auto_hide_delay)
|
|
|
|
# Apply opacity
|
|
self._apply_opacity()
|
|
|
|
# Apply size and position
|
|
self._apply_size_and_position()
|
|
|
|
# Show/hide search and clock
|
|
self.search_box.setVisible(self.config.show_search)
|
|
self.clock_widget.setVisible(self.config.show_clock)
|
|
|
|
def _auto_hide(self):
|
|
"""Auto-hide when mouse leaves."""
|
|
if self.config.auto_hide and not self.underMouse():
|
|
if not hasattr(self, 'drawer') or not self.drawer.isVisible():
|
|
self.hide()
|
|
|
|
def enterEvent(self, event):
|
|
"""Mouse entered - stop hide timer."""
|
|
self.hide_timer.stop()
|
|
super().enterEvent(event)
|
|
|
|
def leaveEvent(self, event):
|
|
"""Mouse left - start hide timer."""
|
|
if self.config.auto_hide:
|
|
self.hide_timer.start()
|
|
super().leaveEvent(event)
|
|
|
|
def _load_config(self) -> ActivityBarConfig:
|
|
"""Load configuration."""
|
|
config_path = Path("config/activity_bar.json")
|
|
if config_path.exists():
|
|
try:
|
|
data = json.loads(config_path.read_text())
|
|
return ActivityBarConfig.from_dict(data)
|
|
except:
|
|
pass
|
|
return ActivityBarConfig()
|
|
|
|
def _save_config(self):
|
|
"""Save configuration."""
|
|
config_path = Path("config/activity_bar.json")
|
|
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
config_path.write_text(json.dumps(self.config.to_dict(), indent=2))
|
|
|
|
|
|
class TaskbarSettingsDialog(QDialog):
|
|
"""Settings dialog for Windows Taskbar."""
|
|
|
|
def __init__(self, config: ActivityBarConfig, parent=None):
|
|
super().__init__(parent)
|
|
self.config = config
|
|
self.setWindowTitle("Activity Bar Settings")
|
|
self.setMinimumSize(400, 450)
|
|
self._setup_ui()
|
|
|
|
def _setup_ui(self):
|
|
"""Setup settings UI."""
|
|
from PyQt6.QtWidgets import QVBoxLayout, QFormLayout, QDialogButtonBox, QGroupBox
|
|
|
|
layout = QVBoxLayout(self)
|
|
|
|
# Appearance Group
|
|
appear_group = QGroupBox("Appearance")
|
|
appear_form = QFormLayout(appear_group)
|
|
|
|
# Opacity
|
|
self.opacity_slider = QSlider(Qt.Orientation.Horizontal)
|
|
self.opacity_slider.setRange(20, 100)
|
|
self.opacity_slider.setValue(self.config.background_opacity)
|
|
self.opacity_label = QLabel(f"{self.config.background_opacity}%")
|
|
self.opacity_slider.valueChanged.connect(lambda v: self.opacity_label.setText(f"{v}%"))
|
|
opacity_layout = QHBoxLayout()
|
|
opacity_layout.addWidget(self.opacity_slider)
|
|
opacity_layout.addWidget(self.opacity_label)
|
|
appear_form.addRow("Background Opacity:", opacity_layout)
|
|
|
|
# Icon size
|
|
self.icon_size = QSpinBox()
|
|
self.icon_size.setRange(24, 48)
|
|
self.icon_size.setValue(self.config.icon_size)
|
|
appear_form.addRow("Icon Size:", self.icon_size)
|
|
|
|
layout.addWidget(appear_group)
|
|
|
|
# Features Group
|
|
features_group = QGroupBox("Features")
|
|
features_form = QFormLayout(features_group)
|
|
|
|
# Show search
|
|
self.show_search_cb = QCheckBox("Show search box")
|
|
self.show_search_cb.setChecked(self.config.show_search)
|
|
features_form.addRow(self.show_search_cb)
|
|
|
|
# Show clock
|
|
self.show_clock_cb = QCheckBox("Show clock")
|
|
self.show_clock_cb.setChecked(self.config.show_clock)
|
|
features_form.addRow(self.show_clock_cb)
|
|
|
|
layout.addWidget(features_group)
|
|
|
|
# Position Group
|
|
pos_group = QGroupBox("Position")
|
|
pos_form = QFormLayout(pos_group)
|
|
|
|
# Position
|
|
self.position_combo = QComboBox()
|
|
self.position_combo.addItems(["Bottom", "Top", "Custom"])
|
|
self.position_combo.setCurrentText(self.config.position.title())
|
|
pos_form.addRow("Position:", self.position_combo)
|
|
|
|
# Width
|
|
self.width_spin = QSpinBox()
|
|
self.width_spin.setRange(400, 1600)
|
|
self.width_spin.setValue(self.config.width)
|
|
pos_form.addRow("Width:", self.width_spin)
|
|
|
|
layout.addWidget(pos_group)
|
|
|
|
# Behavior Group
|
|
behav_group = QGroupBox("Behavior")
|
|
behav_form = QFormLayout(behav_group)
|
|
|
|
# Auto-hide
|
|
self.autohide_cb = QCheckBox("Auto-hide when not in use")
|
|
self.autohide_cb.setChecked(self.config.auto_hide)
|
|
behav_form.addRow(self.autohide_cb)
|
|
|
|
layout.addWidget(behav_group)
|
|
|
|
layout.addStretch()
|
|
|
|
# Buttons
|
|
buttons = QDialogButtonBox(
|
|
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
|
|
)
|
|
buttons.accepted.connect(self.accept)
|
|
buttons.rejected.connect(self.reject)
|
|
layout.addWidget(buttons)
|
|
|
|
def get_config(self) -> ActivityBarConfig:
|
|
"""Get updated config."""
|
|
return ActivityBarConfig(
|
|
enabled=True,
|
|
position=self.position_combo.currentText().lower(),
|
|
x=self.config.x,
|
|
y=self.config.y,
|
|
width=self.width_spin.value(),
|
|
icon_size=self.icon_size.value(),
|
|
auto_hide=self.autohide_cb.isChecked(),
|
|
auto_hide_delay=self.config.auto_hide_delay,
|
|
auto_show_on_focus=self.config.auto_show_on_focus,
|
|
background_opacity=self.opacity_slider.value(),
|
|
show_search=self.show_search_cb.isChecked(),
|
|
show_clock=self.show_clock_cb.isChecked(),
|
|
pinned_plugins=self.config.pinned_plugins
|
|
)
|
|
|
|
|
|
# Global instance
|
|
_taskbar_instance: Optional[WindowsTaskbar] = None
|
|
|
|
|
|
def get_activity_bar(plugin_manager=None) -> Optional[WindowsTaskbar]:
|
|
"""Get or create global taskbar instance."""
|
|
global _taskbar_instance
|
|
if _taskbar_instance is None and plugin_manager:
|
|
_taskbar_instance = WindowsTaskbar(plugin_manager)
|
|
return _taskbar_instance
|