From 4e769ad1ec961cc0e1734f131682af411279a144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benedek=20D=C3=A9v=C3=A9nyi?= Date: Mon, 22 Jul 2024 00:16:27 +0200 Subject: [PATCH] Redesign book cards (#921) --- com.github.geigi.cozy.json | 2 +- cozy/application.py | 9 ++ cozy/ui/library_view.py | 43 +++---- cozy/ui/main_view.py | 15 +++ cozy/ui/widgets/album_element.py | 83 ------------- cozy/ui/widgets/book_card.py | 163 ++++++++++++++++++++++++++ cozy/ui/widgets/book_element.py | 138 ---------------------- cozy/view_model/library_view_model.py | 4 +- data/gresource.xml | 3 +- data/style.css | 23 +--- data/ui/album_element.blp | 65 ---------- data/ui/book_card.blp | 143 ++++++++++++++++++++++ data/ui/book_element.blp | 53 --------- data/ui/chapter_element.ui | 0 data/ui/main_window.blp | 4 +- data/ui/meson.build | 3 +- po/POTFILES | 4 +- 17 files changed, 365 insertions(+), 390 deletions(-) delete mode 100644 cozy/ui/widgets/album_element.py create mode 100644 cozy/ui/widgets/book_card.py delete mode 100644 cozy/ui/widgets/book_element.py delete mode 100644 data/ui/album_element.blp create mode 100644 data/ui/book_card.blp delete mode 100644 data/ui/book_element.blp delete mode 100644 data/ui/chapter_element.ui diff --git a/com.github.geigi.cozy.json b/com.github.geigi.cozy.json index 9cd251a5..6b324bda 100644 --- a/com.github.geigi.cozy.json +++ b/com.github.geigi.cozy.json @@ -115,7 +115,7 @@ { "type": "git", "url": "https://gitlab.gnome.org/jwestman/blueprint-compiler.git", - "tag": "v0.10.0" + "tag": "v0.12.0" } ] }, diff --git a/cozy/application.py b/cozy/application.py index b9c240c8..4fa083ad 100644 --- a/cozy/application.py +++ b/cozy/application.py @@ -24,6 +24,7 @@ class Application(Adw.Application): ui: CozyUI app_controller: AppController + _selected_book = None def __init__(self, pkgdatadir: str): self.pkgdatadir = pkgdatadir @@ -65,6 +66,14 @@ def do_activate(self): mpris = MPRIS(self) mpris._on_current_changed() + @property + def selected_book(self): + return self._selected_book + + @selected_book.setter + def selected_book(self, new_book) -> None: + self._selected_book = new_book + def handle_exception(self, _): print("handle exception") diff --git a/cozy/ui/library_view.py b/cozy/ui/library_view.py index f36c724a..363c7d90 100644 --- a/cozy/ui/library_view.py +++ b/cozy/ui/library_view.py @@ -4,7 +4,7 @@ from cozy.ext import inject from cozy.ui.delete_book_view import DeleteBookView -from cozy.ui.widgets.book_element import BookElement +from cozy.ui.widgets.book_card import BookCard from cozy.ui.widgets.filter_list_box import FilterListBox from cozy.view_model.library_view_model import LibraryViewMode, LibraryViewModel @@ -23,7 +23,7 @@ class LibraryView: def __init__(self, builder: Gtk.Builder): self._builder = builder - self._connected_book_element: Optional[BookElement] = None + self._connected_book_card: Optional[BookCard] = None self._get_ui_elements() self._connect_ui_elements() @@ -87,15 +87,16 @@ def _on_sort_stack_changed(self, widget, _): self._view_model.library_view_mode = view_mode def populate_book_box(self): - for child in self._book_box: + children = list(self._book_box) # Handy PyGObject feature to get children + for child in children: self._book_box.remove(child) for book in self._view_model.books: - book_element = BookElement(book) - book_element.connect("play-pause-clicked", self._play_book_clicked) - book_element.connect("open-book-overview", self._open_book_overview_clicked) - book_element.connect("book-removed", self._on_book_removed) - self._book_box.append(book_element) + book_card = BookCard(book) + book_card.connect("play-pause-clicked", self._play_book_clicked) + book_card.connect("open-book-overview", self._open_book_overview_clicked) + book_card.connect("remove-book", self._on_remove_book) + self._book_box.append(book_card) def populate_author(self): self._author_box.populate(self._view_model.authors) @@ -166,11 +167,11 @@ def _open_book_overview_clicked(self, _, book): return True - def _on_book_removed(self, _, book): + def _on_remove_book(self, _, book): if self._view_model.book_files_exist(book): - DeleteBookView(self._on_book_removed_clicked, book).present() + DeleteBookView(self._on_remove_book_response, book).present() - def _on_book_removed_clicked(self, _, response, book): + def _on_remove_book_response(self, _, response, book): if response != "delete": return @@ -184,24 +185,24 @@ def _on_book_removed_clicked(self, _, response, book): self._view_model.remove_book(book) def _current_book_in_playback(self): - if self._connected_book_element: - self._connected_book_element.set_playing(False) + if self._connected_book_card: + self._connected_book_card.set_playing(False) - self._connected_book_element = None + self._connected_book_card = None index = 0 - while book_element := self._book_box.get_child_at_index(index): - if book_element.book == self._view_model.current_book_in_playback: - self._connected_book_element = book_element + while book_card := self._book_box.get_child_at_index(index): + if book_card.book == self._view_model.current_book_in_playback: + self._connected_book_card = book_card break index += 1 def _playing(self): - if self._connected_book_element: - self._connected_book_element.set_playing(self._view_model.playing) + if self._connected_book_card: + self._connected_book_card.set_playing(self._view_model.playing) def _on_book_progress_changed(self): - if not self._connected_book_element: + if not self._connected_book_card: return - self._connected_book_element.update_progress() + self._connected_book_card.update_progress() diff --git a/cozy/ui/main_view.py b/cozy/ui/main_view.py index 966ae2de..8229c8d7 100644 --- a/cozy/ui/main_view.py +++ b/cozy/ui/main_view.py @@ -91,6 +91,9 @@ def __init_actions(self): Init all app actions. """ self.create_action("about", self.show_about_window, ["F1"]) + self.create_action("remove_book", self.remove_book) + self.create_action("mark_book_as_read", self.mark_book_as_read) + self.create_action("jump_to_book_folder", self.jump_to_book_folder) self.create_action("prefs", self.show_preferences_window, ["comma"]) self.create_action("quit", self.quit, ["q", "w"]) self.scan_action = self.create_action("scan", self.scan) @@ -127,6 +130,18 @@ def create_action( return action + def remove_book(self, *_) -> None: + if self.app.selected_book is not None: + self.app.selected_book.remove() + + def mark_book_as_read(self, *_) -> None: + if self.app.selected_book is not None: + self.app.selected_book.mark_as_read() + + def jump_to_book_folder(self, *_) -> None: + if self.app.selected_book is not None: + self.app.selected_book.jump_to_folder() + def get_object(self, name): return self.window_builder.get_object(name) diff --git a/cozy/ui/widgets/album_element.py b/cozy/ui/widgets/album_element.py deleted file mode 100644 index ce662b55..00000000 --- a/cozy/ui/widgets/album_element.py +++ /dev/null @@ -1,83 +0,0 @@ -import logging -import math - -import cairo -from gi.repository import GObject, Gtk - -from cozy.control.artwork_cache import ArtworkCache -from cozy.ext import inject -from cozy.model.book import Book - -ALBUM_ART_SIZE = 200 -PLAY_BUTTON_ICON_SIZE = Gtk.IconSize.NORMAL -STROKE_WIDTH = 3 - -log = logging.getLogger("album_element") - - -@Gtk.Template.from_resource('/com/github/geigi/cozy/ui/album_element.ui') -class AlbumElement(Gtk.Box): - __gtype_name__ = "AlbumElement" - - artwork_cache: ArtworkCache = inject.attr(ArtworkCache) - - album_art_image: Gtk.Image = Gtk.Template.Child() - play_button: Gtk.Button = Gtk.Template.Child() - progress_drawing_area: Gtk.DrawingArea = Gtk.Template.Child() - album_art_drawing_area: Gtk.DrawingArea = Gtk.Template.Child() - album_art_overlay_revealer: Gtk.Revealer = Gtk.Template.Child() - play_button_revealer: Gtk.Revealer = Gtk.Template.Child() - - def __init__(self, book: Book): - super().__init__() - - self._book: Book = book - paintable = self.artwork_cache.get_cover_paintable(book, 1, ALBUM_ART_SIZE) - - if paintable: - self.album_art_image.set_from_paintable(paintable) - self.album_art_image.set_size_request(ALBUM_ART_SIZE, ALBUM_ART_SIZE) - else: - self.album_art_image.set_from_icon_name("book-open-variant-symbolic") - self.album_art_image.props.pixel_size = ALBUM_ART_SIZE - - self.play_button.connect("clicked", self._on_play_button_press) - - # TODO: Use CSS for hover - self.progress_drawing_area.set_draw_func(self._draw_progress) - - @GObject.Signal(arg_types=(object,)) - def play_pause_clicked(self, *_): ... - - def set_playing(self, playing: bool): - if playing: - self.play_button.set_icon_name("media-playback-pause-symbolic") - else: - self.play_button.set_icon_name("media-playback-start-symbolic") - - def set_hover(self, hover: bool): - self.album_art_overlay_revealer.set_reveal_child(hover) - self.play_button_revealer.set_reveal_child(hover) - - def _on_play_button_press(self, _): - self.emit("play-pause-clicked", self._book) - - def _draw_progress(self, area: Gtk.DrawingArea, context: cairo.Context, *_): - width = area.get_allocated_width() - height = area.get_allocated_height() - button_size = self.play_button.get_allocated_width() - self.radius = (button_size - STROKE_WIDTH) / 2.0 - - book_progress = self._book.progress / self._book.duration - - progress_circle_end = book_progress * math.pi * 2.0 - context.arc(width / 2.0, height / 2.0, self.radius, math.pi * -0.5, progress_circle_end - (math.pi * 0.5)) - if book_progress == 1.0: - context.set_source_rgb(0.2, 0.82, 0.478) - else: - context.set_source_rgb(1.0, 1.0, 1.0) - context.set_line_width(STROKE_WIDTH) - context.stroke() - - def update_progress(self): - self.progress_drawing_area.queue_draw() diff --git a/cozy/ui/widgets/book_card.py b/cozy/ui/widgets/book_card.py new file mode 100644 index 00000000..d33cf893 --- /dev/null +++ b/cozy/ui/widgets/book_card.py @@ -0,0 +1,163 @@ +from math import pi as PI + +import cairo +from gi.repository import Gdk, Gio, GObject, Graphene, Gtk + +from cozy.control.artwork_cache import ArtworkCache +from cozy.ext import inject +from cozy.model.book import Book + +ALBUM_ART_SIZE = 200 +STROKE_WIDTH = 4 + + +class BookCardPlayButton(Gtk.Button): + __gtype_name__ = "BookCardPlayButton" + + progress = GObject.Property(type=float, default=0.0) + + def __init__(self) -> None: + super().__init__() + + self.connect("notify::progress", self.redraw) + + def redraw(self, *_) -> None: + self.queue_draw() + + def set_playing(self, playing: bool): + if playing: + self.set_icon_name("media-playback-pause-symbolic") + else: + self.set_icon_name("media-playback-start-symbolic") + + def do_snapshot(self, snapshot: Gtk.Snapshot) -> None: + Gtk.Button.do_snapshot(self, snapshot) + + if self.progress == 0.0: + return + + size = self.get_allocated_height() + radius = (size - STROKE_WIDTH) / 2.0 + + context = snapshot.append_cairo(Graphene.Rect().init(0, 0, size, size)) + + context.set_source_rgb(1.0, 1.0, 1.0) + context.set_line_width(STROKE_WIDTH) + context.set_line_cap(cairo.LineCap.ROUND) + + context.arc(size / 2, size / 2, radius, -0.5 * PI, self.progress * 2 * PI - (0.5 * PI)) + context.stroke() + + +@Gtk.Template.from_resource('/com/github/geigi/cozy/ui/book_card.ui') +class BookCard(Gtk.FlowBoxChild): + __gtype_name__ = "BookCard" + + title = GObject.Property(type=str, default=_("Unknown")) + author = GObject.Property(type=str, default=_("Unknown")) + + artwork: Gtk.Picture = Gtk.Template.Child() + fallback_icon: Gtk.Image = Gtk.Template.Child() + stack: Gtk.Stack = Gtk.Template.Child() + button: Gtk.Stack = Gtk.Template.Child() + menu_button: Gtk.MenuButton = Gtk.Template.Child() + play_revealer: Gtk.Revealer = Gtk.Template.Child() + menu_revealer: Gtk.Revealer = Gtk.Template.Child() + play_button: BookCardPlayButton = Gtk.Template.Child() + + artwork_cache: ArtworkCache = inject.attr(ArtworkCache) + + __gsignals__ = { + "play-pause-clicked": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (object,)), + "open-book-overview": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (object,)), + "remove-book": (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (object,)), + } + + def __init__(self, book: Book): + super().__init__() + + self.book = book + + self.title = book.name + self.author = book.author + + paintable = self.artwork_cache.get_cover_paintable(book, 1, ALBUM_ART_SIZE) + + if paintable: + self.artwork.set_paintable(paintable) + self.artwork.set_size_request(ALBUM_ART_SIZE, ALBUM_ART_SIZE) + self.stack.set_visible_child(self.artwork) + else: + self.fallback_icon.set_from_icon_name("book-open-variant-symbolic") + self.stack.set_visible_child(self.fallback_icon) + + self.menu_button.connect("notify::active", self._on_leave) + self.set_cursor(Gdk.Cursor.new_from_name("pointer")) + + self._install_event_controllers() + self.update_progress() + + def _install_event_controllers(self): + hover_controller = Gtk.EventControllerMotion() + hover_controller.connect("enter", self._on_enter) + hover_controller.connect("leave", self._on_leave) + + long_press_gesture = Gtk.GestureLongPress() + long_press_gesture.connect("pressed", self._on_long_tap) + + key_event_controller = Gtk.EventControllerKey() + key_event_controller.connect("key-pressed", self._on_key_press_event) + + self.add_controller(hover_controller) + self.add_controller(long_press_gesture) + self.add_controller(key_event_controller) + + def set_playing(self, is_playing): + self.play_button.set_playing(is_playing) + + def update_progress(self): + self.play_button.progress = self.book.progress / self.book.duration + + def remove(self) -> None: + self.emit("remove-book", self.book) + + def mark_as_read(self) -> None: + self.book.position = -1 + self.update_progress() + + def jump_to_folder(self) -> None: + track = self.book.chapters[0] + + file_launcher = Gtk.FileLauncher(file=Gio.File.new_for_path(track.file)) + dummy_callback = lambda d, r: d.open_containing_folder_finish(r) + file_launcher.open_containing_folder(None, None, dummy_callback) + + @Gtk.Template.Callback() + def _play_pause(self, *_): + self.emit("play-pause-clicked", self.book) + + @Gtk.Template.Callback() + def _open_book_overview(self, *_): + self.emit("open-book-overview", self.book) + + def _on_enter(self, *_) -> None: + self.play_revealer.set_reveal_child(True) + self.menu_revealer.set_reveal_child(True) + + inject.instance("GtkApp").selected_book = self + + def _on_leave(self, *_) -> None: + if not self.menu_button.get_active(): + self.play_revealer.set_reveal_child(False) + self.menu_revealer.set_reveal_child(False) + + def _on_long_tap(self, gesture: Gtk.Gesture, *_): + gesture.set_state(Gtk.EventSequenceState.CLAIMED) + + device = gesture.get_device() + if device and device.get_source() == Gdk.InputSource.TOUCHSCREEN: + self.menu_button.emit("activate") + + def _on_key_press_event(self, controller, keyval, *_): + if keyval == Gdk.KEY_Return: + self.emit("open-book-overview", self.book) diff --git a/cozy/ui/widgets/book_element.py b/cozy/ui/widgets/book_element.py deleted file mode 100644 index 34285cad..00000000 --- a/cozy/ui/widgets/book_element.py +++ /dev/null @@ -1,138 +0,0 @@ -from gi.repository import Gdk, Gio, GObject, Gtk - -from cozy.model.book import Book -from cozy.ui.widgets.album_element import AlbumElement - - -@Gtk.Template.from_resource('/com/github/geigi/cozy/ui/book_element.ui') -class BookElement(Gtk.FlowBoxChild): - __gtype_name__ = "BookElement" - - name_label: Gtk.Label = Gtk.Template.Child() - author_label: Gtk.Label = Gtk.Template.Child() - container_box: Gtk.Box = Gtk.Template.Child() - - def __init__(self, book: Book): - super().__init__() - - self.book = book - self.pressed = False - - self.name_label.set_text(book.name) - self.name_label.set_tooltip_text(book.name) - self.author_label.set_text(book.author) - self.author_label.set_tooltip_text(book.author) - - self.art = AlbumElement(self.book) - self.art.connect("play-pause-clicked", self._on_album_art_press_event) - - self.container_box.prepend(self.art) - self.set_cursor(Gdk.Cursor.new_from_name("pointer")) - - self._add_event_controllers() - - def _add_event_controllers(self): - primary_button_gesture = Gtk.GestureClick(button=Gdk.BUTTON_PRIMARY) - # primary_button_gesture.connect("pressed", self._select_item) - primary_button_gesture.connect("released", self._open_book_overview) - self.container_box.add_controller(primary_button_gesture) - - secondary_button_gesture = Gtk.GestureClick(button=Gdk.BUTTON_SECONDARY) - secondary_button_gesture.connect("released", self._show_context_menu) - self.container_box.add_controller(secondary_button_gesture) - - # FIXME: When clicking on an album's play button in the recents view, - # it jumps to the first position, and GtkGestureLongPress thinks it's - # a long press gesture, although it's just an unfinished long press - long_press_gesture = Gtk.GestureLongPress() - long_press_gesture.connect("pressed", self._show_context_menu) - self.container_box.add_controller(long_press_gesture) - - key_event_controller = Gtk.EventControllerKey() - key_event_controller.connect("key-pressed", self._on_key_press_event) - self.container_box.add_controller(key_event_controller) - - motion_event_controller = Gtk.EventControllerMotion() - motion_event_controller.connect("enter", self._on_cover_enter_notify) - motion_event_controller.connect("leave", self._on_cover_leave_notify) - self.container_box.add_controller(motion_event_controller) - - @GObject.Signal(arg_types=(object,)) - def play_pause_clicked(self, *_): ... - - @GObject.Signal(arg_types=(object,)) - def open_book_overview(self, *_): ... - - @GObject.Signal(arg_types=(object,)) - def book_removed(self, *_): ... - - def set_playing(self, is_playing): - self.art.set_playing(is_playing) - - def update_progress(self): - self.art.update_progress() - - def _create_context_menu(self): - menu_model = Gio.Menu() - - self.install_action("book_element.mark_as_read", None, self._mark_as_read) - menu_model.append(_("Mark as Read"), "book_element.mark_as_read") - - self.install_action("book_element.jump_to_folder", None, self._jump_to_folder) - menu_model.append(_("Open in File Browser"), "book_element.jump_to_folder") - - self.install_action("book_element.remove_book", None, self._remove_book) - menu_model.append(_("Permanently Delete…"), "book_element.remove_book") - - menu = Gtk.PopoverMenu(menu_model=menu_model, has_arrow=False) - menu.set_parent(self.art) - - return menu - - def _remove_book(self, *_): - self.emit("book-removed", self.book) - - def _mark_as_read(self, *_): - self.book.position = -1 - - def _jump_to_folder(self, *_): - """ - Opens the folder containing this books files in the default file explorer. - """ - track = self.book.chapters[0] - - file_launcher = Gtk.FileLauncher(file=Gio.File.new_for_path(track.file)) - dummy_callback = lambda d, r: d.open_containing_folder_finish(r) - file_launcher.open_containing_folder(None, None, dummy_callback) - - def _show_context_menu(self, gesture: Gtk.Gesture, *_): - gesture.set_state(Gtk.EventSequenceState.CLAIMED) - - self._create_context_menu().popup() - - def _select_item(self, gesture: Gtk.Gesture, *_): - if super().get_sensitive(): - gesture.set_state(Gtk.EventSequenceState.CLAIMED) - self.pressed = True - self.container_box.add_css_class("selected") - - def _open_book_overview(self, gesture, *_): - gesture.set_state(Gtk.EventSequenceState.CLAIMED) - - self.pressed = False - self.container_box.remove_css_class("selected") - if super().get_sensitive(): - self.emit("open-book-overview", self.book) - - def _on_key_press_event(self, keyval, *_): - if keyval == Gdk.KEY_Return and super().get_sensitive(): - self.emit("open-book-overview", self.book) - - def _on_cover_enter_notify(self, *_): - self.art.set_hover(True) - - def _on_cover_leave_notify(self, *_): - self.art.set_hover(False) - - def _on_album_art_press_event(self, *_): - self.emit("play-pause-clicked", self.book) diff --git a/cozy/view_model/library_view_model.py b/cozy/view_model/library_view_model.py index 116ab552..029b8a40 100644 --- a/cozy/view_model/library_view_model.py +++ b/cozy/view_model/library_view_model.py @@ -16,7 +16,7 @@ from cozy.open_view import OpenView from cozy.report import reporter from cozy.ui.import_failed_dialog import ImportFailedDialog -from cozy.ui.widgets.book_element import BookElement +from cozy.ui.widgets.book_card import BookCard from cozy.view_model.storages_view_model import StoragesViewModel log = logging.getLogger("library_view_model") @@ -122,7 +122,7 @@ def remove_book(self, book: Book): self._notify("books") self._notify("books-filter") - def display_book_filter(self, book_element: BookElement): + def display_book_filter(self, book_element: BookCard): book = book_element.book hide_offline_books = self._application_settings.hide_offline diff --git a/data/gresource.xml b/data/gresource.xml index ed745904..79f0f758 100644 --- a/data/gresource.xml +++ b/data/gresource.xml @@ -2,9 +2,8 @@ style.css - ui/album_element.ui + ui/book_card.ui ui/book_detail.ui - ui/book_element.ui ui/chapter_element.ui ui/error_reporting.ui ui/first_import_button.ui diff --git a/data/style.css b/data/style.css index 6c5893fa..1cf17d53 100644 --- a/data/style.css +++ b/data/style.css @@ -15,10 +15,6 @@ color: white; } -.book_card { - padding: 1rem; -} - .selected { color: @theme_selected_fg_color; background-color: @theme_selected_bg_color; @@ -32,21 +28,6 @@ border-radius: 0.5rem; } -.book_play_button { - background: rgba(50, 50, 50, 1.0); - border: none; - color: white; - box-shadow: none; - transition: none; - -gtk-icon-shadow: none; -} - -.book_play_button:hover, .book_play_button:active, .play_button:hover, .play_button:active { - border: none; - box-shadow: none; - -gtk-icon-shadow: none; -} - .filter-list-box-row { border-radius: 0.5rem; } @@ -92,3 +73,7 @@ .round-6 { border-radius: 6px; } + +.opaque { + opacity: 1; +} diff --git a/data/ui/album_element.blp b/data/ui/album_element.blp deleted file mode 100644 index 84eeb458..00000000 --- a/data/ui/album_element.blp +++ /dev/null @@ -1,65 +0,0 @@ -using Gtk 4.0; - -template $AlbumElement: Box { - orientation: vertical; - - Overlay { - child: Overlay { - child: Image album_art_image {}; - - [overlay] - Revealer album_art_overlay_revealer { - transition-type: crossfade; - transition-duration: 200; - - child: DrawingArea album_art_drawing_area {}; - } - }; - - [overlay] - Revealer play_button_revealer { - name: 'play_button_revealer'; - halign: end; - valign: end; - hexpand: false; - vexpand: false; - transition-type: crossfade; - transition-duration: 200; - - child: Overlay { - halign: end; - valign: end; - margin-end: 10; - margin-bottom: 10; - - child: Button play_button { - width-request: 40; - height-request: 40; - focusable: true; - focus-on-click: false; - tooltip-text: _("Start/Stop playback"); - halign: center; - valign: center; - icon-name: 'media-playback-start-symbolic'; - accessibility { - label: _("Start or pause the playback"); - } - - styles [ - "circular", - "book_play_button", - ] - }; - - [overlay] - DrawingArea progress_drawing_area { - can-target: false; - width-request: 40; - height-request: 40; - halign: center; - valign: center; - } - }; - } - } -} diff --git a/data/ui/book_card.blp b/data/ui/book_card.blp new file mode 100644 index 00000000..94d87249 --- /dev/null +++ b/data/ui/book_card.blp @@ -0,0 +1,143 @@ +using Gtk 4.0; +using Adw 1; + +template $BookCard: FlowBoxChild { + Adw.Clamp { + maximum-size: 250; + Overlay { + [overlay] + Revealer play_revealer { + transition-type: crossfade; + valign: end; + halign: end; + + $BookElementPlayButton play_button { + width-request: 54; + height-request: 54; + margin-end: 18; + margin-bottom: 18; + focusable: true; + focus-on-click: false; + icon_name: "media-playback-start-symbolic"; + tooltip-text: _("Start/Stop playback"); + + clicked => $_play_pause(); + + accessibility { + label: _("Start or pause the playback"); + } + + styles [ + "circular", + "suggested-action", + ] + } + } + + [overlay] + Revealer menu_revealer { + transition-type: crossfade; + valign: start; + halign: end; + + MenuButton menu_button { + margin-end: 6; + margin-top: 6; + menu-model: book_menu; + icon-name: "view-more-symbolic"; + tooltip-text: _("Open Book Menu"); + + styles [ + "circular", + "opaque", + ] + } + } + + Button button { + overflow: hidden; + tooltip-text: _("Open Book Overview"); + + clicked => $_open_book_overview(); + + accessibility { + labelled-by: title; + } + + Box { + orientation: vertical; + + Stack stack { + Picture artwork { + content-fit: contain; + hexpand: true; + vexpand: true; + } + + Image fallback_icon { + pixel-size: 200; + hexpand: true; + vexpand: true; + } + } + + Label title { + label: bind template.title; + tooltip-text: bind template.title; + ellipsize: end; + hexpand: false; + halign: start; + margin-top: 12; + margin-start: 12; + margin-end: 12; + xalign: 0; + + styles [ + "heading", + ] + } + + Label { + label: bind template.author; + tooltip-text: bind template.author; + ellipsize: end; + hexpand: false; + halign: start; + margin-top: 3; + margin-bottom: 12; + margin-start: 12; + margin-end: 12; + xalign: 0; + + styles [ + "dim-label", + "caption", + ] + } + } + styles ["card"] + } + } + } +} + +menu book_menu { + section { + item { + action: 'app.mark_book_as_read'; + label: _("Mark as Read"); + } + + item { + action: 'app.jump_to_book_folder'; + label: _("Open in Files"); + } + } + + section { + item { + action: 'app.remove_book'; + label: _("Delete Permanently…"); + } + } +} diff --git a/data/ui/book_element.blp b/data/ui/book_element.blp deleted file mode 100644 index 6b8acea9..00000000 --- a/data/ui/book_element.blp +++ /dev/null @@ -1,53 +0,0 @@ -using Gtk 4.0; - -template $BookElement: FlowBoxChild { - height-request: 150; - width-request: 150; - - child: Box container_box { - focusable: true; - tooltip-text: _("Open book overview"); - orientation: vertical; - spacing: 1; - valign: start; - - Label name_label { - halign: start; - valign: end; - margin-top: 15; - hexpand: false; - vexpand: false; - wrap: true; - ellipsize: end; - max-width-chars: 20; - xalign: 0; - yalign: 0; - - styles [ - "heading", - ] - } - - Label author_label { - halign: start; - valign: end; - hexpand: false; - vexpand: false; - wrap: true; - ellipsize: end; - max-width-chars: 30; - xalign: 0; - yalign: 0; - - styles [ - "dim-label", - "caption", - ] - } - - styles [ - "card", - "book_card", - ] - }; -} diff --git a/data/ui/chapter_element.ui b/data/ui/chapter_element.ui deleted file mode 100644 index e69de29b..00000000 diff --git a/data/ui/main_window.blp b/data/ui/main_window.blp index 00decdb6..540a2a36 100644 --- a/data/ui/main_window.blp +++ b/data/ui/main_window.blp @@ -148,13 +148,13 @@ Adw.ApplicationWindow app_window { margin-bottom: 18; margin-start: 18; margin-end: 18; - hexpand: true; + halign: center; valign: start; homogeneous: true; column-spacing: 18; row-spacing: 18; min-children-per-line: 1; - max-children-per-line: 10; + max-children-per-line: 6; selection-mode: none; accessibility { diff --git a/data/ui/meson.build b/data/ui/meson.build index 62d2ee72..12bfd3b0 100644 --- a/data/ui/meson.build +++ b/data/ui/meson.build @@ -3,9 +3,8 @@ message('Compiling blueprints') blueprints = custom_target('blueprints', input: files( - 'album_element.blp', + 'book_card.blp', 'book_detail.blp', - 'book_element.blp', 'chapter_element.blp', 'error_reporting.blp', 'first_import_button.blp', diff --git a/po/POTFILES b/po/POTFILES index 2a1de74e..eeeba9d4 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -15,7 +15,7 @@ cozy/ui/file_not_found_dialog.py cozy/ui/import_failed_dialog.py cozy/ui/main_view.py cozy/ui/preferences_window.py -cozy/ui/widgets/book_element.py +cozy/ui/widgets/book_card.py cozy/ui/widgets/book_row.py cozy/ui/widgets/error_reporting.py cozy/ui/widgets/filter_list_box.py @@ -25,8 +25,8 @@ cozy/ui/widgets/storages.py cozy/view_model/headerbar_view_model.py cozy/view_model/library_view_model.py data/ui/album_element.blp +data/ui/book_card.blp data/ui/book_detail.blp -data/ui/book_element.blp data/ui/chapter_element.blp data/ui/error_reporting.blp data/ui/first_import_button.blp