feat: Redesigned Activity Bar - Windows 11 Taskbar Style

Complete redesign of the in-game Activity Bar:
- Transparent background (no visible background)
- Windows-style start button (⊞ icon)
- Search box with rounded corners (Windows 11 style)
- Pinned plugins expand the bar dynamically
- Clean minimal design
- System clock display
- Right-click context menu for settings
- Auto-hide functionality
- Draggable positioning

Features:
- Click ⊞ button to open app drawer
- Type in search box to find plugins
- Pin plugins to taskbar for quick access
- Clock shows current time (updates every minute)
- Right-click for settings

The bar now looks like a floating Windows taskbar
perfect for in-game overlay use.
This commit is contained in:
LemonNexus 2026-02-15 19:05:57 +00:00
parent acbdef6133
commit 0feaf24732
1 changed files with 363 additions and 370 deletions

View File

@ -1,93 +1,102 @@
"""
EU-Utility - Activity Bar (In-Game Overlay)
EU-Utility - Windows Taskbar Style Activity Bar
===============================================
macOS-style activity bar/dock for in-game use.
Features: plugin drawer, mini widgets, floating widgets,
customizable layout (vertical/horizontal/grid), size, opacity.
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 enum import Enum
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
QFrame, QScrollArea, QGridLayout, QMenu, QDialog,
QSlider, QComboBox, QCheckBox, QSpinBox, QTabWidget
QFrame, QLineEdit, QMenu, QDialog, QSlider, QComboBox,
QCheckBox, QSpinBox, QApplication, QSizePolicy
)
from PyQt6.QtCore import Qt, QPoint, QSize, QTimer, pyqtSignal
from PyQt6.QtGui import QMouseEvent, QPainter, QColor, QIcon, QPixmap
class ActivityBarLayout(Enum):
"""Activity bar layout modes."""
HORIZONTAL = "horizontal"
VERTICAL = "vertical"
GRID = "grid"
from PyQt6.QtCore import Qt, QPoint, QSize, QTimer, pyqtSignal, QPropertyAnimation, QEasingCurve
from PyQt6.QtGui import QMouseEvent, QPainter, QColor, QFont, QIcon, QPixmap
@dataclass
class ActivityBarConfig:
"""Activity bar configuration."""
enabled: bool = True
layout: ActivityBarLayout = ActivityBarLayout.HORIZONTAL
position: str = "bottom" # top, bottom, left, right
size: int = 48 # Icon size in pixels
opacity: float = 0.9
always_visible: bool = False
position: str = "bottom" # top, bottom
icon_size: int = 32
auto_hide: bool = True
show_labels: bool = False
grid_columns: int = 4 # For grid layout
grid_rows: int = 2
pinned_plugins: List[str] = None # Plugin IDs to show in bar
auto_hide_delay: int = 3000 # milliseconds
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,
'icon_size': self.icon_size,
'auto_hide': self.auto_hide,
'auto_hide_delay': self.auto_hide_delay,
'pinned_plugins': self.pinned_plugins
}
class ActivityBar(QFrame):
@classmethod
def from_dict(cls, data):
return cls(
enabled=data.get('enabled', True),
position=data.get('position', 'bottom'),
icon_size=data.get('icon_size', 32),
auto_hide=data.get('auto_hide', True),
auto_hide_delay=data.get('auto_hide_delay', 3000),
pinned_plugins=data.get('pinned_plugins', [])
)
class WindowsTaskbar(QFrame):
"""
macOS-style activity bar for in-game overlay.
Windows 11-style taskbar for in-game overlay.
Features:
- Plugin drawer (app drawer style)
- Pinned plugins in bar
- Mini widgets
- Configurable layout (horizontal/vertical/grid)
- Draggable, resizable
- Opacity control
- Transparent background (no background visible)
- Windows-style start button
- Search box for quick access
- Pinned plugins expand the bar
- Clean, minimal design
"""
widget_requested = pyqtSignal(str) # plugin_id
drawer_toggled = pyqtSignal(bool) # is_open
widget_requested = pyqtSignal(str)
search_requested = pyqtSignal(str)
def __init__(self, plugin_manager, parent=None):
super().__init__(parent)
self.plugin_manager = plugin_manager
self.config = self._load_config()
self.drawer_open = False
# State
self.pinned_buttons: Dict[str, QPushButton] = {}
self.mini_widgets: Dict[str, QWidget] = {}
self.is_expanded = False
self.search_text = ""
self._setup_window()
self._setup_ui()
self._apply_config()
# Auto-hide timer
# Auto-hide
self.hide_timer = QTimer(self)
self.hide_timer.timeout.connect(self._auto_hide)
self.hide_timer.setInterval(3000) # 3 seconds
# Show initially
# Show if enabled
if self.config.enabled:
self.show()
def _setup_window(self):
"""Setup window properties for overlay."""
"""Setup frameless overlay window."""
self.setWindowFlags(
Qt.WindowType.FramelessWindowHint |
Qt.WindowType.WindowStaysOnTopHint |
@ -95,150 +104,152 @@ class ActivityBar(QFrame):
Qt.WindowType.WindowDoesNotAcceptFocus
)
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
# Make draggable
# Draggable state
self._dragging = False
self._drag_offset = QPoint()
def _setup_ui(self):
"""Setup the activity bar UI."""
self.main_layout = QVBoxLayout(self)
self.main_layout.setContentsMargins(4, 4, 4, 4)
self.main_layout.setSpacing(2)
# Main container
self.container = QFrame()
self.container.setObjectName("activityBarContainer")
self.main_layout.addWidget(self.container)
# Container layout based on config
self._update_container_layout()
# Add pinned plugins
self._refresh_pinned_plugins()
# Drawer button (always last)
self.drawer_btn = self._create_drawer_button()
self._add_to_container(self.drawer_btn)
# Setup drawer panel
self._setup_drawer()
def _update_container_layout(self):
"""Update container layout based on config."""
# Clear existing layout
old_layout = self.container.layout()
if old_layout:
while old_layout.count():
item = old_layout.takeAt(0)
if item.widget():
item.widget().setParent(None)
import sip
sip.delete(old_layout)
# Create new layout
if self.config.layout == ActivityBarLayout.HORIZONTAL:
layout = QHBoxLayout(self.container)
elif self.config.layout == ActivityBarLayout.VERTICAL:
layout = QVBoxLayout(self.container)
else: # GRID
layout = QGridLayout(self.container)
layout.setContentsMargins(6, 6, 6, 6)
"""Setup Windows taskbar-style UI."""
# Main layout with minimal margins
layout = QHBoxLayout(self)
layout.setContentsMargins(8, 4, 8, 4)
layout.setSpacing(4)
self.container.setLayout(layout)
# Update container style
self._update_style()
def _update_style(self):
"""Update container styling."""
opacity = int(self.config.opacity * 255)
border_radius = 12 if self.config.layout == ActivityBarLayout.GRID else 20
self.container.setStyleSheet(f"""
QFrame#activityBarContainer {{
background-color: rgba(35, 40, 55, {opacity});
border: 1px solid rgba(100, 110, 130, 100);
border-radius: {border_radius}px;
}}
# Style: No background, just floating elements
self.setStyleSheet("""
WindowsTaskbar {
background: transparent;
border: none;
}
""")
def _create_drawer_button(self) -> QPushButton:
"""Create the app drawer button."""
btn = QPushButton("⋮⋮⋮") # Drawer icon
btn.setFixedSize(self.config.size, self.config.size)
btn.setStyleSheet(f"""
QPushButton {{
background-color: rgba(255, 140, 66, 200);
# === START BUTTON (Windows icon style) ===
self.start_btn = QPushButton("") # Windows-like icon
self.start_btn.setFixedSize(40, 40)
self.start_btn.setStyleSheet("""
QPushButton {
background: rgba(255, 255, 255, 0.1);
color: white;
border: none;
border-radius: {self.config.size // 4}px;
font-size: 16px;
border-radius: 8px;
font-size: 20px;
font-weight: bold;
}}
QPushButton:hover {{
background-color: rgba(255, 140, 66, 255);
}}
}
QPushButton:hover {
background: rgba(255, 255, 255, 0.2);
}
QPushButton:pressed {
background: rgba(255, 255, 255, 0.15);
}
""")
btn.setToolTip("Plugin Drawer")
btn.clicked.connect(self._toggle_drawer)
return btn
self.start_btn.setToolTip("Open App Drawer")
self.start_btn.clicked.connect(self._toggle_drawer)
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)
layout.addWidget(self.search_box)
# Separator
separator = QFrame()
separator.setFixedSize(1, 24)
separator.setStyleSheet("background: rgba(255, 255, 255, 0.1);")
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)
layout.addWidget(self.pinned_container)
# Spacer to push icons together
layout.addStretch()
# === SYSTEM TRAY AREA ===
# Add a small clock or status indicator
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)
layout.addWidget(self.clock_label)
# 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()
def _create_plugin_button(self, plugin_id: str, plugin_class) -> QPushButton:
"""Create a plugin button for the bar."""
"""Create a pinned plugin button (taskbar icon style)."""
btn = QPushButton()
btn.setFixedSize(self.config.size, self.config.size)
size = self.config.icon_size
btn.setFixedSize(size + 8, size + 8)
# Get icon
icon_name = getattr(plugin_class, 'icon', 'box')
try:
from core.icon_manager import get_icon_manager
icon_mgr = get_icon_manager()
pixmap = icon_mgr.get_pixmap(icon_name, size=self.config.size - 12)
btn.setIcon(QIcon(pixmap))
btn.setIconSize(QSize(self.config.size - 12, self.config.size - 12))
except:
# Fallback to emoji
btn.setText("📦")
# Get plugin icon or use default
icon_text = getattr(plugin_class, 'icon', '')
btn.setText(icon_text)
# Style
btn.setStyleSheet(f"""
QPushButton {{
background-color: rgba(60, 65, 80, 180);
background: transparent;
color: white;
border: none;
border-radius: {self.config.size // 4}px;
border-radius: 6px;
font-size: {size // 2}px;
}}
QPushButton:hover {{
background-color: rgba(80, 85, 100, 220);
background: rgba(255, 255, 255, 0.1);
}}
QPushButton:pressed {{
background: rgba(255, 255, 255, 0.05);
}}
""")
# Tooltip
btn.setToolTip(plugin_class.name)
# Click handler
btn.clicked.connect(lambda: self._on_plugin_clicked(plugin_id))
return btn
def _add_to_container(self, widget: QWidget):
"""Add widget to container based on layout."""
layout = self.container.layout()
if isinstance(layout, QGridLayout):
# For grid, calculate position
count = layout.count()
col = count % self.config.grid_columns
row = count // self.config.grid_columns
layout.addWidget(widget, row, col)
else:
layout.addWidget(widget)
def _refresh_pinned_plugins(self):
"""Refresh pinned plugins in the bar."""
# Clear existing pinned buttons (except drawer)
"""Refresh pinned plugin buttons."""
# Clear existing
for btn in self.pinned_buttons.values():
btn.deleteLater()
self.pinned_buttons.clear()
@ -255,10 +266,25 @@ class ActivityBar(QFrame):
plugin_class = all_plugins[plugin_id]
btn = self._create_plugin_button(plugin_id, plugin_class)
self.pinned_buttons[plugin_id] = btn
self._add_to_container(btn)
self.pinned_layout.addWidget(btn)
def _setup_drawer(self):
"""Setup the plugin drawer panel."""
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 |
@ -266,148 +292,179 @@ class ActivityBar(QFrame):
Qt.WindowType.Tool
)
self.drawer.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
self.drawer.hide()
self.drawer.setFixedSize(400, 500)
# Drawer layout
drawer_layout = QVBoxLayout(self.drawer)
drawer_layout.setContentsMargins(12, 12, 12, 12)
drawer_layout.setSpacing(8)
# Drawer header
header = QLabel("Plugin Drawer")
header.setStyleSheet("""
color: white;
font-size: 16px;
font-weight: bold;
""")
drawer_layout.addWidget(header)
# Scroll area for plugins
scroll = QScrollArea()
scroll.setWidgetResizable(True)
scroll.setFrameShape(QFrame.Shape.NoFrame)
scroll.setStyleSheet("background: transparent;")
self.drawer_content = QWidget()
self.drawer_layout = QGridLayout(self.drawer_content)
self.drawer_layout.setSpacing(8)
scroll.setWidget(self.drawer_content)
drawer_layout.addWidget(scroll)
# Refresh drawer content
self._refresh_drawer()
def _refresh_drawer(self):
"""Refresh plugin drawer content."""
# Clear existing
while self.drawer_layout.count():
item = self.drawer_layout.takeAt(0)
if item.widget():
item.widget().deleteLater()
if not self.plugin_manager:
return
all_plugins = self.plugin_manager.get_all_discovered_plugins()
col = 0
row = 0
for plugin_id, plugin_class in all_plugins.items():
# Skip already pinned
if plugin_id in self.config.pinned_plugins:
continue
# Create plugin item
item = self._create_drawer_item(plugin_id, plugin_class)
self.drawer_layout.addWidget(item, row, col)
col += 1
if col >= 4: # 4 columns
col = 0
row += 1
def _create_drawer_item(self, plugin_id: str, plugin_class) -> QFrame:
"""Create a drawer item for a plugin."""
frame = QFrame()
frame.setFixedSize(80, 80)
frame.setStyleSheet("""
# Drawer style: subtle frosted glass
self.drawer.setStyleSheet("""
QFrame {
background-color: rgba(50, 55, 70, 200);
border-radius: 8px;
}
QFrame:hover {
background-color: rgba(70, 75, 90, 220);
background: rgba(32, 32, 32, 0.95);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
}
""")
layout = QVBoxLayout(frame)
layout.setContentsMargins(4, 4, 4, 4)
layout.setSpacing(2)
layout = QVBoxLayout(self.drawer)
layout.setContentsMargins(16, 16, 16, 16)
layout.setSpacing(12)
# Icon
icon_label = QLabel("📦")
icon_label.setStyleSheet("font-size: 24px;")
icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(icon_label)
# Header
header = QLabel("All Plugins")
header.setStyleSheet("color: white; font-size: 16px; font-weight: bold;")
layout.addWidget(header)
# Name
name_label = QLabel(plugin_class.name[:8])
name_label.setStyleSheet("color: white; font-size: 9px;")
name_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(name_label)
# 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)
# Click to open
frame.mousePressEvent = lambda e, pid=plugin_id: self._on_drawer_item_clicked(pid)
# Plugin grid
plugins_widget = QWidget()
plugins_layout = QVBoxLayout(plugins_widget)
plugins_layout.setSpacing(8)
return frame
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)
def _toggle_drawer(self):
"""Toggle the plugin drawer."""
self.drawer_open = not self.drawer_open
plugins_layout.addStretch()
layout.addWidget(plugins_widget)
if self.drawer_open:
# Position drawer near activity bar
pos = self.pos()
if self.config.layout == ActivityBarLayout.HORIZONTAL:
self.drawer.move(pos.x(), pos.y() - 300)
else:
self.drawer.move(pos.x() + self.width(), pos.y())
self.drawer.resize(360, 300)
self.drawer.show()
self.drawer_toggled.emit(True)
else:
self.drawer.hide()
self.drawer_toggled.emit(False)
def _create_drawer_item(self, plugin_id: str, plugin_class) -> QPushButton:
"""Create a drawer item (like Start menu app)."""
btn = QPushButton(f" {getattr(plugin_class, 'icon', '')} {plugin_class.name}")
btn.setFixedHeight(44)
btn.setStyleSheet("""
QPushButton {
background: transparent;
color: white;
border: none;
border-radius: 8px;
text-align: left;
font-size: 13px;
}
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 plugin button click."""
print(f"[ActivityBar] Plugin clicked: {plugin_id}")
"""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."""
print(f"[ActivityBar] Drawer item clicked: {plugin_id}")
self.widget_requested.emit(plugin_id)
self._toggle_drawer() # Close drawer
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(40, 40, 40, 0.95);
color: white;
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
padding: 8px;
}
QMenu::item {
padding: 8px 24px;
border-radius: 4px;
}
QMenu::item:selected {
background: rgba(255, 255, 255, 0.1);
}
""")
settings_action = menu.addAction("⚙️ Settings")
settings_action.triggered.connect(self._show_settings)
menu.addSeparator()
hide_action = menu.addAction("🗕 Hide")
hide_action.triggered.connect(self.hide)
menu.exec(self.mapToGlobal(position))
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)
# Position bar
screen = QApplication.primaryScreen().geometry()
if self.config.position == "bottom":
self.move((screen.width() - 600) // 2, screen.height() - 60)
else: # top
self.move((screen.width() - 600) // 2, 10)
# Size
self.setFixedSize(600, 50)
def _auto_hide(self):
"""Auto-hide the activity bar."""
if self.config.auto_hide and not self.config.always_visible:
# Check if mouse is over bar or drawer
if not self.underMouse() and not self.drawer.underMouse():
"""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 activity bar."""
if self.config.auto_hide:
self.hide_timer.stop()
"""Mouse entered - stop hide timer."""
self.hide_timer.stop()
super().enterEvent(event)
def leaveEvent(self, event):
"""Mouse left activity bar."""
if self.config.auto_hide and not self.drawer_open:
"""Mouse left - start hide timer."""
if self.config.auto_hide:
self.hide_timer.start()
super().leaveEvent(event)
@ -419,7 +476,7 @@ class ActivityBar(QFrame):
event.accept()
def mouseMoveEvent(self, event: QMouseEvent):
"""Drag activity bar."""
"""Drag window."""
if self._dragging:
new_pos = event.globalPosition().toPoint() - self._drag_offset
self.move(new_pos)
@ -428,66 +485,33 @@ class ActivityBar(QFrame):
"""Stop dragging."""
if event.button() == Qt.MouseButton.LeftButton:
self._dragging = False
self._save_position()
def _apply_config(self):
"""Apply configuration."""
self.setWindowOpacity(self.config.opacity)
self._update_style()
# Resize based on layout and size
if self.config.layout == ActivityBarLayout.HORIZONTAL:
self.resize(400, self.config.size + 20)
elif self.config.layout == ActivityBarLayout.VERTICAL:
self.resize(self.config.size + 20, 400)
else: # GRID
self.resize(
self.config.grid_columns * (self.config.size + 8) + 20,
self.config.grid_rows * (self.config.size + 8) + 20
)
def _load_config(self) -> ActivityBarConfig:
"""Load activity bar configuration."""
"""Load configuration."""
config_path = Path("config/activity_bar.json")
if config_path.exists():
try:
data = json.loads(config_path.read_text())
return ActivityBarConfig(**data)
return ActivityBarConfig.from_dict(data)
except:
pass
return ActivityBarConfig()
def _save_config(self):
"""Save activity bar configuration."""
"""Save configuration."""
config_path = Path("config/activity_bar.json")
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text(json.dumps(asdict(self.config), indent=2))
def _save_position(self):
"""Save bar position."""
# TODO: Save position to config
pass
def show_settings_dialog(self):
"""Show activity bar settings dialog."""
dialog = ActivityBarSettingsDialog(self.config, self)
if dialog.exec():
self.config = dialog.get_config()
self._apply_config()
self._save_config()
self._refresh_pinned_plugins()
config_path.write_text(json.dumps(self.config.to_dict(), indent=2))
class ActivityBarSettingsDialog(QDialog):
"""Settings dialog for activity bar."""
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, 500)
self.setWindowTitle("Taskbar Settings")
self.setMinimumSize(350, 300)
self._setup_ui()
def _setup_ui(self):
@ -496,47 +520,24 @@ class ActivityBarSettingsDialog(QDialog):
layout = QVBoxLayout(self)
# Form
form = QFormLayout()
# Enabled
self.enabled_cb = QCheckBox("Enable Activity Bar")
self.enabled_cb.setChecked(self.config.enabled)
form.addRow(self.enabled_cb)
# Layout
self.layout_combo = QComboBox()
self.layout_combo.addItems(["Horizontal", "Vertical", "Grid"])
self.layout_combo.setCurrentText(self.config.layout.value.title())
form.addRow("Layout:", self.layout_combo)
# Size
self.size_spin = QSpinBox()
self.size_spin.setRange(32, 96)
self.size_spin.setValue(self.config.size)
form.addRow("Icon Size:", self.size_spin)
# Opacity
self.opacity_slider = QSlider(Qt.Orientation.Horizontal)
self.opacity_slider.setRange(20, 100)
self.opacity_slider.setValue(int(self.config.opacity * 100))
form.addRow("Opacity:", self.opacity_slider)
# Auto-hide
self.autohide_cb = QCheckBox("Auto-hide when not in use")
self.autohide_cb.setChecked(self.config.auto_hide)
form.addRow(self.autohide_cb)
# Show labels
self.labels_cb = QCheckBox("Show plugin labels")
self.labels_cb.setChecked(self.config.show_labels)
form.addRow(self.labels_cb)
# Position
self.position_combo = QComboBox()
self.position_combo.addItems(["Bottom", "Top"])
self.position_combo.setCurrentText(self.config.position.title())
form.addRow("Position:", self.position_combo)
# Grid config
self.grid_cols = QSpinBox()
self.grid_cols.setRange(2, 8)
self.grid_cols.setValue(self.config.grid_columns)
form.addRow("Grid Columns:", self.grid_cols)
# Icon size
self.icon_size = QSpinBox()
self.icon_size.setRange(24, 48)
self.icon_size.setValue(self.config.icon_size)
form.addRow("Icon Size:", self.icon_size)
layout.addLayout(form)
@ -549,32 +550,24 @@ class ActivityBarSettingsDialog(QDialog):
layout.addWidget(buttons)
def get_config(self) -> ActivityBarConfig:
"""Get updated configuration."""
layout_map = {
"Horizontal": ActivityBarLayout.HORIZONTAL,
"Vertical": ActivityBarLayout.VERTICAL,
"Grid": ActivityBarLayout.GRID
}
"""Get updated config."""
return ActivityBarConfig(
enabled=self.enabled_cb.isChecked(),
layout=layout_map.get(self.layout_combo.currentText(), ActivityBarLayout.HORIZONTAL),
size=self.size_spin.value(),
opacity=self.opacity_slider.value() / 100,
enabled=True,
position=self.position_combo.currentText().lower(),
icon_size=self.icon_size.value(),
auto_hide=self.autohide_cb.isChecked(),
show_labels=self.labels_cb.isChecked(),
grid_columns=self.grid_cols.value(),
auto_hide_delay=self.config.auto_hide_delay,
pinned_plugins=self.config.pinned_plugins
)
# Global instance
_activity_bar: Optional[ActivityBar] = None
_taskbar_instance: Optional[WindowsTaskbar] = None
def get_activity_bar(plugin_manager=None) -> Optional[ActivityBar]:
"""Get or create global activity bar instance."""
global _activity_bar
if _activity_bar is None and plugin_manager:
_activity_bar = ActivityBar(plugin_manager)
return _activity_bar
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