from __future__ import annotations from pathlib import Path from PyQt6.QtCore import QPointF, QRectF, Qt from PyQt6.QtGui import ( QColor, QIcon, QLinearGradient, QPainter, QPainterPath, QPen, QPixmap, QPolygonF, ) from .models import MediaKind BG = "#0A0D12" CARD = "#11161E" ACCENT = "#D7FF3F" TEXT = "#E8EDF4" MUTED = "#8795A7" GREEN = "#8FE3A6" RED = "#FF7D7D" AMBER = "#F6C96B" _ASSET_DIR = Path(__file__).resolve().parent / "assets" def _asset_url(name: str) -> str: return (_ASSET_DIR / name).as_posix() def stylesheet() -> str: chevron = _asset_url("chevron-down.svg") check = _asset_url("check.svg") qss = """ QWidget { color: #E8EDF4; font-family: "Segoe UI Variable Text", "Segoe UI", "Inter", sans-serif; font-size: 13px; } QMainWindow, QWidget#root { background-color: #0A0D12; } QLabel { background: transparent; } QFrame#queueCard, QFrame#settingsCard { background-color: #11161E; border: 1px solid #202A35; border-radius: 16px; } QFrame#queueCard[dragActive="true"] { border: 1px solid #D7FF3F; background-color: #121A17; } QFrame#divider { background-color: #1E2832; border: none; min-height: 1px; max-height: 1px; } QFrame#dropZone { background-color: #0D1219; border: 1px dashed #2B3745; border-radius: 13px; } QFrame#dropZone[dragActive="true"] { background-color: #111B17; border: 1px dashed #D7FF3F; } QLabel#appTitle { font-size: 22px; font-weight: 700; color: #F5F8FC; } QLabel#appSubtitle { color: #8491A2; font-size: 12px; } QLabel#cardTitle { font-size: 16px; font-weight: 700; color: #F1F5FA; } QLabel#sectionHintTitle { font-size: 15px; font-weight: 700; color: #F1F5FA; } QLabel#badge { background-color: #1C2631; border: 1px solid #293543; border-radius: 9px; color: #9EABBA; font-size: 11px; font-weight: 700; padding: 2px 8px; } QLabel#privacyChip { background-color: #142018; border: 1px solid #27402E; border-radius: 11px; color: #91D8A4; font-size: 11px; font-weight: 600; padding: 5px 10px; } QLabel#fieldLabel { color: #A8B4C3; font-size: 12px; font-weight: 600; } QLabel#hintLabel { color: #7B8898; font-size: 11px; } QLabel#qualityValue { color: #D7FF3F; font-size: 12px; font-weight: 700; } QLabel#emptyTitle { font-size: 17px; font-weight: 700; color: #EDF2F8; } QLabel#emptyText { color: #778595; font-size: 12px; } QLabel#statsLabel { color: #A9B5C4; font-size: 12px; } QLabel#outputLabel { color: #6F7D8E; font-size: 10px; font-weight: 700; } QLabel#percentLabel { color: #D7FF3F; font-size: 11px; font-weight: 700; min-width: 32px; } QLabel#footerText { color: #758294; font-size: 11px; } QLabel#engineLabel { color: #768496; font-size: 11px; } QLabel#successText { color: #8FE3A6; font-size: 11px; } QPushButton { background-color: #1A232D; border: 1px solid #2A3745; border-radius: 8px; padding: 7px 12px; color: #D9E1EA; font-size: 12px; font-weight: 600; } QPushButton:hover { background-color: #202B37; border-color: #3A4A5C; } QPushButton:pressed { background-color: #18212A; } QPushButton:disabled { background-color: #141A21; border-color: #202831; color: #586473; } QPushButton#ghostButton { background-color: transparent; border: 1px solid transparent; color: #8593A4; padding: 7px 9px; } QPushButton#ghostButton:hover { background-color: #19222C; color: #D9E1EA; } QPushButton#accentSmall { background-color: #D7FF3F; border: 1px solid #D7FF3F; border-radius: 9px; color: #101500; padding: 8px 18px; font-weight: 700; } QPushButton#accentSmall:hover { background-color: #E4FF70; border-color: #E4FF70; } QPushButton#primaryAction { background-color: #D7FF3F; border: 1px solid #D7FF3F; border-radius: 10px; color: #0D1203; font-size: 13px; font-weight: 700; padding: 11px 18px; min-height: 22px; } QPushButton#primaryAction:hover { background-color: #E5FF77; border-color: #E5FF77; } QPushButton#primaryAction:pressed { background-color: #C9F42F; } QPushButton#primaryAction:disabled { background-color: #222B20; border-color: #293523; color: #65705A; } QPushButton#cancelButton { background-color: transparent; border: 1px solid #4A2A2E; color: #E79A9A; border-radius: 10px; padding: 11px 16px; font-weight: 600; } QPushButton#cancelButton:hover { background-color: #221719; border-color: #784047; } QToolButton#destButton { background-color: #0E141B; border: 1px solid #25303C; border-radius: 8px; padding: 7px 26px 7px 10px; color: #B7C3D1; font-size: 12px; text-align: left; } QToolButton#destButton:hover { border-color: #3A4B5D; background-color: #121A23; } QToolButton#destButton::menu-indicator { image: url("__CHEVRON__"); subcontrol-origin: padding; subcontrol-position: center right; width: 14px; height: 14px; right: 8px; } QTabWidget#settingsTabs::pane { border: none; margin-top: 4px; } QTabWidget#settingsTabs QTabBar::tab { background-color: #0E141B; border: 1px solid #202A35; border-radius: 8px; color: #8694A5; padding: 7px 17px; margin-right: 5px; font-size: 12px; font-weight: 600; min-width: 62px; } QTabWidget#settingsTabs QTabBar::tab:hover { color: #DCE4ED; border-color: #334253; } QTabWidget#settingsTabs QTabBar::tab:selected { background-color: #D7FF3F; border-color: #D7FF3F; color: #101500; font-weight: 700; } QScrollArea#tabScroll { border: none; background-color: transparent; } QScrollArea#tabScroll > QWidget > QWidget { background-color: transparent; } QWidget#tabContent { background-color: transparent; } QComboBox, QSpinBox { background-color: #0D131A; border: 1px solid #25303D; border-radius: 8px; padding: 7px 10px; color: #DDE5EE; min-height: 20px; selection-background-color: #293644; } QComboBox:hover, QSpinBox:hover { border-color: #38495B; } QComboBox:focus, QSpinBox:focus { border-color: #D7FF3F; } QComboBox:disabled, QSpinBox:disabled { color: #5E6978; background-color: #10151C; border-color: #1C242D; } QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 26px; border: none; background: transparent; } QComboBox::down-arrow { image: url("__CHEVRON__"); width: 14px; height: 14px; } QComboBox QAbstractItemView { background-color: #141B24; border: 1px solid #2A3745; border-radius: 8px; padding: 4px; color: #DDE5EE; selection-background-color: #24313E; selection-color: #F4F8FC; outline: 0; } QSlider::groove:horizontal { height: 4px; background-color: #26313D; border-radius: 2px; } QSlider::sub-page:horizontal { background-color: #D7FF3F; border-radius: 2px; } QSlider::handle:horizontal { background-color: #F7FFE2; border: 3px solid #D7FF3F; width: 12px; height: 12px; margin: -7px 0; border-radius: 9px; } QSlider::handle:horizontal:hover { background-color: #FFFFFF; border-color: #E5FF77; } QSlider:disabled::sub-page:horizontal { background-color: #47533A; } QSlider:disabled::handle:horizontal { background-color: #87937B; border-color: #47533A; } QCheckBox { color: #AEBAC8; font-size: 12px; spacing: 9px; } QCheckBox::indicator { width: 16px; height: 16px; border: 1px solid #354250; border-radius: 4px; background-color: #0D131A; } QCheckBox::indicator:hover { border-color: #526679; } QCheckBox::indicator:checked { background-color: #D7FF3F; border-color: #D7FF3F; image: url("__CHECK__"); } QTreeWidget { background-color: transparent; border: none; outline: 0; show-decoration-selected: 1; alternate-background-color: transparent; selection-background-color: #19222C; } QTreeWidget::item { border-bottom: 1px solid #1A222B; padding: 0px; color: #D8E0E9; } QTreeWidget::item:hover { background-color: #151D26; } QTreeWidget::item:selected { background-color: #1B2530; color: #F2F6FA; } QTreeWidget::branch { background: transparent; } QHeaderView { background-color: transparent; } QHeaderView::section { background-color: transparent; border: none; border-bottom: 1px solid #202A34; color: #768496; font-size: 10px; font-weight: 700; padding: 0px 2px 9px 2px; } QHeaderView::section:first { padding-left: 8px; } QProgressBar { background-color: #232C37; border: none; border-radius: 3px; min-height: 6px; max-height: 6px; text-align: center; } QProgressBar::chunk { background-color: #D7FF3F; border-radius: 3px; } QScrollBar:vertical { background: transparent; width: 8px; margin: 2px 0px; } QScrollBar::handle:vertical { background: #2A3542; min-height: 28px; border-radius: 4px; } QScrollBar::handle:vertical:hover { background: #3C4A5A; } QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; } QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: transparent; } QScrollBar:horizontal { background: transparent; height: 8px; } QScrollBar::handle:horizontal { background: #2A3542; min-width: 28px; border-radius: 4px; } QMenu { background-color: #151C25; border: 1px solid #2A3745; border-radius: 8px; padding: 5px; } QMenu::item { background-color: transparent; color: #DCE4ED; padding: 7px 24px 7px 12px; border-radius: 5px; } QMenu::item:selected { background-color: #232F3C; color: #FFFFFF; } QMenu::item:disabled { color: #687483; } QMenu::separator { height: 1px; background-color: #27323E; margin: 5px 8px; } QToolTip { background-color: #1A222C; color: #E6ECF3; border: 1px solid #354250; padding: 6px 8px; } QMessageBox { background-color: #11161E; } QMessageBox QLabel { color: #E6ECF3; } """ return qss.replace("__CHEVRON__", chevron).replace("__CHECK__", check) def make_app_icon() -> QIcon: icon = QIcon() for size in (16, 24, 32, 48, 64, 128, 256, 512): icon.addPixmap(_render_logo(size)) return icon def app_logo_pixmap(size: int) -> QPixmap: return _render_logo(size) def _render_logo(size: int) -> QPixmap: pixmap = QPixmap(size, size) pixmap.fill(Qt.GlobalColor.transparent) painter = QPainter(pixmap) painter.setRenderHint(QPainter.RenderHint.Antialiasing, True) margin = size * 0.045 rect = QRectF(margin, margin, size - margin * 2, size - margin * 2) path = QPainterPath() path.addRoundedRect(rect, size * 0.235, size * 0.235) gradient = QLinearGradient(rect.topLeft(), rect.bottomRight()) gradient.setColorAt(0.0, QColor("#E5FF77")) gradient.setColorAt(0.55, QColor("#D7FF3F")) gradient.setColorAt(1.0, QColor("#B8EA22")) painter.fillPath(path, gradient) # A soft inner highlight gives the mark a little dimension. highlight = QPainterPath() highlight.addRoundedRect(QRectF(margin, margin, size - margin * 2, (size - margin * 2) * 0.48), size * 0.22, size * 0.22) painter.fillPath(highlight, QColor(255, 255, 255, 18)) points = [ (0.575, 0.135), (0.245, 0.555), (0.465, 0.555), (0.405, 0.875), (0.765, 0.445), (0.535, 0.445), ] bolt = QPolygonF([QPointF(x * size, y * size) for x, y in points]) painter.setPen(QPen(QColor("#142000"), max(1.0, size * 0.012), Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap, Qt.PenJoinStyle.RoundJoin)) painter.setBrush(QColor("#0B1005")) painter.drawPolygon(bolt) painter.end() return pixmap def media_icon(kind: MediaKind, size: int = 64) -> QIcon: pixmap = QPixmap(size, size) pixmap.fill(Qt.GlobalColor.transparent) painter = QPainter(pixmap) painter.setRenderHint(QPainter.RenderHint.Antialiasing, True) scale = size / 64.0 if kind == MediaKind.VIDEO: background = QColor("#202319") border = QColor("#3B4527") glyph = QColor(ACCENT) else: background = QColor("#15231D") border = QColor("#2A4639") glyph = QColor("#91E1B5") painter.setPen(QPen(border, 2.0 * scale)) painter.setBrush(background) painter.drawRoundedRect(QRectF(1.5 * scale, 1.5 * scale, 61 * scale, 61 * scale), 15 * scale, 15 * scale) if kind == MediaKind.VIDEO: triangle = QPolygonF( [ QPointF(25 * scale, 19 * scale), QPointF(48 * scale, 32 * scale), QPointF(25 * scale, 45 * scale), ] ) painter.setPen(Qt.PenStyle.NoPen) painter.setBrush(glyph) painter.drawPolygon(triangle) else: painter.setBrush(Qt.BrushStyle.NoBrush) painter.setPen(QPen(glyph, 3.2 * scale, Qt.PenStyle.SolidLine, Qt.PenCapStyle.RoundCap, Qt.PenJoinStyle.RoundJoin)) painter.drawEllipse(QRectF(17 * scale, 16 * scale, 9 * scale, 9 * scale)) mountain = QPainterPath() mountain.moveTo(13 * scale, 47 * scale) mountain.lineTo(27 * scale, 31 * scale) mountain.lineTo(36 * scale, 40 * scale) mountain.lineTo(43 * scale, 32 * scale) mountain.lineTo(52 * scale, 47 * scale) painter.drawPath(mountain) painter.setPen(QPen(glyph, 2.4 * scale)) painter.drawRoundedRect(QRectF(11 * scale, 13 * scale, 42 * scale, 38 * scale), 6 * scale, 6 * scale) painter.end() return QIcon(pixmap)