Tema clar/fosc segons el sistema i títol de finestra

This commit is contained in:
2026-05-29 21:07:00 +02:00
parent b71df66e22
commit 9d13c2434b
4 changed files with 131 additions and 6 deletions
+3
View File
@@ -9,11 +9,14 @@ from PySide6.QtWidgets import QApplication, QMessageBox
from .config import load_config
from .paths import config_file, data_root
from .ui.main_window import MainWindow
from .ui.theme import apply_theme, watch_system_theme
def main() -> int:
app = QApplication(sys.argv)
app.setApplicationName("jlauncher")
apply_theme(app)
watch_system_theme(app)
cfg_path = config_file()
try:
+3 -2
View File
@@ -5,7 +5,7 @@ from __future__ import annotations
from pathlib import Path
from PySide6.QtCore import Qt, Signal
from PySide6.QtGui import QPixmap
from PySide6.QtGui import QPalette, QPixmap
from PySide6.QtWidgets import (
QFrame,
QHBoxLayout,
@@ -52,7 +52,8 @@ class GameRow(QFrame):
self.name_label.setStyleSheet("font-weight: bold; font-size: 14px;")
self.desc_label = QLabel("")
self.desc_label.setWordWrap(True)
self.desc_label.setStyleSheet("color: #aaaaaa;")
# Texto atenuado que sigue la paleta (claro/oscuro) en vez de un gris fijo.
self.desc_label.setForegroundRole(QPalette.PlaceholderText)
self.status_label = QLabel("")
self.status_label.setStyleSheet("color: #6fae6f; font-size: 11px;")
text_box.addWidget(self.name_label)
+5 -4
View File
@@ -18,6 +18,9 @@ from ..config import Config, Game
from ..workers import DownloadWorker, RunWorker
from .game_row import GameRow
APP_NAME = "Jail Launcher"
WINDOW_TITLE = f"© 2026 {APP_NAME} — JailDesigner"
class MainWindow(QMainWindow):
def __init__(self, config: Config, root: Path, parent=None) -> None:
@@ -31,7 +34,7 @@ class MainWindow(QMainWindow):
# llegue al hilo principal, y la UI nunca se refresca.
self._workers: set = set()
self.setWindowTitle("jlauncher — Juegos jailgames")
self.setWindowTitle(WINDOW_TITLE)
self.resize(720, 640)
splitter = QSplitter(Qt.Vertical)
@@ -58,9 +61,7 @@ class MainWindow(QMainWindow):
self.log_view = QPlainTextEdit()
self.log_view.setReadOnly(True)
self.log_view.setMaximumBlockCount(5000)
self.log_view.setStyleSheet(
"font-family: monospace; font-size: 11px; background:#1e1e1e; color:#d4d4d4;"
)
self.log_view.setStyleSheet("font-family: monospace; font-size: 11px;")
splitter.addWidget(self.log_view)
splitter.setStretchFactor(0, 3)
splitter.setStretchFactor(1, 1)
+120
View File
@@ -0,0 +1,120 @@
"""Tema claro/oscuro siguiendo el esquema de color del sistema.
Usa el estilo Fusion (consistente entre plataformas) y aplica una paleta clara u
oscura según ``QStyleHints.colorScheme()``. Los widgets que no fuerzan colores
propios (consola de log, fondos) siguen automáticamente esta paleta.
"""
from __future__ import annotations
import subprocess
from PySide6.QtCore import Qt
from PySide6.QtGui import QColor, QPalette
from PySide6.QtWidgets import QApplication
def _portal_is_dark() -> bool | None:
"""Consulta el portal XDG (org.freedesktop.appearance color-scheme).
Devuelve True (1=dark), False (2=light) o None (0=sin preferencia / no disponible).
Es la fuente más fiable en Linux cuando Qt no detecta el esquema.
"""
try:
out = subprocess.run(
[
"gdbus", "call", "--session",
"--dest", "org.freedesktop.portal.Desktop",
"--object-path", "/org/freedesktop/portal/desktop",
"--method", "org.freedesktop.portal.Settings.Read",
"org.freedesktop.appearance", "color-scheme",
],
capture_output=True, text=True, timeout=2,
)
except (OSError, subprocess.SubprocessError):
return None
if out.returncode != 0:
return None
if "uint32 1" in out.stdout:
return True
if "uint32 2" in out.stdout:
return False
return None
def _gsettings_is_dark() -> bool | None:
"""Respaldo: gsettings color-scheme de GNOME ('prefer-dark')."""
try:
out = subprocess.run(
["gsettings", "get", "org.gnome.desktop.interface", "color-scheme"],
capture_output=True, text=True, timeout=2,
)
except (OSError, subprocess.SubprocessError):
return None
if out.returncode != 0:
return None
return "dark" in out.stdout.lower()
def system_is_dark(app: QApplication) -> bool:
"""True si el sistema está en modo oscuro.
Prioriza el aviso de Qt; si Qt dice claro (o no soporta la API), consulta el
portal XDG y gsettings, que en sesiones xcb suelen ser más fiables que Qt.
"""
hints = app.styleHints()
if hasattr(hints, "colorScheme"):
try:
if hints.colorScheme() == Qt.ColorScheme.Dark:
return True
except Exception: # noqa: BLE001 - APIs viejas/raras: caer al fallback
pass
for probe in (_portal_is_dark, _gsettings_is_dark):
val = probe()
if val is not None:
return val
return app.palette().color(QPalette.Window).lightness() < 128
def _dark_palette() -> QPalette:
p = QPalette()
window = QColor(0x2b, 0x2b, 0x2b)
base = QColor(0x1e, 0x1e, 0x1e)
alt = QColor(0x35, 0x35, 0x35)
text = QColor(0xdc, 0xdc, 0xdc)
disabled = QColor(0x7f, 0x7f, 0x7f)
highlight = QColor(0x2a, 0x82, 0xda)
p.setColor(QPalette.Window, window)
p.setColor(QPalette.WindowText, text)
p.setColor(QPalette.Base, base)
p.setColor(QPalette.AlternateBase, alt)
p.setColor(QPalette.ToolTipBase, window)
p.setColor(QPalette.ToolTipText, text)
p.setColor(QPalette.Text, text)
p.setColor(QPalette.Button, alt)
p.setColor(QPalette.ButtonText, text)
p.setColor(QPalette.BrightText, Qt.red)
p.setColor(QPalette.Link, highlight)
p.setColor(QPalette.Highlight, highlight)
p.setColor(QPalette.HighlightedText, Qt.black)
p.setColor(QPalette.PlaceholderText, disabled)
for role in (QPalette.WindowText, QPalette.Text, QPalette.ButtonText):
p.setColor(QPalette.Disabled, role, disabled)
return p
def apply_theme(app: QApplication) -> None:
"""Aplica estilo Fusion + paleta acorde al esquema del sistema."""
app.setStyle("Fusion")
if system_is_dark(app):
app.setPalette(_dark_palette())
else:
app.setPalette(app.style().standardPalette())
def watch_system_theme(app: QApplication) -> None:
"""Re-aplica el tema cuando el sistema cambia entre claro/oscuro en caliente."""
hints = app.styleHints()
if hasattr(hints, "colorSchemeChanged"):
hints.colorSchemeChanged.connect(lambda _scheme: apply_theme(app))