From af6c42420733b345510a4492bc01e927bc1cdce7 Mon Sep 17 00:00:00 2001 From: kramo Date: Sat, 29 Nov 2025 11:20:34 +0100 Subject: [PATCH] games: Load cover art --- cartridges/games.py | 30 +++++++++++++++---- cartridges/ui/style.css | 8 +++++ cartridges/ui/ui.gresource.xml.in | 1 + cartridges/ui/window.blp | 49 +++++++++++++++++++++++-------- pyproject.toml | 1 + 5 files changed, 72 insertions(+), 17 deletions(-) create mode 100644 cartridges/ui/style.css diff --git a/cartridges/games.py b/cartridges/games.py index 3490250..0c8ed47 100644 --- a/cartridges/games.py +++ b/cartridges/games.py @@ -1,18 +1,22 @@ # SPDX-License-Identifier: GPL-3.0-or-later # SPDX-FileCopyrightText: Copyright 2025 Zoey Ahmed +# SPDX-FileCopyrightText: Copyright 2025 kramo from collections.abc import Generator from pathlib import Path +from typing import cast from gi.repository import ( + Gdk, Gio, GLib, GObject, Json, # pyright: ignore[reportAttributeAccessIssue, reportUnknownVariableType] ) - -GAMES_DIR = Path(GLib.get_user_data_dir(), "cartridges", "games") +DATA_DIR = Path(GLib.get_user_data_dir(), "cartridges") +GAMES_DIR = DATA_DIR / "games" +COVERS_DIR = DATA_DIR / "covers" class Game(GObject.Object): @@ -32,16 +36,32 @@ class Game(GObject.Object): blacklisted = GObject.Property(type=bool, default=False) version = GObject.Property(type=float, default=2.0) + cover = GObject.Property(type=Gdk.Texture) + def load() -> Generator[Game]: """Load the user's games from disk.""" - for game in GAMES_DIR.iterdir(): - data = game.read_text("utf-8") + for path in GAMES_DIR.glob("*.json"): try: - yield Json.gobject_from_data(Game, data, len(data)) + data = path.read_text("utf-8") + except UnicodeError: + continue + + try: + game = cast(Game, Json.gobject_from_data(Game, data, len(data))) except GLib.Error: continue + cover_path = COVERS_DIR / game.game_id + for ext in ".gif", ".tiff": + filename = str(cover_path.with_suffix(ext)) + try: + game.cover = Gdk.Texture.new_from_filename(filename) + except GLib.Error: + continue + + yield game + model = Gio.ListStore.new(Game) model.splice(0, 0, tuple(load())) diff --git a/cartridges/ui/style.css b/cartridges/ui/style.css new file mode 100644 index 0000000..4f0e2d8 --- /dev/null +++ b/cartridges/ui/style.css @@ -0,0 +1,8 @@ +#grid { + padding: 6px 12px 12px; +} + +#grid > child { + padding: 12px; + border-radius: 24px; +} diff --git a/cartridges/ui/ui.gresource.xml.in b/cartridges/ui/ui.gresource.xml.in index 1294c80..b57587a 100644 --- a/cartridges/ui/ui.gresource.xml.in +++ b/cartridges/ui/ui.gresource.xml.in @@ -2,5 +2,6 @@ window.ui + style.css diff --git a/cartridges/ui/window.blp b/cartridges/ui/window.blp index f38d39c..0c2d997 100644 --- a/cartridges/ui/window.blp +++ b/cartridges/ui/window.blp @@ -3,8 +3,8 @@ using Adw 1; template $Window: Adw.ApplicationWindow { title: _("Cartridges"); - default-width: 800; - default-height: 600; + default-width: 920; + default-height: 700; ShortcutController { Shortcut { @@ -28,18 +28,43 @@ template $Window: Adw.ApplicationWindow { } } - content: GridView { - model: NoSelection { - model: bind template.games; - }; + content: ScrolledWindow { + child: GridView { + name: "grid"; - factory: BuilderListItemFactory { - template ListItem { - child: Label { - label: bind template.item as <$Game>.name; - }; - } + model: NoSelection { + model: bind template.games; + }; + + factory: BuilderListItemFactory { + template ListItem { + child: Box { + orientation: vertical; + spacing: 12; + + Picture { + paintable: bind template.item as <$Game>.cover; + width-request: 200; + height-request: 300; + halign: center; + + styles [ + "card", + ] + } + + Label { + label: bind template.item as <$Game>.name; + ellipsize: middle; + } + }; + } + }; }; }; + + styles [ + "view", + ] }; } diff --git a/pyproject.toml b/pyproject.toml index 828d03d..6859fd0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,6 +56,7 @@ ignore = [ "Q002", "Q003", "S310", + "TC006", "TD", "TRY301", "W191",