610 lines
14 KiB
Python
610 lines
14 KiB
Python
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)
|