La fila és el botó: clic descarrega/actualitza o juga, amb text d'acció
This commit is contained in:
+65
-58
@@ -1,17 +1,23 @@
|
|||||||
"""Fila de la lista: icono, nombre, descripción y botones Download/Run."""
|
"""Fila de la llista: la fila sencera actua com un botó.
|
||||||
|
|
||||||
|
Un clic fa l'acció principal segons l'estat:
|
||||||
|
- no descarregat → descarrega
|
||||||
|
- update pendent → actualitza
|
||||||
|
- descarregat i al dia → juga
|
||||||
|
Un indicador d'icona a la dreta (passiu) mostra què farà el clic. L'esborrat es
|
||||||
|
gestiona per fora (mètode a definir); aquest widget només exposa la senyal.
|
||||||
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from PySide6.QtCore import QSize, Qt, Signal
|
from PySide6.QtCore import Qt, Signal
|
||||||
from PySide6.QtGui import QPalette, QPixmap
|
from PySide6.QtGui import QPalette, QPixmap
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QFrame,
|
QFrame,
|
||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
QLabel,
|
QLabel,
|
||||||
QPushButton,
|
|
||||||
QStyle,
|
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,38 +29,43 @@ ICON_SIZE = 64
|
|||||||
|
|
||||||
|
|
||||||
class GameRow(QFrame):
|
class GameRow(QFrame):
|
||||||
"""Widget de una fila. Emite señales cuando se pulsan los botones."""
|
"""Fila clicable. Emet `activated` en clic i `delete_requested` per al futur esborrat."""
|
||||||
|
|
||||||
download_requested = Signal(object) # Game
|
activated = Signal(object) # Game — clic sobre la fila (acció principal)
|
||||||
run_requested = Signal(object) # Game
|
delete_requested = Signal(object) # Game — esborrar la descàrrega local
|
||||||
delete_requested = Signal(object) # Game
|
|
||||||
|
|
||||||
def __init__(self, game: Game, root: Path, parent=None) -> None:
|
def __init__(self, game: Game, root: Path, parent=None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.game = game
|
self.game = game
|
||||||
self.root = root
|
self.root = root
|
||||||
self._update_available = False
|
self._update_available = False
|
||||||
|
self._busy = False
|
||||||
self.setObjectName("gameRow")
|
self.setObjectName("gameRow")
|
||||||
self.setFrameShape(QFrame.StyledPanel)
|
self.setFrameShape(QFrame.StyledPanel)
|
||||||
|
# La fila és un botó: cursor de mà i ressaltat en passar el ratolí.
|
||||||
|
self.setCursor(Qt.PointingHandCursor)
|
||||||
|
self.setStyleSheet(
|
||||||
|
"QFrame#gameRow:hover { background: rgba(127, 127, 127, 0.15); }"
|
||||||
|
)
|
||||||
|
|
||||||
layout = QHBoxLayout(self)
|
layout = QHBoxLayout(self)
|
||||||
layout.setContentsMargins(10, 8, 10, 8)
|
layout.setContentsMargins(10, 8, 10, 8)
|
||||||
layout.setSpacing(12)
|
layout.setSpacing(12)
|
||||||
|
|
||||||
# --- Icono ---
|
# --- Icona del joc ---
|
||||||
self.icon_label = QLabel()
|
self.icon_label = QLabel()
|
||||||
self.icon_label.setFixedSize(ICON_SIZE, ICON_SIZE)
|
self.icon_label.setFixedSize(ICON_SIZE, ICON_SIZE)
|
||||||
self.icon_label.setAlignment(Qt.AlignCenter)
|
self.icon_label.setAlignment(Qt.AlignCenter)
|
||||||
layout.addWidget(self.icon_label)
|
layout.addWidget(self.icon_label)
|
||||||
|
|
||||||
# --- Texto (nombre + descripción + estado) ---
|
# --- Text (nom + descripció + estat) ---
|
||||||
text_box = QVBoxLayout()
|
text_box = QVBoxLayout()
|
||||||
text_box.setSpacing(2)
|
text_box.setSpacing(2)
|
||||||
self.name_label = QLabel(game.name)
|
self.name_label = QLabel(game.name)
|
||||||
self.name_label.setStyleSheet("font-weight: bold; font-size: 14px;")
|
self.name_label.setStyleSheet("font-weight: bold; font-size: 14px;")
|
||||||
self.desc_label = QLabel("")
|
self.desc_label = QLabel("")
|
||||||
self.desc_label.setWordWrap(True)
|
self.desc_label.setWordWrap(True)
|
||||||
# Texto atenuado que sigue la paleta (claro/oscuro) en vez de un gris fijo.
|
# Text atenuat que segueix la paleta (clar/fosc) en lloc d'un gris fix.
|
||||||
self.desc_label.setForegroundRole(QPalette.PlaceholderText)
|
self.desc_label.setForegroundRole(QPalette.PlaceholderText)
|
||||||
self.status_label = QLabel("")
|
self.status_label = QLabel("")
|
||||||
self.status_label.setStyleSheet("color: #6fae6f; font-size: 11px;")
|
self.status_label.setStyleSheet("color: #6fae6f; font-size: 11px;")
|
||||||
@@ -64,47 +75,51 @@ class GameRow(QFrame):
|
|||||||
text_box.addStretch(1)
|
text_box.addStretch(1)
|
||||||
layout.addLayout(text_box, stretch=1)
|
layout.addLayout(text_box, stretch=1)
|
||||||
|
|
||||||
# --- Botons (icones compactes amb tooltip; visibilitat segons l'estat) ---
|
# --- Indicador d'acció (passiu, text): què farà el clic ---
|
||||||
style = self.style()
|
self.action_label = QLabel()
|
||||||
self._icon_download = style.standardIcon(QStyle.SP_ArrowDown)
|
self.action_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||||||
self._icon_update = style.standardIcon(QStyle.SP_BrowserReload)
|
self.action_label.setMinimumWidth(96)
|
||||||
|
layout.addWidget(self.action_label)
|
||||||
|
|
||||||
self.download_btn = QPushButton()
|
# Els fills no intercepten el ratolí: tot clic arriba a la fila.
|
||||||
self.run_btn = QPushButton()
|
for child in (
|
||||||
self.run_btn.setIcon(style.standardIcon(QStyle.SP_MediaPlay))
|
self.icon_label,
|
||||||
self.run_btn.setToolTip("Juga")
|
self.name_label,
|
||||||
self.delete_btn = QPushButton()
|
self.desc_label,
|
||||||
self.delete_btn.setIcon(style.standardIcon(QStyle.SP_TrashIcon))
|
self.status_label,
|
||||||
self.delete_btn.setToolTip("Esborra")
|
self.action_label,
|
||||||
for btn in (self.download_btn, self.run_btn, self.delete_btn):
|
):
|
||||||
btn.setFixedSize(36, 30)
|
child.setAttribute(Qt.WA_TransparentForMouseEvents)
|
||||||
btn.setIconSize(QSize(18, 18))
|
|
||||||
self.download_btn.clicked.connect(lambda: self.download_requested.emit(self.game))
|
|
||||||
self.run_btn.clicked.connect(lambda: self.run_requested.emit(self.game))
|
|
||||||
self.delete_btn.clicked.connect(lambda: self.delete_requested.emit(self.game))
|
|
||||||
|
|
||||||
btn_box = QHBoxLayout()
|
|
||||||
btn_box.setSpacing(4)
|
|
||||||
btn_box.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
|
||||||
btn_box.addWidget(self.download_btn)
|
|
||||||
btn_box.addWidget(self.run_btn)
|
|
||||||
btn_box.addWidget(self.delete_btn)
|
|
||||||
layout.addLayout(btn_box)
|
|
||||||
|
|
||||||
self.refresh()
|
self.refresh()
|
||||||
|
|
||||||
# ----------------------------------------------------------------- estado
|
# ------------------------------------------------------------- clic fila
|
||||||
|
|
||||||
|
def mouseReleaseEvent(self, event) -> None:
|
||||||
|
if (
|
||||||
|
event.button() == Qt.LeftButton
|
||||||
|
and not self._busy
|
||||||
|
and self.rect().contains(event.position().toPoint())
|
||||||
|
):
|
||||||
|
self.activated.emit(self.game)
|
||||||
|
super().mouseReleaseEvent(event)
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------- estat
|
||||||
|
|
||||||
def is_installed(self) -> bool:
|
def is_installed(self) -> bool:
|
||||||
return (repo_dir(self.root, self.game.id) / ".git").exists()
|
return (repo_dir(self.root, self.game.id) / ".git").exists()
|
||||||
|
|
||||||
|
def primary_action_is_download(self) -> bool:
|
||||||
|
"""True si el clic ha de descarregar/actualitzar; False si ha de jugar."""
|
||||||
|
return (not self.is_installed()) or self._update_available
|
||||||
|
|
||||||
def set_update_available(self, available: bool) -> None:
|
def set_update_available(self, available: bool) -> None:
|
||||||
"""Marca/desmarca la fila como 'tiene actualización pendiente'."""
|
"""Marca/desmarca la fila com a 'té actualització pendent'."""
|
||||||
self._update_available = available
|
self._update_available = available
|
||||||
self.refresh()
|
self.refresh()
|
||||||
|
|
||||||
def refresh(self) -> None:
|
def refresh(self) -> None:
|
||||||
"""Recarga icono, descripción y estado desde la cache local."""
|
"""Recarrega icona, descripció i estat des de la cache local."""
|
||||||
meta = load_meta(self.root, self.game.id)
|
meta = load_meta(self.root, self.game.id)
|
||||||
self._set_icon()
|
self._set_icon()
|
||||||
self.desc_label.setText(meta.description or "(sense descripció encara)")
|
self.desc_label.setText(meta.description or "(sense descripció encara)")
|
||||||
@@ -128,40 +143,32 @@ class GameRow(QFrame):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _set_status(self, meta: GameMeta) -> None:
|
def _set_status(self, meta: GameMeta) -> None:
|
||||||
installed = self.is_installed()
|
if not self.is_installed():
|
||||||
|
|
||||||
# Visibilitat dels botons segons l'estat:
|
|
||||||
# - Juga / Esborra: només si està descarregat.
|
|
||||||
# - Descarrega: si no està descarregat o hi ha update pendent.
|
|
||||||
self.run_btn.setVisible(installed)
|
|
||||||
self.delete_btn.setVisible(installed)
|
|
||||||
self.download_btn.setVisible((not installed) or self._update_available)
|
|
||||||
if installed and self._update_available:
|
|
||||||
self.download_btn.setIcon(self._icon_update)
|
|
||||||
self.download_btn.setToolTip("Actualitza")
|
|
||||||
else:
|
|
||||||
self.download_btn.setIcon(self._icon_download)
|
|
||||||
self.download_btn.setToolTip("Descarrega")
|
|
||||||
|
|
||||||
if not installed:
|
|
||||||
self.status_label.setText("No descarregat")
|
self.status_label.setText("No descarregat")
|
||||||
self.status_label.setStyleSheet("color: #cc8855; font-size: 11px;")
|
self.status_label.setStyleSheet("color: #cc8855; font-size: 11px;")
|
||||||
|
self._set_action("Descarrega", "#4a90d9")
|
||||||
elif self._update_available:
|
elif self._update_available:
|
||||||
self.status_label.setText("⬆ Actualització disponible")
|
self.status_label.setText("⬆ Actualització disponible")
|
||||||
self.status_label.setStyleSheet(
|
self.status_label.setStyleSheet(
|
||||||
"color: #e0a030; font-weight: bold; font-size: 11px;"
|
"color: #e0a030; font-weight: bold; font-size: 11px;"
|
||||||
)
|
)
|
||||||
|
self._set_action("Actualitza", "#e0a030")
|
||||||
else:
|
else:
|
||||||
ver = f" {meta.version}" if meta.version else ""
|
ver = f" {meta.version}" if meta.version else ""
|
||||||
self.status_label.setText(f"Descarregat{ver}")
|
self.status_label.setText(f"Descarregat{ver}")
|
||||||
self.status_label.setStyleSheet("color: #6fae6f; font-size: 11px;")
|
self.status_label.setStyleSheet("color: #6fae6f; font-size: 11px;")
|
||||||
|
self._set_action("Juga", "#6fae6f")
|
||||||
|
|
||||||
|
def _set_action(self, text: str, color: str) -> None:
|
||||||
|
self.action_label.setText(text)
|
||||||
|
self.action_label.setStyleSheet(
|
||||||
|
f"color: {color}; font-weight: bold; font-size: 13px;"
|
||||||
|
)
|
||||||
|
|
||||||
# --------------------------------------------------------------- busy UI
|
# --------------------------------------------------------------- busy UI
|
||||||
|
|
||||||
def set_busy(self, busy: bool, message: str = "") -> None:
|
def set_busy(self, busy: bool, message: str = "") -> None:
|
||||||
self.download_btn.setEnabled(not busy)
|
self._busy = busy
|
||||||
self.run_btn.setEnabled(not busy)
|
|
||||||
self.delete_btn.setEnabled(not busy)
|
|
||||||
if busy and message:
|
if busy and message:
|
||||||
self.status_label.setText(message)
|
self.status_label.setText(message)
|
||||||
self.status_label.setStyleSheet("color: #d0d060; font-size: 11px;")
|
self.status_label.setStyleSheet("color: #d0d060; font-size: 11px;")
|
||||||
|
|||||||
@@ -53,8 +53,7 @@ class MainWindow(QMainWindow):
|
|||||||
list_layout.setSpacing(6)
|
list_layout.setSpacing(6)
|
||||||
for game in config.games:
|
for game in config.games:
|
||||||
row = GameRow(game, root)
|
row = GameRow(game, root)
|
||||||
row.download_requested.connect(self._on_download)
|
row.activated.connect(self._on_activate)
|
||||||
row.run_requested.connect(self._on_run)
|
|
||||||
row.delete_requested.connect(self._on_delete)
|
row.delete_requested.connect(self._on_delete)
|
||||||
self.rows[game.id] = row
|
self.rows[game.id] = row
|
||||||
list_layout.addWidget(row)
|
list_layout.addWidget(row)
|
||||||
@@ -151,6 +150,14 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
# --------------------------------------------------------------- accions
|
# --------------------------------------------------------------- accions
|
||||||
|
|
||||||
|
def _on_activate(self, game: Game) -> None:
|
||||||
|
"""Clic sobre la fila: descarrega/actualitza si cal, si no juga."""
|
||||||
|
row = self.rows[game.id]
|
||||||
|
if row.primary_action_is_download():
|
||||||
|
self._on_download(game)
|
||||||
|
else:
|
||||||
|
self._on_run(game)
|
||||||
|
|
||||||
def _on_download(self, game: Game) -> None:
|
def _on_download(self, game: Game) -> None:
|
||||||
row = self.rows[game.id]
|
row = self.rows[game.id]
|
||||||
row.set_busy(True, "Descarregant…")
|
row.set_busy(True, "Descarregant…")
|
||||||
|
|||||||
Reference in New Issue
Block a user