You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
SoulSync/ui/sidebar.py

312 lines
10 KiB

from PyQt6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QPushButton,
QLabel, QFrame, QSizePolicy, QSpacerItem)
from PyQt6.QtCore import Qt, pyqtSignal, QPropertyAnimation, QEasingCurve, QRect
from PyQt6.QtGui import QFont, QPalette, QIcon, QPixmap, QPainter
class SidebarButton(QPushButton):
def __init__(self, text: str, icon_text: str = "", parent=None):
super().__init__(parent)
self.text = text
self.icon_text = icon_text
self.is_active = False
self.setup_ui()
def setup_ui(self):
self.setFixedHeight(50)
self.setFixedWidth(200)
self.setCursor(Qt.CursorShape.PointingHandCursor)
layout = QHBoxLayout(self)
layout.setContentsMargins(20, 0, 20, 0)
layout.setSpacing(15)
# Icon label
self.icon_label = QLabel(self.icon_text)
self.icon_label.setFixedSize(24, 24)
self.icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.icon_label.setStyleSheet("""
QLabel {
color: #b3b3b3;
font-size: 16px;
font-weight: bold;
border-radius: 12px;
background: rgba(255, 255, 255, 0.1);
}
""")
# Text label
self.text_label = QLabel(self.text)
self.text_label.setFont(QFont("Arial", 11, QFont.Weight.Medium))
layout.addWidget(self.icon_label)
layout.addWidget(self.text_label)
layout.addStretch()
self.update_style()
def set_active(self, active: bool):
self.is_active = active
self.update_style()
def update_style(self):
if self.is_active:
self.setStyleSheet("""
SidebarButton {
background: rgba(29, 185, 84, 0.15);
border-left: 4px solid #1db954;
border-radius: 12px;
text-align: left;
padding: 0px;
}
SidebarButton:hover {
background: rgba(29, 185, 84, 0.25);
transform: scale(1.02);
}
""")
self.text_label.setStyleSheet("color: #1db954; font-weight: bold; background: transparent;")
self.icon_label.setStyleSheet("""
QLabel {
color: #1db954;
font-size: 16px;
font-weight: bold;
border-radius: 14px;
background: rgba(29, 185, 84, 0.25);
}
""")
else:
self.setStyleSheet("""
SidebarButton {
background: transparent;
border: none;
border-radius: 12px;
text-align: left;
padding: 0px;
}
SidebarButton:hover {
background: rgba(255, 255, 255, 0.08);
border-left: 2px solid rgba(255, 255, 255, 0.3);
}
""")
self.text_label.setStyleSheet("color: #b3b3b3; background: transparent;")
self.icon_label.setStyleSheet("""
QLabel {
color: #b3b3b3;
font-size: 16px;
font-weight: bold;
border-radius: 14px;
background: rgba(255, 255, 255, 0.08);
}
""")
class StatusIndicator(QWidget):
def __init__(self, service_name: str, parent=None):
super().__init__(parent)
self.service_name = service_name
self.is_connected = False
self.setup_ui()
def setup_ui(self):
self.setFixedHeight(35) # Ensure enough height
layout = QHBoxLayout(self)
layout.setContentsMargins(15, 8, 15, 8)
layout.setSpacing(12)
# Status dot with rounded background
self.status_dot = QLabel("")
self.status_dot.setFixedSize(16, 16)
self.status_dot.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.status_dot.setStyleSheet("""
QLabel {
border-radius: 8px;
font-size: 12px;
font-weight: bold;
}
""")
# Service name
self.service_label = QLabel(self.service_name)
self.service_label.setFont(QFont("Arial", 10, QFont.Weight.Medium))
self.service_label.setMinimumWidth(80) # Ensure text doesn't get cut off
layout.addWidget(self.status_dot)
layout.addWidget(self.service_label)
layout.addStretch()
self.update_status(False)
def update_status(self, connected: bool):
self.is_connected = connected
if connected:
self.status_dot.setStyleSheet("""
QLabel {
color: #1db954;
background: rgba(29, 185, 84, 0.15);
border-radius: 8px;
font-size: 12px;
font-weight: bold;
}
""")
self.service_label.setStyleSheet("color: #ffffff; font-weight: 500;")
else:
self.status_dot.setStyleSheet("""
QLabel {
color: #e22134;
background: rgba(226, 33, 52, 0.15);
border-radius: 8px;
font-size: 12px;
font-weight: bold;
}
""")
self.service_label.setStyleSheet("color: #b3b3b3; font-weight: 400;")
class ModernSidebar(QWidget):
page_changed = pyqtSignal(str)
def __init__(self, parent=None):
super().__init__(parent)
self.current_page = "dashboard"
self.buttons = {}
self.setup_ui()
def setup_ui(self):
self.setFixedWidth(220)
self.setStyleSheet("""
ModernSidebar {
background: #121212;
border-right: 1px solid #282828;
}
""")
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
# Header
header = self.create_header()
layout.addWidget(header)
# Navigation buttons
nav_section = self.create_navigation()
layout.addWidget(nav_section)
# Spacer
layout.addItem(QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding))
# Status section
status_section = self.create_status_section()
layout.addWidget(status_section)
def create_header(self):
header = QWidget()
header.setFixedHeight(85)
header.setStyleSheet("""
QWidget {
background: #121212;
border-bottom: 1px solid #282828;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
}
""")
layout = QVBoxLayout(header)
layout.setContentsMargins(20, 22, 20, 22)
layout.setSpacing(3)
# App name
app_name = QLabel("NewMusic")
app_name.setFont(QFont("Arial", 19, QFont.Weight.Bold))
app_name.setStyleSheet("color: #ffffff; letter-spacing: -0.5px;")
# Subtitle
subtitle = QLabel("Music Sync & Manager")
subtitle.setFont(QFont("Arial", 10))
subtitle.setStyleSheet("color: #b3b3b3; opacity: 0.8;")
layout.addWidget(app_name)
layout.addWidget(subtitle)
return header
def create_navigation(self):
nav_widget = QWidget()
nav_widget.setStyleSheet("""
QWidget {
background: #121212;
border-radius: 8px;
}
""")
layout = QVBoxLayout(nav_widget)
layout.setContentsMargins(8, 20, 8, 20)
layout.setSpacing(6)
# Navigation buttons
nav_items = [
("dashboard", "Dashboard", "📊"),
("sync", "Playlist Sync", "🔄"),
("downloads", "Downloads", "📥"),
("artists", "Artists", "🎵"),
("settings", "Settings", "⚙️")
]
for page_id, title, icon in nav_items:
button = SidebarButton(title, icon)
button.clicked.connect(lambda checked, pid=page_id: self.change_page(pid))
self.buttons[page_id] = button
layout.addWidget(button)
# Set dashboard as active by default
self.buttons["dashboard"].set_active(True)
return nav_widget
def create_status_section(self):
status_widget = QWidget()
status_widget.setFixedHeight(140) # Increased height
status_widget.setStyleSheet("""
QWidget {
background: #181818;
border-top: 1px solid #282828;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}
""")
layout = QVBoxLayout(status_widget)
layout.setContentsMargins(0, 18, 0, 18) # Better margins
layout.setSpacing(6) # Tighter spacing between items
# Status title
status_title = QLabel("Connection Status")
status_title.setFont(QFont("Arial", 11, QFont.Weight.Bold))
status_title.setStyleSheet("color: #ffffff; padding: 0 15px; margin-bottom: 5px;")
layout.addWidget(status_title)
# Status indicators
self.spotify_status = StatusIndicator("Spotify")
self.plex_status = StatusIndicator("Plex")
self.soulseek_status = StatusIndicator("Soulseek")
layout.addWidget(self.spotify_status)
layout.addWidget(self.plex_status)
layout.addWidget(self.soulseek_status)
return status_widget
def change_page(self, page_id: str):
if page_id != self.current_page:
# Update button states
for btn_id, button in self.buttons.items():
button.set_active(btn_id == page_id)
self.current_page = page_id
self.page_changed.emit(page_id)
def update_service_status(self, service: str, connected: bool):
status_map = {
"spotify": self.spotify_status,
"plex": self.plex_status,
"soulseek": self.soulseek_status
}
if service in status_map:
status_map[service].update_status(connected)