collection-details: Support adding collections
This commit is contained in:
@@ -21,6 +21,11 @@ class Collection(GObject.Object):
|
||||
|
||||
icon_name = GObject.Property(type=str)
|
||||
|
||||
@GObject.Property(type=bool, default=True)
|
||||
def in_model(self) -> bool:
|
||||
"""Whether `self` has been added to the model."""
|
||||
return self in model
|
||||
|
||||
def __init__(self, **kwargs: Any):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@@ -54,6 +59,9 @@ def load():
|
||||
model.splice(0, 0, tuple(_get_collections()))
|
||||
save()
|
||||
|
||||
for collection in model:
|
||||
collection.notify("in-model")
|
||||
|
||||
|
||||
def save():
|
||||
"""Save collections to GSettings."""
|
||||
|
||||
58
cartridges/ui/collection-details.blp
Normal file
58
cartridges/ui/collection-details.blp
Normal file
@@ -0,0 +1,58 @@
|
||||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $CollectionDetails: Adw.Dialog {
|
||||
title: _("New Collection");
|
||||
content-width: 360;
|
||||
default-widget: apply_button;
|
||||
focus-widget: name_entry;
|
||||
|
||||
child: Adw.ToolbarView {
|
||||
[top]
|
||||
Adw.HeaderBar {
|
||||
show-start-title-buttons: false;
|
||||
show-end-title-buttons: false;
|
||||
|
||||
[start]
|
||||
Button {
|
||||
action-name: "window.close";
|
||||
icon-name: "cancel-symbolic";
|
||||
tooltip-text: _("Cancel");
|
||||
}
|
||||
|
||||
[end]
|
||||
Button apply_button {
|
||||
action-name: "details.apply";
|
||||
icon-name: "apply-symbolic";
|
||||
tooltip-text: _("Add");
|
||||
|
||||
styles [
|
||||
"suggested-action",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
content: Adw.PreferencesPage {
|
||||
Adw.PreferencesGroup {
|
||||
Adw.EntryRow name_entry {
|
||||
title: _("Name");
|
||||
activates-default: true;
|
||||
}
|
||||
}
|
||||
|
||||
Adw.PreferencesGroup {
|
||||
FlowBox icons_box {
|
||||
min-children-per-line: 7;
|
||||
|
||||
styles [
|
||||
"navigation-sidebar",
|
||||
]
|
||||
}
|
||||
|
||||
styles [
|
||||
"card",
|
||||
]
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
90
cartridges/ui/collection_details.py
Normal file
90
cartridges/ui/collection_details.py
Normal file
@@ -0,0 +1,90 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# SPDX-FileCopyrightText: Copyright 2025 Jamie Gravendeel
|
||||
|
||||
from typing import Any, cast
|
||||
|
||||
from gi.repository import Adw, Gio, GObject, Gtk
|
||||
|
||||
from cartridges import collections
|
||||
from cartridges.collections import Collection
|
||||
from cartridges.config import PREFIX
|
||||
|
||||
ICONS = (
|
||||
"collection",
|
||||
"star",
|
||||
"heart",
|
||||
"music",
|
||||
"people",
|
||||
"skull",
|
||||
"private",
|
||||
"globe",
|
||||
"map",
|
||||
"city",
|
||||
"car",
|
||||
"horse",
|
||||
"sprout",
|
||||
"step-over",
|
||||
"gamepad",
|
||||
"ball",
|
||||
"puzzle",
|
||||
"flashlight",
|
||||
"knife",
|
||||
"gun",
|
||||
"fist",
|
||||
)
|
||||
|
||||
|
||||
@Gtk.Template.from_resource(f"{PREFIX}/collection-details.ui")
|
||||
class CollectionDetails(Adw.Dialog):
|
||||
"""The details of a category."""
|
||||
|
||||
__gtype_name__ = __qualname__
|
||||
|
||||
name_entry: Adw.EntryRow = Gtk.Template.Child()
|
||||
icons_box: Gtk.FlowBox = Gtk.Template.Child()
|
||||
|
||||
collection = GObject.Property(type=Collection)
|
||||
|
||||
def __init__(self, **kwargs: Any):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.insert_action_group("details", group := Gio.SimpleActionGroup())
|
||||
|
||||
group.add_action(apply := Gio.SimpleAction.new("apply"))
|
||||
apply.connect("activate", lambda *_: self._apply())
|
||||
self.name_entry.bind_property(
|
||||
"text",
|
||||
apply,
|
||||
"enabled",
|
||||
GObject.BindingFlags.SYNC_CREATE,
|
||||
transform_to=lambda _, text: bool(text),
|
||||
)
|
||||
|
||||
icons = Gtk.StringList.new(tuple(f"{icon}-symbolic" for icon in ICONS))
|
||||
self.icons_box.bind_model(
|
||||
icons,
|
||||
lambda string: Gtk.FlowBoxChild(
|
||||
name="collection-icon-child",
|
||||
child=Gtk.Image.new_from_icon_name(string.props.string),
|
||||
halign=Gtk.Align.CENTER,
|
||||
),
|
||||
)
|
||||
self.icons_box.select_child(
|
||||
cast(
|
||||
Gtk.FlowBoxChild,
|
||||
self.icons_box.get_child_at_index(ICONS.index(self.collection.icon)),
|
||||
)
|
||||
)
|
||||
|
||||
def _apply(self):
|
||||
self.collection.name = self.name_entry.props.text
|
||||
self.collection.icon = ICONS[
|
||||
self.icons_box.get_selected_children()[0].get_index()
|
||||
]
|
||||
|
||||
if not self.collection.in_model:
|
||||
collections.model.append(self.collection)
|
||||
self.collection.notify("in-model")
|
||||
|
||||
collections.save()
|
||||
self.close()
|
||||
@@ -5,6 +5,7 @@ from typing import Any, override
|
||||
|
||||
from gi.repository import Adw, GObject, Gtk
|
||||
|
||||
from cartridges import collections
|
||||
from cartridges.collections import Collection
|
||||
from cartridges.games import Game
|
||||
|
||||
@@ -51,3 +52,17 @@ class CollectionSidebarItem(Adw.SidebarItem): # pyright: ignore[reportAttribute
|
||||
super().__init__(**kwargs)
|
||||
|
||||
self.bind_property("title", self, "tooltip", GObject.BindingFlags.SYNC_CREATE)
|
||||
|
||||
|
||||
sorter = Gtk.StringSorter.new(Gtk.PropertyExpression.new(Collection, None, "name"))
|
||||
model = Gtk.SortListModel.new(
|
||||
Gtk.FilterListModel(
|
||||
model=collections.model,
|
||||
filter=Gtk.BoolFilter(
|
||||
expression=Gtk.PropertyExpression.new(Collection, None, "removed"),
|
||||
invert=True,
|
||||
),
|
||||
watch_items=True, # pyright: ignore[reportCallIssue]
|
||||
),
|
||||
sorter,
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
python.install_sources(
|
||||
files(
|
||||
'__init__.py',
|
||||
'collection_details.py',
|
||||
'collections.py',
|
||||
'cover.py',
|
||||
'game_details.py',
|
||||
@@ -13,6 +14,7 @@ python.install_sources(
|
||||
|
||||
blueprints = custom_target(
|
||||
input: files(
|
||||
'collection-details.blp',
|
||||
'cover.blp',
|
||||
'game-details.blp',
|
||||
'game-item.blp',
|
||||
|
||||
@@ -63,6 +63,10 @@
|
||||
filter: saturate(300%) opacity(50%);
|
||||
}
|
||||
|
||||
#collection-icon-child {
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
#details list {
|
||||
background: rgb(from white r g b / 10%);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<gresources>
|
||||
<gresource prefix="@PREFIX@">
|
||||
<file>collection-details.ui</file>
|
||||
<file>cover.ui</file>
|
||||
<file>game-details.ui</file>
|
||||
<file>game-item.ui</file>
|
||||
|
||||
@@ -78,6 +78,8 @@ template $Window: Adw.ApplicationWindow {
|
||||
|
||||
content: Adw.Sidebar sidebar {
|
||||
activated => $_navigate();
|
||||
setup-menu => $_setup_sidebar_menu();
|
||||
notify::selected => $_update_selection();
|
||||
|
||||
Adw.SidebarSection {
|
||||
Adw.SidebarItem {
|
||||
@@ -89,6 +91,13 @@ template $Window: Adw.ApplicationWindow {
|
||||
Adw.SidebarSection collections {
|
||||
title: _("Collections");
|
||||
}
|
||||
|
||||
Adw.SidebarSection {
|
||||
Adw.SidebarItem new_collection_item {
|
||||
icon-name: "list-add-symbolic";
|
||||
title: _("New Collection");
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -10,11 +10,13 @@ from typing import Any, TypeVar, cast
|
||||
|
||||
from gi.repository import Adw, Gio, GLib, GObject, Gtk
|
||||
|
||||
from cartridges import STATE_SETTINGS, collections, games
|
||||
from cartridges import STATE_SETTINGS, games
|
||||
from cartridges.collections import Collection
|
||||
from cartridges.config import PREFIX, PROFILE
|
||||
from cartridges.games import Game
|
||||
from cartridges.ui import collections
|
||||
|
||||
from .collection_details import CollectionDetails
|
||||
from .collections import (
|
||||
CollectionFilter, # noqa: F401
|
||||
CollectionSidebarItem,
|
||||
@@ -47,6 +49,7 @@ class Window(Adw.ApplicationWindow):
|
||||
|
||||
split_view: Adw.OverlaySplitView = Gtk.Template.Child()
|
||||
collections: Adw.SidebarSection = Gtk.Template.Child() # pyright: ignore[reportAttributeAccessIssue]
|
||||
new_collection_item: Adw.SidebarItem = Gtk.Template.Child() # pyright: ignore[reportAttributeAccessIssue]
|
||||
navigation_view: Adw.NavigationView = Gtk.Template.Child()
|
||||
header_bar: Adw.HeaderBar = Gtk.Template.Child()
|
||||
title_box: Gtk.CenterBox = Gtk.Template.Child()
|
||||
@@ -64,6 +67,8 @@ class Window(Adw.ApplicationWindow):
|
||||
|
||||
settings = GObject.Property(type=Gtk.Settings)
|
||||
|
||||
_selected_sidebar_item = 0
|
||||
|
||||
@GObject.Property(type=Gio.ListStore)
|
||||
def games(self) -> Gio.ListStore:
|
||||
"""Model of the user's games."""
|
||||
@@ -128,9 +133,24 @@ class Window(Adw.ApplicationWindow):
|
||||
@Gtk.Template.Callback()
|
||||
def _navigate(self, sidebar: Adw.Sidebar, index: int): # pyright: ignore[reportAttributeAccessIssue]
|
||||
item = sidebar.get_item(index)
|
||||
self.collection = (
|
||||
item.collection if isinstance(item, CollectionSidebarItem) else None
|
||||
)
|
||||
|
||||
match item:
|
||||
case self.new_collection_item:
|
||||
self._add_collection()
|
||||
sidebar.props.selected = self._selected_sidebar_item
|
||||
case CollectionSidebarItem():
|
||||
self.collection = item.collection
|
||||
case _:
|
||||
self.collection = None
|
||||
|
||||
if item is not self.new_collection_item:
|
||||
self._selected_sidebar_item = index
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def _update_selection(self, sidebar: Adw.Sidebar, *_args): # pyright: ignore[reportAttributeAccessIssue]
|
||||
if sidebar.props.selected_item is self.new_collection_item:
|
||||
sidebar.props.selected = self._selected_sidebar_item
|
||||
self._selected_sidebar_item = sidebar.props.selected
|
||||
|
||||
@Gtk.Template.Callback()
|
||||
def _setup_gamepad_monitor(self, *_args):
|
||||
@@ -186,6 +206,10 @@ class Window(Adw.ApplicationWindow):
|
||||
|
||||
self.details.edit()
|
||||
|
||||
def _add_collection(self):
|
||||
details = CollectionDetails(collection=Collection())
|
||||
details.present(self)
|
||||
|
||||
def _undo(self, toast: Adw.Toast | None = None):
|
||||
if toast:
|
||||
self._history.pop(toast)()
|
||||
|
||||
Reference in New Issue
Block a user