Initial commit
This commit is contained in:
140
app/theme.py
Normal file
140
app/theme.py
Normal 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)
|
||||
Reference in New Issue
Block a user