Initial commit

This commit is contained in:
2026-10-09 02:48:24 +02:00
commit e05d973a8d
133 changed files with 101507 additions and 0 deletions

140
app/theme.py Normal file
View File

@@ -0,0 +1,140 @@
"""The app look: neutral dark greys, one dark red accent, the wallpaper behind the filter panel.
`apply(app)` sets the style for the whole application; `Sidebar` is the filter panel, painted over the wallpaper
(app/assets/wallpaper.jpg) when the background is on.
"""
from PySide6.QtCore import Qt
from PySide6.QtGui import QColor, QImage, QPainter, QPalette
from PySide6.QtWidgets import QWidget
from . import icons
ACCENT = "#b0242c"
ACCENT_HI = "#cf3a42"
STEEL = "#c4c4c4" # the original waveform: neutral light grey
TEXT = "#e6e6e6"
MUTED = "#9a9a9a"
BG = "#141414" # dialogs
PANEL_BG = "#0c0c0c" # main window: behind the filter and editor panels (darker for contrast)
SURFACE = "#1b1b1b"
BORDER = "#2c2c2c"
CONTROL = "#3a3a3a"
QSS = """
QWidget { color: %(TEXT)s; }
QMainWindow { background: %(PANEL_BG)s; }
QDialog { background: %(BG)s; }
QToolTip { background: #202020; color: %(TEXT)s; border: 1px solid %(CONTROL)s; padding: 4px; }
QToolBar { background: #0f0f0f; border: none; border-bottom: 1px solid %(BORDER)s; spacing: 6px; padding: 4px 6px; }
QToolBar QToolButton { color: %(TEXT)s; padding: 4px 9px; border-radius: 4px; }
QToolBar QToolButton:hover { background: #262626; }
QLabel#AppTitle { color: %(TEXT)s; font-weight: bold; padding: 0 10px 0 2px; }
QStatusBar { background: #0f0f0f; color: %(MUTED)s; border-top: 1px solid %(BORDER)s; }
QStatusBar QLabel { color: %(MUTED)s; }
QWidget#Sidebar { background: transparent; border-right: 1px solid %(BORDER)s; }
QWidget#EditorPanel, QScrollArea, QScrollArea > QWidget > QWidget { background: transparent; }
QWidget#Banner { background: #3a1416; border-bottom: 1px solid %(ACCENT)s; }
QLabel#EditorTitle { color: %(TEXT)s; }
QLabel#Muted { color: %(MUTED)s; }
QTableView { background: %(SURFACE)s; alternate-background-color: #202020; gridline-color: %(BORDER)s;
border: 1px solid %(BORDER)s; selection-background-color: rgba(176,36,44,70); selection-color: #ffffff; }
QTableView::item { padding: 0 4px; }
QHeaderView::section { background: #171717; color: %(MUTED)s; border: none; border-right: 1px solid %(BORDER)s;
border-bottom: 1px solid %(CONTROL)s; padding: 4px 6px; font-weight: bold; }
QTableCornerButton::section { background: #171717; border: none; }
QGroupBox { background: %(SURFACE)s; border: 1px solid %(BORDER)s; border-radius: 6px;
margin-top: 16px; padding: 10px 6px 6px 6px; }
QGroupBox::title { subcontrol-origin: margin; left: 12px; padding: 0 6px; color: %(MUTED)s; font-weight: bold; }
QPushButton { background: #242424; border: 1px solid %(CONTROL)s; border-radius: 4px; padding: 5px 12px; }
QPushButton:hover { background: #2c2c2c; border-color: #4a4a4a; }
QPushButton:pressed, QPushButton:checked { background: rgba(176,36,44,45); border-color: %(ACCENT)s; }
QPushButton:disabled { color: #5c5c5c; border-color: #2a2a2a; background: #1c1c1c; }
QPushButton#Primary { background: %(ACCENT)s; color: #ffffff; font-weight: bold; border: 1px solid %(ACCENT)s; }
QPushButton#Primary:hover { background: %(ACCENT_HI)s; border-color: %(ACCENT_HI)s; }
QPushButton#Primary:disabled { background: #3a1c1e; color: #8a6466; border-color: #3a1c1e; }
QLineEdit, QComboBox, QDoubleSpinBox, QPlainTextEdit, QListWidget, QTreeWidget {
background: #101010; border: 1px solid %(CONTROL)s; border-radius: 4px; padding: 3px 6px;
selection-background-color: rgba(176,36,44,120); }
QLineEdit:focus, QComboBox:focus, QDoubleSpinBox:focus, QPlainTextEdit:focus { border-color: %(ACCENT)s; }
QComboBox QAbstractItemView { background: #1b1b1b; border: 1px solid %(CONTROL)s;
selection-background-color: rgba(176,36,44,90); }
QCheckBox::indicator { width: 14px; height: 14px; border: 1px solid #555555; border-radius: 3px; background: #101010; }
QCheckBox::indicator:checked { background: %(ACCENT)s; border-color: %(ACCENT)s; }
QProgressBar { background: #101010; border: 1px solid %(CONTROL)s; border-radius: 3px; height: 10px; }
QProgressBar::chunk { background: %(ACCENT)s; border-radius: 2px; }
QSlider::groove:horizontal { height: 4px; background: #333333; border-radius: 2px; }
QSlider::sub-page:horizontal { background: %(ACCENT)s; border-radius: 2px; }
QSlider::handle:horizontal { background: %(TEXT)s; width: 12px; margin: -5px 0; border-radius: 6px; }
QSplitter::handle { background: %(BORDER)s; }
QScrollBar:vertical, QScrollBar:horizontal { background: transparent; border: none; margin: 0; }
QScrollBar:vertical { width: 10px; } QScrollBar:horizontal { height: 10px; }
QScrollBar::handle { background: #3c3c3c; border-radius: 4px; min-height: 24px; min-width: 24px; }
QScrollBar::handle:hover { background: #555555; }
QScrollBar::add-line, QScrollBar::sub-line { width: 0; height: 0; }
QMenu { background: #1b1b1b; border: 1px solid %(CONTROL)s; }
QMenu::item { padding: 5px 22px 5px 10px; }
QMenu::item:selected { background: rgba(176,36,44,70); }
""" % dict(TEXT=TEXT, BG=BG, PANEL_BG=PANEL_BG, ACCENT=ACCENT, ACCENT_HI=ACCENT_HI, MUTED=MUTED, BORDER=BORDER, SURFACE=SURFACE,
CONTROL=CONTROL)
# waveform colours: original / installed / replacement
WAVE = {"orig": QColor(STEEL), "inst": QColor("#8f4a4e"), "new": QColor(ACCENT)}
WAVE_BG = QColor("#111111")
SIDEBAR_VEIL = QColor(16, 16, 16) # alpha follows the background strength
def apply(app):
app.setStyle("Fusion")
pal = QPalette()
for role, col in [(QPalette.ColorRole.Window, BG), (QPalette.ColorRole.WindowText, TEXT),
(QPalette.ColorRole.Base, "#101010"), (QPalette.ColorRole.AlternateBase, "#202020"),
(QPalette.ColorRole.Text, TEXT), (QPalette.ColorRole.Button, "#242424"),
(QPalette.ColorRole.ButtonText, TEXT), (QPalette.ColorRole.Highlight, ACCENT),
(QPalette.ColorRole.HighlightedText, "#ffffff"), (QPalette.ColorRole.ToolTipBase, "#202020"),
(QPalette.ColorRole.ToolTipText, TEXT), (QPalette.ColorRole.PlaceholderText, MUTED),
(QPalette.ColorRole.Mid, "#333333"), (QPalette.ColorRole.Link, ACCENT)]:
pal.setColor(role, QColor(col))
pal.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, QColor("#5c5c5c"))
pal.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, QColor("#5c5c5c"))
app.setPalette(pal)
app.setStyleSheet(QSS)
def wallpaper():
"""The Arkham City wallpaper (app/assets/wallpaper.jpg) as a QImage, or None if the assets are missing."""
p = icons.asset("wallpaper.jpg")
img = QImage(p) if p else QImage()
return None if img.isNull() else img
class Sidebar(QWidget):
"""Filter panel with the wallpaper behind it: scaled to the panel height, its left part fills the panel."""
def __init__(self, parent=None):
super().__init__(parent)
self.setObjectName("Sidebar")
self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True)
self.image = None
self.opacity = 0.1
self._scaled = None
def configure(self, settings):
st = settings or {}
self.image = wallpaper() if st.get("background_enabled", True) else None
self.opacity = max(0.0, min(1.0, float(st.get("background_opacity", 0.1))))
self._scaled = None
self.update()
def paintEvent(self, e):
if self.image is not None:
if self._scaled is None or self._scaled.height() != self.height():
self._scaled = self.image.scaledToHeight(self.height(), Qt.TransformationMode.SmoothTransformation)
p = QPainter(self)
p.setOpacity(self.opacity)
p.drawImage(0, 0, self._scaled)
p.setOpacity(1.0)
veil = QColor(SIDEBAR_VEIL)
veil.setAlpha(int(180 * self.opacity)) # stronger image, stronger veil: labels stay readable
p.fillRect(self.rect(), veil)
p.end()
super().paintEvent(e)