collections: Support adding games
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# SPDX-FileCopyrightText: Copyright 2025 Jamie Gravendeel
|
||||
|
||||
from typing import Any, override
|
||||
from collections.abc import Iterable
|
||||
from typing import Any, cast, override
|
||||
|
||||
from gi.repository import Adw, GObject, Gtk
|
||||
|
||||
@@ -54,6 +55,76 @@ class CollectionSidebarItem(Adw.SidebarItem): # pyright: ignore[reportAttribute
|
||||
self.bind_property("title", self, "tooltip", GObject.BindingFlags.SYNC_CREATE)
|
||||
|
||||
|
||||
class CollectionButton(Gtk.ToggleButton):
|
||||
"""A toggle button representing a collection."""
|
||||
|
||||
collection = GObject.Property(type=Collection)
|
||||
|
||||
def __init__(self, collection: Collection, **kwargs: Any):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.collection = collection
|
||||
self.props.child = Adw.ButtonContent(
|
||||
icon_name=collection.icon_name,
|
||||
label=collection.name,
|
||||
can_shrink=True,
|
||||
)
|
||||
|
||||
|
||||
class CollectionsBox(Adw.Bin):
|
||||
"""A wrap box for adding games to collections."""
|
||||
|
||||
__gtype_name__ = __qualname__
|
||||
|
||||
game = GObject.Property(type=Game)
|
||||
|
||||
def __init__(self, **kwargs: Any):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.props.child = self.box = Adw.WrapBox(
|
||||
child_spacing=6,
|
||||
line_spacing=6,
|
||||
justify=Adw.JustifyMode.FILL,
|
||||
justify_last_line=True,
|
||||
natural_line_length=240,
|
||||
)
|
||||
model.bind_property(
|
||||
"n-items",
|
||||
self,
|
||||
"visible",
|
||||
GObject.BindingFlags.SYNC_CREATE,
|
||||
)
|
||||
|
||||
def build(self):
|
||||
"""Populate the box with collections."""
|
||||
for collection in cast(Iterable[Collection], model):
|
||||
button = CollectionButton(collection)
|
||||
button.props.active = self.game.game_id in collection.game_ids
|
||||
self.box.append(button)
|
||||
|
||||
def finish(self):
|
||||
"""Clear the box and save changes."""
|
||||
filter_changed = False
|
||||
for button in cast(Iterable[CollectionButton], self.box):
|
||||
game_ids = button.collection.game_ids
|
||||
old_game_ids = game_ids.copy()
|
||||
in_collection = self.game.game_id in game_ids
|
||||
|
||||
if button.props.active and not in_collection:
|
||||
game_ids.append(self.game.game_id)
|
||||
elif not button.props.active and in_collection:
|
||||
game_ids.remove(self.game.game_id)
|
||||
|
||||
if game_ids != old_game_ids:
|
||||
filter_changed = True
|
||||
|
||||
self.box.remove_all() # pyright: ignore[reportAttributeAccessIssue]
|
||||
collections.save()
|
||||
|
||||
if filter_changed:
|
||||
self.activate_action("win.notify-collection-filter")
|
||||
|
||||
|
||||
sorter = Gtk.StringSorter.new(Gtk.PropertyExpression.new(Collection, None, "name"))
|
||||
model = Gtk.SortListModel.new(
|
||||
Gtk.FilterListModel(
|
||||
|
||||
@@ -164,6 +164,52 @@ template $GameDetails: Adw.NavigationPage {
|
||||
]
|
||||
}
|
||||
|
||||
MenuButton {
|
||||
icon-name: "collection-symbolic";
|
||||
tooltip-text: _("Collections");
|
||||
valign: center;
|
||||
notify::active => $_setup_collections();
|
||||
|
||||
popover: PopoverMenu {
|
||||
menu-model: menu {
|
||||
item (_("New Collection"), "win.add-collection")
|
||||
|
||||
item {
|
||||
custom: "collections";
|
||||
}
|
||||
};
|
||||
|
||||
[collections]
|
||||
Box {
|
||||
orientation: vertical;
|
||||
visible: bind collections_box.visible;
|
||||
|
||||
Separator {}
|
||||
|
||||
Label {
|
||||
label: _("Collections");
|
||||
halign: start;
|
||||
margin-top: 6;
|
||||
margin-bottom: 9;
|
||||
margin-start: 12;
|
||||
margin-end: 12;
|
||||
|
||||
styles [
|
||||
"heading",
|
||||
]
|
||||
}
|
||||
|
||||
$CollectionsBox collections_box {
|
||||
game: bind template.game;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
styles [
|
||||
"circular",
|
||||
]
|
||||
}
|
||||
|
||||
Button hide_button {
|
||||
visible: bind hide_button.sensitive;
|
||||
action-name: "game.hide";
|
||||
|
||||
@@ -29,23 +29,61 @@ template $GameItem: Box {
|
||||
margin-top: 6;
|
||||
margin-end: 6;
|
||||
notify::active => $_reveal_buttons();
|
||||
notify::active => $_setup_collections();
|
||||
|
||||
menu-model: menu {
|
||||
item (_("Edit"), "item.edit")
|
||||
popover: PopoverMenu {
|
||||
menu-model: menu {
|
||||
section {
|
||||
item (_("Edit"), "item.edit")
|
||||
|
||||
item {
|
||||
label: _("Hide");
|
||||
action: "game.hide";
|
||||
hidden-when: "action-disabled";
|
||||
item {
|
||||
label: _("Hide");
|
||||
action: "game.hide";
|
||||
hidden-when: "action-disabled";
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("Unhide");
|
||||
action: "game.unhide";
|
||||
hidden-when: "action-disabled";
|
||||
}
|
||||
|
||||
item (_("Remove"), "game.remove")
|
||||
}
|
||||
|
||||
section {
|
||||
item (_("New Collection"), "win.add-collection")
|
||||
|
||||
item {
|
||||
custom: "collections";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
[collections]
|
||||
Box {
|
||||
orientation: vertical;
|
||||
visible: bind collections_box.visible;
|
||||
|
||||
Separator {}
|
||||
|
||||
Label {
|
||||
label: _("Collections");
|
||||
halign: start;
|
||||
margin-top: 6;
|
||||
margin-bottom: 9;
|
||||
margin-start: 12;
|
||||
margin-end: 12;
|
||||
|
||||
styles [
|
||||
"heading",
|
||||
]
|
||||
}
|
||||
|
||||
$CollectionsBox collections_box {
|
||||
game: bind template.game;
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("Unhide");
|
||||
action: "game.unhide";
|
||||
hidden-when: "action-disabled";
|
||||
}
|
||||
|
||||
item (_("Remove"), "game.remove")
|
||||
};
|
||||
|
||||
styles [
|
||||
|
||||
@@ -16,6 +16,7 @@ from cartridges import games
|
||||
from cartridges.config import PREFIX
|
||||
from cartridges.games import Game
|
||||
|
||||
from .collections import CollectionsBox
|
||||
from .cover import Cover # noqa: F401
|
||||
|
||||
_POP_ON_ACTION = "hide", "unhide", "remove"
|
||||
@@ -35,6 +36,7 @@ class GameDetails(Adw.NavigationPage):
|
||||
|
||||
stack: Adw.ViewStack = Gtk.Template.Child()
|
||||
actions: Gtk.Box = Gtk.Template.Child()
|
||||
collections_box: CollectionsBox = Gtk.Template.Child()
|
||||
name_entry: Adw.EntryRow = Gtk.Template.Child()
|
||||
developer_entry: Adw.EntryRow = Gtk.Template.Child()
|
||||
executable_entry: Adw.EntryRow = Gtk.Template.Child()
|
||||
@@ -131,6 +133,13 @@ class GameDetails(Adw.NavigationPage):
|
||||
|
||||
self.stack.props.visible_child_name = "details"
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def _setup_collections(self, button: Gtk.MenuButton, *_args):
|
||||
if button.props.active:
|
||||
self.collections_box.build()
|
||||
else:
|
||||
self.collections_box.finish()
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def _or(self, _obj, first: _T, second: _T) -> _T:
|
||||
return first or second
|
||||
|
||||
@@ -8,6 +8,7 @@ from gi.repository import Gio, GLib, GObject, Gtk
|
||||
from cartridges.config import PREFIX
|
||||
from cartridges.games import Game
|
||||
|
||||
from .collections import CollectionsBox
|
||||
from .cover import Cover # noqa: F401
|
||||
|
||||
|
||||
@@ -19,6 +20,7 @@ class GameItem(Gtk.Box):
|
||||
|
||||
motion: Gtk.EventControllerMotion = Gtk.Template.Child()
|
||||
options: Gtk.MenuButton = Gtk.Template.Child()
|
||||
collections_box: CollectionsBox = Gtk.Template.Child()
|
||||
play: Gtk.Button = Gtk.Template.Child()
|
||||
|
||||
position = GObject.Property(type=int)
|
||||
@@ -56,3 +58,10 @@ class GameItem(Gtk.Box):
|
||||
):
|
||||
widget.props.can_focus = widget.props.can_target = reveal
|
||||
(widget.remove_css_class if reveal else widget.add_css_class)("hidden")
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def _setup_collections(self, button: Gtk.MenuButton, *_args):
|
||||
if button.props.active:
|
||||
self.collections_box.build()
|
||||
else:
|
||||
self.collections_box.finish()
|
||||
|
||||
@@ -24,7 +24,8 @@
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
#game-item button {
|
||||
#game-item overlay > button,
|
||||
#game-item overlay > menubutton > button {
|
||||
color: white;
|
||||
backdrop-filter: blur(9px) brightness(30%) saturate(600%);
|
||||
box-shadow:
|
||||
@@ -37,12 +38,12 @@
|
||||
opacity, transform;
|
||||
}
|
||||
|
||||
#game-item button.hidden {
|
||||
#game-item overlay > button.hidden {
|
||||
opacity: 0;
|
||||
transform: translateY(3px);
|
||||
}
|
||||
|
||||
#game-item menubutton.hidden button {
|
||||
#game-item overlay > menubutton.hidden > button {
|
||||
opacity: 0;
|
||||
transform: translateY(-6px);
|
||||
}
|
||||
@@ -83,7 +84,8 @@
|
||||
outline: 5px solid var(--accent-color);
|
||||
}
|
||||
|
||||
#game-item button {
|
||||
#game-item overlay > button,
|
||||
#game-item overlay > menubutton > button {
|
||||
backdrop-filter: blur(9px) brightness(20%) saturate(600%);
|
||||
}
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@ template $Window: Adw.ApplicationWindow {
|
||||
watch-items: true;
|
||||
|
||||
filter: EveryFilter {
|
||||
$CollectionFilter {
|
||||
$CollectionFilter collection_filter {
|
||||
collection: bind template.collection;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,7 @@ from cartridges.games import Game
|
||||
from cartridges.ui import collections
|
||||
|
||||
from .collection_details import CollectionDetails
|
||||
from .collections import (
|
||||
CollectionFilter, # noqa: F401
|
||||
CollectionSidebarItem,
|
||||
)
|
||||
from .collections import CollectionFilter, CollectionSidebarItem
|
||||
from .game_details import GameDetails
|
||||
from .game_item import GameItem # noqa: F401
|
||||
from .games import GameSorter
|
||||
@@ -61,6 +58,7 @@ class Window(Adw.ApplicationWindow):
|
||||
toast_overlay: Adw.ToastOverlay = Gtk.Template.Child()
|
||||
grid: Gtk.GridView = Gtk.Template.Child()
|
||||
sorter: GameSorter = Gtk.Template.Child()
|
||||
collection_filter: CollectionFilter = Gtk.Template.Child()
|
||||
details: GameDetails = Gtk.Template.Child()
|
||||
|
||||
search_text = GObject.Property(type=str)
|
||||
@@ -130,6 +128,7 @@ class Window(Adw.ApplicationWindow):
|
||||
"u",
|
||||
),
|
||||
("add", lambda *_: self._add()),
|
||||
("add-collection", lambda *_: self._add_collection()),
|
||||
(
|
||||
"edit-collection",
|
||||
lambda _action, param, *_: self._edit_collection(param.get_uint32()),
|
||||
@@ -140,6 +139,10 @@ class Window(Adw.ApplicationWindow):
|
||||
lambda _action, param, *_: self._remove_collection(param.get_uint32()),
|
||||
"u",
|
||||
),
|
||||
(
|
||||
"notify-collection-filter",
|
||||
lambda *_: self.collection_filter.changed(Gtk.FilterChange.DIFFERENT),
|
||||
),
|
||||
("undo", lambda *_: self._undo()),
|
||||
))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user