[gnome-software: 3/4] details-page: Use a carousel for the screenshots
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software: 3/4] details-page: Use a carousel for the screenshots
- Date: Fri, 28 May 2021 10:37:24 +0000 (UTC)
commit 97f609863d8a478623a6346eea52996e26357f58
Author: Adrien Plazas <kekun plazas laposte net>
Date: Wed Dec 2 14:48:41 2020 +0100
details-page: Use a carousel for the screenshots
src/gnome-software.gresource.xml | 1 +
src/gs-details-page.c | 208 +--------------------
src/gs-details-page.ui | 86 +--------
src/gs-screenshot-carousel.c | 385 +++++++++++++++++++++++++++++++++++++++
src/gs-screenshot-carousel.h | 27 +++
src/gs-screenshot-carousel.ui | 152 ++++++++++++++++
src/gtk-style-hc.css | 19 +-
src/gtk-style.css | 19 +-
src/meson.build | 1 +
9 files changed, 606 insertions(+), 292 deletions(-)
---
diff --git a/src/gnome-software.gresource.xml b/src/gnome-software.gresource.xml
index b509b0334..8da93bec8 100644
--- a/src/gnome-software.gresource.xml
+++ b/src/gnome-software.gresource.xml
@@ -28,6 +28,7 @@
<file preprocess="xml-stripblanks">gs-review-dialog.ui</file>
<file preprocess="xml-stripblanks">gs-review-histogram.ui</file>
<file preprocess="xml-stripblanks">gs-review-row.ui</file>
+ <file preprocess="xml-stripblanks">gs-screenshot-carousel.ui</file>
<file preprocess="xml-stripblanks">gs-screenshot-image.ui</file>
<file preprocess="xml-stripblanks">gs-search-page.ui</file>
<file preprocess="xml-stripblanks">gs-shell.ui</file>
diff --git a/src/gs-details-page.c b/src/gs-details-page.c
index 565608e71..55984df5b 100644
--- a/src/gs-details-page.c
+++ b/src/gs-details-page.c
@@ -24,7 +24,7 @@
#include "gs-description-box.h"
#include "gs-history-dialog.h"
#include "gs-origin-popover-row.h"
-#include "gs-screenshot-image.h"
+#include "gs-screenshot-carousel.h"
#include "gs-star-widget.h"
#include "gs-review-histogram.h"
#include "gs-review-dialog.h"
@@ -90,10 +90,7 @@ struct _GsDetailsPage
GtkWidget *box_progress2;
GtkWidget *star;
GtkWidget *label_review_count;
- GtkWidget *box_details_screenshot;
- GtkWidget *box_details_screenshot_main;
- GtkWidget *box_details_screenshot_scrolledwindow;
- GtkWidget *box_details_screenshot_thumbnails;
+ GtkWidget *screenshot_carousel;
GtkWidget *box_details_license_list;
GtkWidget *button_details_launch;
GtkWidget *button_details_add_shortcut;
@@ -143,7 +140,6 @@ struct _GsDetailsPage
GtkWidget *row_latest_version;
GtkWidget *version_history_button;
GtkWidget *box_reviews;
- GtkWidget *box_details_screenshot_fallback;
GtkWidget *histogram;
GtkWidget *button_review;
GtkWidget *list_box_reviews;
@@ -580,192 +576,6 @@ gs_details_page_notify_state_changed_cb (GsApp *app,
g_idle_add (gs_details_page_switch_to_idle, g_object_ref (self));
}
-static void
-gs_details_page_load_main_screenshot (GsDetailsPage *self,
- AsScreenshot *screenshot)
-{
- GsScreenshotImage *ssmain;
- g_autoptr(GList) children = NULL;
-
- children = gtk_container_get_children (GTK_CONTAINER (self->box_details_screenshot_main));
- ssmain = GS_SCREENSHOT_IMAGE (children->data);
-
- gs_screenshot_image_set_screenshot (ssmain, screenshot);
- gs_screenshot_image_load_async (ssmain, NULL);
-}
-
-static void
-gs_details_page_screenshot_selected_cb (GtkListBox *list,
- GtkListBoxRow *row,
- GsDetailsPage *self)
-{
- GsScreenshotImage *ssthumb;
- AsScreenshot *ss;
-
- if (row == NULL)
- return;
-
- ssthumb = GS_SCREENSHOT_IMAGE (gtk_bin_get_child (GTK_BIN (row)));
- ss = gs_screenshot_image_get_screenshot (ssthumb);
-
- gs_details_page_load_main_screenshot (self, ss);
-}
-
-static void
-gs_details_page_refresh_screenshots (GsDetailsPage *self)
-{
- GPtrArray *screenshots;
- AsScreenshot *ss;
- GtkWidget *label;
- GtkWidget *list;
- GtkWidget *ssimg;
- GtkWidget *main_screenshot = NULL;
- guint i;
- gboolean is_offline = !gs_plugin_loader_get_network_available (self->plugin_loader);
- guint num_screenshots_loaded = 0;
-
- /* reset the visibility of screenshots */
- gtk_widget_show (self->box_details_screenshot);
-
- /* treat screenshots differently */
- if (gs_app_get_kind (self->app) == AS_COMPONENT_KIND_FONT) {
- gs_container_remove_all (GTK_CONTAINER (self->box_details_screenshot_thumbnails));
- gs_container_remove_all (GTK_CONTAINER (self->box_details_screenshot_main));
- screenshots = gs_app_get_screenshots (self->app);
- for (i = 0; i < screenshots->len; i++) {
- ss = g_ptr_array_index (screenshots, i);
-
- /* set caption */
- label = gtk_label_new (as_screenshot_get_caption (ss));
- g_object_set (label,
- "xalign", 0.0,
- "max-width-chars", 10,
- "wrap", TRUE,
- NULL);
- gtk_container_add (GTK_CONTAINER (self->box_details_screenshot_main), label);
- gtk_widget_set_visible (label, TRUE);
-
- /* set images */
- ssimg = gs_screenshot_image_new (self->session);
- gs_screenshot_image_set_screenshot (GS_SCREENSHOT_IMAGE (ssimg), ss);
- gs_screenshot_image_set_size (GS_SCREENSHOT_IMAGE (ssimg),
- 640,
- 48);
- gs_screenshot_image_load_async (GS_SCREENSHOT_IMAGE (ssimg), NULL);
- gtk_container_add (GTK_CONTAINER (self->box_details_screenshot_main), ssimg);
- gtk_widget_set_visible (ssimg, TRUE);
- }
- gtk_widget_set_visible (self->box_details_screenshot,
- screenshots->len > 0);
- gtk_widget_set_visible (self->box_details_screenshot_fallback,
- screenshots->len == 0 && !is_offline);
- return;
- }
-
- /* fallback warning */
- screenshots = gs_app_get_screenshots (self->app);
- switch (gs_app_get_kind (self->app)) {
- case AS_COMPONENT_KIND_GENERIC:
- case AS_COMPONENT_KIND_CODEC:
- case AS_COMPONENT_KIND_ADDON:
- case AS_COMPONENT_KIND_REPOSITORY:
- case AS_COMPONENT_KIND_FIRMWARE:
- case AS_COMPONENT_KIND_DRIVER:
- case AS_COMPONENT_KIND_INPUT_METHOD:
- case AS_COMPONENT_KIND_LOCALIZATION:
- case AS_COMPONENT_KIND_RUNTIME:
- gtk_widget_set_visible (self->box_details_screenshot_fallback, FALSE);
- break;
- default:
- gtk_widget_set_visible (self->box_details_screenshot_fallback,
- screenshots->len == 0 && !is_offline);
- break;
- }
-
- /* reset screenshots */
- gs_container_remove_all (GTK_CONTAINER (self->box_details_screenshot_main));
- gs_container_remove_all (GTK_CONTAINER (self->box_details_screenshot_thumbnails));
-
- list = gtk_list_box_new ();
- gtk_style_context_add_class (gtk_widget_get_style_context (list), "image-list");
- gtk_widget_show (list);
- gtk_widget_show (self->box_details_screenshot_scrolledwindow);
- gtk_container_add (GTK_CONTAINER (self->box_details_screenshot_thumbnails), list);
-
- for (i = 0; i < screenshots->len; i++) {
- ss = g_ptr_array_index (screenshots, i);
-
- /* we need to load the main screenshot only once if we're online
- * but all times if we're offline (to check which are cached and
- * hide those who aren't) */
- if (is_offline || main_screenshot == NULL) {
- GtkWidget *ssmain = gs_screenshot_image_new (self->session);
- gtk_widget_set_can_focus (gtk_bin_get_child (GTK_BIN (ssmain)), FALSE);
- gs_screenshot_image_set_screenshot (GS_SCREENSHOT_IMAGE (ssmain), ss);
- gs_screenshot_image_set_size (GS_SCREENSHOT_IMAGE (ssmain),
- AS_IMAGE_NORMAL_WIDTH,
- AS_IMAGE_NORMAL_HEIGHT);
- gtk_style_context_add_class (gtk_widget_get_style_context (ssmain),
- "screenshot-image-main");
- gs_screenshot_image_load_async (GS_SCREENSHOT_IMAGE (ssmain), NULL);
-
- /* when we're offline, the load will be immediate, so we
- * can check if it succeeded, and just skip it and its
- * thumbnails otherwise */
- if (is_offline &&
- !gs_screenshot_image_is_showing (GS_SCREENSHOT_IMAGE (ssmain)))
- continue;
-
- /* only set the main_screenshot once */
- if (main_screenshot == NULL) {
- main_screenshot = ssmain;
- gtk_box_pack_start (GTK_BOX (self->box_details_screenshot_main),
- main_screenshot, FALSE, FALSE, 0);
- gtk_widget_show (main_screenshot);
- }
- }
-
- ssimg = gs_screenshot_image_new (self->session);
- gs_screenshot_image_set_screenshot (GS_SCREENSHOT_IMAGE (ssimg), ss);
- gs_screenshot_image_set_size (GS_SCREENSHOT_IMAGE (ssimg),
- AS_IMAGE_THUMBNAIL_WIDTH,
- AS_IMAGE_THUMBNAIL_HEIGHT);
- gtk_style_context_add_class (gtk_widget_get_style_context (ssimg),
- "screenshot-image-thumb");
- gs_screenshot_image_load_async (GS_SCREENSHOT_IMAGE (ssimg), NULL);
- gtk_list_box_insert (GTK_LIST_BOX (list), ssimg, -1);
- gtk_widget_set_visible (ssimg, TRUE);
- ++num_screenshots_loaded;
- }
-
- if (main_screenshot == NULL) {
- gtk_widget_hide (self->box_details_screenshot);
- return;
- }
-
- /* reload the main screenshot with a larger size if it's the only screenshot
- * available */
- if (num_screenshots_loaded == 1) {
- gs_screenshot_image_set_size (GS_SCREENSHOT_IMAGE (main_screenshot),
- AS_IMAGE_LARGE_WIDTH,
- AS_IMAGE_LARGE_HEIGHT);
- gs_screenshot_image_load_async (GS_SCREENSHOT_IMAGE (main_screenshot), NULL);
- }
-
- if (num_screenshots_loaded <= 1) {
- gtk_widget_hide (self->box_details_screenshot_thumbnails);
- return;
- }
-
- gtk_widget_show (self->box_details_screenshot_thumbnails);
- gtk_list_box_set_selection_mode (GTK_LIST_BOX (list), GTK_SELECTION_BROWSE);
- g_signal_connect (list, "row-selected",
- G_CALLBACK (gs_details_page_screenshot_selected_cb),
- self);
- gtk_list_box_select_row (GTK_LIST_BOX (list),
- gtk_list_box_get_row_at_index (GTK_LIST_BOX (list), 0));
-}
-
static void
gs_details_page_website_cb (GtkWidget *widget, GsDetailsPage *self)
{
@@ -2032,6 +1842,8 @@ gs_details_page_load_stage2 (GsDetailsPage *self)
g_autofree gchar *tmp = NULL;
g_autoptr(GsPluginJob) plugin_job1 = NULL;
g_autoptr(GsPluginJob) plugin_job2 = NULL;
+ gboolean is_online = gs_plugin_loader_get_network_available (self->plugin_loader);
+ gboolean has_screenshots;
/* print what we've got */
tmp = gs_app_to_string (self->app);
@@ -2039,7 +1851,9 @@ gs_details_page_load_stage2 (GsDetailsPage *self)
/* update UI */
gs_details_page_set_state (self, GS_DETAILS_PAGE_STATE_READY);
- gs_details_page_refresh_screenshots (self);
+ gs_screenshot_carousel_load_screenshots (GS_SCREENSHOT_CAROUSEL (self->screenshot_carousel),
self->app, is_online, NULL);
+ has_screenshots = gs_screenshot_carousel_get_has_screenshots (GS_SCREENSHOT_CAROUSEL
(self->screenshot_carousel));
+ gtk_widget_set_visible (self->screenshot_carousel, has_screenshots);
gs_details_page_refresh_addons (self);
gs_details_page_refresh_reviews (self);
gs_details_page_refresh_all (self);
@@ -3021,10 +2835,7 @@ gs_details_page_class_init (GsDetailsPageClass *klass)
gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, box_progress2);
gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, star);
gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_review_count);
- gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, box_details_screenshot);
- gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, box_details_screenshot_main);
- gtk_widget_class_bind_template_child (widget_class, GsDetailsPage,
box_details_screenshot_scrolledwindow);
- gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, box_details_screenshot_thumbnails);
+ gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, screenshot_carousel);
gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, box_details_license_list);
gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, button_details_launch);
gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, button_details_add_shortcut);
@@ -3072,7 +2883,6 @@ gs_details_page_class_init (GsDetailsPageClass *klass)
gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, row_latest_version);
gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, version_history_button);
gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, box_reviews);
- gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, box_details_screenshot_fallback);
gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, histogram);
gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, button_review);
gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, list_box_reviews);
@@ -3115,6 +2925,8 @@ gs_details_page_class_init (GsDetailsPageClass *klass)
static void
gs_details_page_init (GsDetailsPage *self)
{
+ g_type_ensure (GS_TYPE_SCREENSHOT_CAROUSEL);
+
gtk_widget_init_template (GTK_WIDGET (self));
/* setup networking */
diff --git a/src/gs-details-page.ui b/src/gs-details-page.ui
index 612418afe..bafb11d15 100644
--- a/src/gs-details-page.ui
+++ b/src/gs-details-page.ui
@@ -65,11 +65,11 @@
<property name="visible">True</property>
<property name="halign">fill</property>
<property name="valign">start</property>
- <property name="margin_top">4</property>
- <property name="margin_bottom">4</property>
- <property name="border_width">24</property>
<property name="spacing">18</property>
<property name="hexpand">False</property>
+ <style>
+ <class name="details-page"/>
+ </style>
<child>
<object class="GsFixedSizeBin">
<property name="visible">True</property>
@@ -317,86 +317,8 @@
</child>
<child>
- <object class="GsFixedSizeBin">
+ <object class="GsScreenshotCarousel" id="screenshot_carousel">
<property name="visible">True</property>
- <property name="preferred-width">860</property>
- <child>
- <object class="GtkBox" id="box_details_screenshot">
- <property name="visible">True</property>
- <property name="margin_bottom">14</property>
- <property name="spacing">9</property>
- <child>
- <object class="GtkBox" id="box_details_screenshot_main">
- <property name="visible">True</property>
- <property name="orientation">vertical</property>
- <property name="spacing">6</property>
- <property name="hexpand">True</property>
- <child>
- <placeholder/>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkScrolledWindow" id="box_details_screenshot_scrolledwindow">
- <property name="visible">True</property>
- <property name="shadow_type">none</property>
- <property name="can_focus">True</property>
- <property name="hscrollbar_policy">never</property>
- <property name="vscrollbar_policy">automatic</property>
- <child>
- <object class="GtkBox" id="box_details_screenshot_thumbnails">
- <property name="visible">True</property>
- <property name="orientation">vertical</property>
- <property name="spacing">7</property>
- <child>
- <placeholder/>
- </child>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
-
- <child>
- <object class="GsFixedSizeBin">
- <property name="visible">True</property>
- <property name="preferred-width">860</property>
- <child>
- <object class="GtkBox" id="box_details_screenshot_fallback">
- <property name="visible">True</property>
- <property name="orientation">vertical</property>
- <property name="spacing">6</property>
- <property name="width_request">752</property>
- <property name="height_request">423</property>
- <property name="hexpand">True</property>
- <property name="halign">fill</property>
- <style>
- <class name="screenshot-image"/>
- </style>
- <child>
- <object class="GtkImage">
- <property name="visible">True</property>
- <property name="pixel_size">64</property>
- <property name="icon_name">camera-photo-symbolic</property>
- <property name="icon_size">6</property>
- <property name="valign">end</property>
- <property name="vexpand">True</property>
- </object>
- </child>
- <child>
- <object class="GtkLabel">
- <property name="visible">True</property>
- <property name="halign">center</property>
- <property name="valign">start</property>
- <property name="vexpand">True</property>
- <property name="label" translatable="yes">No screenshot provided</property>
- </object>
- </child>
- </object>
- </child>
</object>
</child>
diff --git a/src/gs-screenshot-carousel.c b/src/gs-screenshot-carousel.c
new file mode 100644
index 000000000..7a3ca5292
--- /dev/null
+++ b/src/gs-screenshot-carousel.c
@@ -0,0 +1,385 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2013-2016 Richard Hughes <richard hughsie com>
+ * Copyright (C) 2013 Matthias Clasen <mclasen redhat com>
+ * Copyright (C) 2015-2019 Kalev Lember <klember redhat com>
+ * Copyright (C) 2019 Joaquim Rocha <jrocha endlessm com>
+ * Copyright (C) 2021 Adrien Plazas <adrien plazas puri sm>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+/**
+ * SECTION:gs-screenshot-carousel
+ * @short_description: A carousel presenting the screenshots of a #GsApp
+ *
+ * #GsScreenshotCarousel loads screenshots from a #GsApp and present them to the
+ * users.
+ *
+ * If the carousel doesn't have any screenshot to display, an empty state
+ * fallback will be presented, and it will be considered to have screenshots as
+ * long as it is trying to load some.
+ *
+ * Since: 41
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <handy.h>
+#include <locale.h>
+#include <math.h>
+#include <string.h>
+
+#include "gs-common.h"
+#include "gs-utils.h"
+
+#include "gs-screenshot-carousel.h"
+#include "gs-app-version-history-row.h"
+#include "gs-app-version-history-dialog.h"
+#include "gs-description-box.h"
+#include "gs-history-dialog.h"
+#include "gs-origin-popover-row.h"
+#include "gs-screenshot-image.h"
+
+struct _GsScreenshotCarousel
+{
+ GtkStack parent_instance;
+
+ SoupSession *session; /* (owned) (not nullable) */
+ gboolean has_screenshots;
+
+ GtkWidget *button_next;
+ GtkWidget *button_next_revealer;
+ GtkWidget *button_previous;
+ GtkWidget *button_previous_revealer;
+ GtkWidget *carousel;
+ GtkWidget *carousel_indicator;
+};
+
+typedef enum {
+ PROP_HAS_SCREENSHOTS = 1,
+} GsScreenshotCarouselProperty;
+
+static GParamSpec *obj_props[PROP_HAS_SCREENSHOTS + 1] = { NULL, };
+
+G_DEFINE_TYPE (GsScreenshotCarousel, gs_screenshot_carousel, GTK_TYPE_STACK)
+
+static void
+_set_state (GsScreenshotCarousel *self, guint length, gboolean allow_fallback, gboolean is_online)
+{
+ gboolean has_screenshots;
+
+ gtk_widget_set_visible (self->carousel_indicator, length > 1);
+ gtk_stack_set_visible_child_name (GTK_STACK (self), length > 0 ? "carousel" : "fallback");
+
+ has_screenshots = length > 0 || (allow_fallback && is_online);
+ if (self->has_screenshots != has_screenshots) {
+ self->has_screenshots = has_screenshots;
+ g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_HAS_SCREENSHOTS]);
+ }
+}
+
+/**
+ * gs_screenshot_carousel_load_screenshots:
+ * @self: a #GsScreenshotCarousel
+ * @app: app to load the screenshots for
+ * @is_online: %TRUE if the network is expected to work to load screenshots, %FALSE otherwise
+ *
+ * Clear the existing set of screenshot images, and load the
+ * screenshots for @app instead. Display them, or display a
+ * fallback if no screenshots could be loaded (and the fallback
+ * is enabled).
+ *
+ * This will start some asynchronous network requests to download
+ * screenshots. Those requests may continue after this function
+ * call returns.
+ *
+ * Since: 41
+ */
+void
+gs_screenshot_carousel_load_screenshots (GsScreenshotCarousel *self, GsApp *app, gboolean is_online,
GCancellable *cancellable)
+{
+ GPtrArray *screenshots;
+ gboolean allow_fallback;
+ guint num_screenshots_loaded = 0;
+
+ g_return_if_fail (GS_IS_SCREENSHOT_CAROUSEL (self));
+ g_return_if_fail (GS_IS_APP (app));
+
+ /* treat screenshots differently */
+ if (gs_app_get_kind (app) == AS_COMPONENT_KIND_FONT) {
+ gs_container_remove_all (GTK_CONTAINER (self->carousel));
+ screenshots = gs_app_get_screenshots (app);
+ for (guint i = 0; i < screenshots->len && !g_cancellable_is_cancelled (cancellable); i++) {
+ AsScreenshot *ss = g_ptr_array_index (screenshots, i);
+ GtkWidget *ssimg;
+ GtkWidget *label;
+
+ /* set caption */
+ label = gtk_label_new (as_screenshot_get_caption (ss));
+ g_object_set (label,
+ "xalign", 0.0,
+ "max-width-chars", 10,
+ "wrap", TRUE,
+ NULL);
+ gtk_container_add (GTK_CONTAINER (self->carousel), label);
+ gtk_widget_set_visible (label, TRUE);
+
+ /* set images */
+ ssimg = gs_screenshot_image_new (self->session);
+ gs_screenshot_image_set_screenshot (GS_SCREENSHOT_IMAGE (ssimg), ss);
+ gs_screenshot_image_set_size (GS_SCREENSHOT_IMAGE (ssimg),
+ 640,
+ 48);
+ gs_screenshot_image_load_async (GS_SCREENSHOT_IMAGE (ssimg), cancellable);
+ gtk_container_add (GTK_CONTAINER (self->carousel), ssimg);
+ gtk_widget_set_visible (ssimg, TRUE);
+ gs_screenshot_image_set_description (GS_SCREENSHOT_IMAGE (ssimg),
+ as_screenshot_get_caption (ss));
+ }
+ _set_state (self, screenshots->len, TRUE, is_online);
+ return;
+ }
+
+ /* fallback warning */
+ screenshots = gs_app_get_screenshots (app);
+ switch (gs_app_get_kind (app)) {
+ case AS_COMPONENT_KIND_GENERIC:
+ case AS_COMPONENT_KIND_CODEC:
+ case AS_COMPONENT_KIND_ADDON:
+ case AS_COMPONENT_KIND_REPOSITORY:
+ case AS_COMPONENT_KIND_FIRMWARE:
+ case AS_COMPONENT_KIND_DRIVER:
+ case AS_COMPONENT_KIND_INPUT_METHOD:
+ case AS_COMPONENT_KIND_LOCALIZATION:
+ case AS_COMPONENT_KIND_RUNTIME:
+ allow_fallback = FALSE;
+ break;
+ default:
+ allow_fallback = TRUE;
+ break;
+ }
+
+ /* reset screenshots */
+ gs_container_remove_all (GTK_CONTAINER (self->carousel));
+
+ for (guint i = 0; i < screenshots->len && !g_cancellable_is_cancelled (cancellable); i++) {
+ AsScreenshot *ss = g_ptr_array_index (screenshots, i);
+ GtkWidget *ssimg = gs_screenshot_image_new (self->session);
+ gtk_widget_set_can_focus (gtk_bin_get_child (GTK_BIN (ssimg)), FALSE);
+ gs_screenshot_image_set_screenshot (GS_SCREENSHOT_IMAGE (ssimg), ss);
+ gs_screenshot_image_set_size (GS_SCREENSHOT_IMAGE (ssimg),
+ AS_IMAGE_NORMAL_WIDTH,
+ AS_IMAGE_NORMAL_HEIGHT);
+ gtk_style_context_add_class (gtk_widget_get_style_context (ssimg),
+ "screenshot-image-main");
+ gs_screenshot_image_load_async (GS_SCREENSHOT_IMAGE (ssimg), cancellable);
+
+ /* when we're offline, the load will be immediate, so we
+ * can check if it succeeded, and just skip it and its
+ * thumbnails otherwise */
+ if (!is_online &&
+ !gs_screenshot_image_is_showing (GS_SCREENSHOT_IMAGE (ssimg))) {
+ g_object_unref (ssimg);
+ continue;
+ }
+
+ gtk_container_add (GTK_CONTAINER (self->carousel),
+ ssimg);
+ gtk_widget_show (ssimg);
+ gs_screenshot_image_set_description (GS_SCREENSHOT_IMAGE (ssimg),
+ as_screenshot_get_caption (ss));
+ ++num_screenshots_loaded;
+ }
+
+ _set_state (self, num_screenshots_loaded, allow_fallback, is_online);
+}
+
+/**
+ * gs_screenshot_carousel_get_has_screenshots:
+ * @self: a #GsScreenshotCarousel
+ *
+ * Get whether the carousel contains any screenshots.
+ *
+ * Returns: %TRUE if there are screenshots, %FALSE otherwise
+ *
+ * Since: 41
+ */
+gboolean
+gs_screenshot_carousel_get_has_screenshots (GsScreenshotCarousel *self)
+{
+ g_return_val_if_fail (GS_IS_SCREENSHOT_CAROUSEL (self), FALSE);
+
+ return self->has_screenshots;
+}
+
+static void
+_carousel_navigate (HdyCarousel *carousel, HdyNavigationDirection direction)
+{
+ g_autoptr (GList) children = NULL;
+ GtkWidget *child;
+ gdouble position;
+ guint n_children;
+
+ children = gtk_container_get_children (GTK_CONTAINER (carousel));
+ n_children = g_list_length (children);
+
+ position = hdy_carousel_get_position (carousel);
+ position += (direction == HDY_NAVIGATION_DIRECTION_BACK) ? -1 : 1;
+ /* Round the position to the closest integer in the valid range. */
+ position = round (position);
+ position = MIN (position, n_children - 1);
+ position = MAX (0, position);
+
+ child = g_list_nth_data (children, position);
+ if (child)
+ hdy_carousel_scroll_to (carousel, child);
+}
+
+static void
+gs_screenshot_carousel_update_buttons (GsScreenshotCarousel *self)
+{
+ gdouble position = hdy_carousel_get_position (HDY_CAROUSEL (self->carousel));
+ guint n_pages = hdy_carousel_get_n_pages (HDY_CAROUSEL (self->carousel));
+ gtk_revealer_set_reveal_child (GTK_REVEALER (self->button_previous_revealer), position >= 0.5);
+ gtk_revealer_set_reveal_child (GTK_REVEALER (self->button_next_revealer), position < n_pages - 1.5);
+}
+
+static void
+gs_screenshot_carousel_notify_n_pages_cb (GsScreenshotCarousel *self)
+{
+ gs_screenshot_carousel_update_buttons (self);
+}
+
+static void
+gs_screenshot_carousel_notify_position_cb (GsScreenshotCarousel *self)
+{
+ gs_screenshot_carousel_update_buttons (self);
+}
+
+static void
+gs_screenshot_carousel_button_previous_clicked_cb (GsScreenshotCarousel *self)
+{
+ _carousel_navigate (HDY_CAROUSEL (self->carousel),
+ HDY_NAVIGATION_DIRECTION_BACK);
+}
+
+static void
+gs_screenshot_carousel_button_next_clicked_cb (GsScreenshotCarousel *self)
+{
+ _carousel_navigate (HDY_CAROUSEL (self->carousel),
+ HDY_NAVIGATION_DIRECTION_FORWARD);
+}
+
+static void
+gs_screenshot_carousel_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+ GsScreenshotCarousel *self = GS_SCREENSHOT_CAROUSEL (object);
+
+ switch ((GsScreenshotCarouselProperty) prop_id) {
+ case PROP_HAS_SCREENSHOTS:
+ g_value_set_boolean (value, self->has_screenshots);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gs_screenshot_carousel_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+ switch ((GsScreenshotCarouselProperty) prop_id) {
+ case PROP_HAS_SCREENSHOTS:
+ /* Read only */
+ g_assert_not_reached ();
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gs_screenshot_carousel_dispose (GObject *object)
+{
+ GsScreenshotCarousel *self = GS_SCREENSHOT_CAROUSEL (object);
+
+ g_clear_object (&self->session);
+
+ G_OBJECT_CLASS (gs_screenshot_carousel_parent_class)->dispose (object);
+}
+
+static void
+gs_screenshot_carousel_class_init (GsScreenshotCarouselClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = gs_screenshot_carousel_dispose;
+ object_class->get_property = gs_screenshot_carousel_get_property;
+ object_class->set_property = gs_screenshot_carousel_set_property;
+
+ /**
+ * GsScreenshotCarousel:has-screenshots:
+ *
+ * Whether the carousel contains any screenshots.
+ *
+ * Since: 41
+ */
+ obj_props[PROP_HAS_SCREENSHOTS] =
+ g_param_spec_boolean ("has-screenshots", NULL, NULL,
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, G_N_ELEMENTS (obj_props), obj_props);
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/Software/gs-screenshot-carousel.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, GsScreenshotCarousel, button_next);
+ gtk_widget_class_bind_template_child (widget_class, GsScreenshotCarousel, button_next_revealer);
+ gtk_widget_class_bind_template_child (widget_class, GsScreenshotCarousel, button_previous);
+ gtk_widget_class_bind_template_child (widget_class, GsScreenshotCarousel, button_previous_revealer);
+ gtk_widget_class_bind_template_child (widget_class, GsScreenshotCarousel, carousel);
+ gtk_widget_class_bind_template_child (widget_class, GsScreenshotCarousel, carousel_indicator);
+
+ gtk_widget_class_bind_template_callback (widget_class, gs_screenshot_carousel_notify_n_pages_cb);
+ gtk_widget_class_bind_template_callback (widget_class, gs_screenshot_carousel_notify_position_cb);
+ gtk_widget_class_bind_template_callback (widget_class,
gs_screenshot_carousel_button_previous_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, gs_screenshot_carousel_button_next_clicked_cb);
+
+ gtk_widget_class_set_css_name (widget_class, "screenshot-carousel");
+}
+
+static void
+gs_screenshot_carousel_init (GsScreenshotCarousel *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+#if HDY_CHECK_VERSION(1, 3, 0)
+ /* Disable scrolling through the carousel, as it’s typically used
+ * in application pages which are themselves scrollable. */
+ hdy_carousel_set_allow_scroll_wheel (HDY_CAROUSEL (self->carousel), FALSE);
+#endif
+
+ /* setup networking */
+ self->session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT, gs_user_agent (),
+ NULL);
+}
+
+/**
+ * gs_screenshot_carousel_new:
+ *
+ * Create a new #GsScreenshotCarousel.
+ *
+ * Returns: (transfer full): a new #GsScreenshotCarousel
+ *
+ * Since: 41
+ */
+GsScreenshotCarousel *
+gs_screenshot_carousel_new (void)
+{
+ return GS_SCREENSHOT_CAROUSEL (g_object_new (GS_TYPE_SCREENSHOT_CAROUSEL, NULL));
+}
diff --git a/src/gs-screenshot-carousel.h b/src/gs-screenshot-carousel.h
new file mode 100644
index 000000000..ea208280a
--- /dev/null
+++ b/src/gs-screenshot-carousel.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2021 Adrien Plazas <adrien plazas puri sm>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include "gs-app.h"
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_SCREENSHOT_CAROUSEL (gs_screenshot_carousel_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsScreenshotCarousel, gs_screenshot_carousel, GS, SCREENSHOT_CAROUSEL, GtkStack)
+
+GsScreenshotCarousel *gs_screenshot_carousel_new (void);
+void gs_screenshot_carousel_load_screenshots (GsScreenshotCarousel *self,
+ GsApp *app,
+ gboolean is_online,
+ GCancellable *cancellable);
+gboolean gs_screenshot_carousel_get_has_screenshots (GsScreenshotCarousel *self);
+
+G_END_DECLS
diff --git a/src/gs-screenshot-carousel.ui b/src/gs-screenshot-carousel.ui
new file mode 100644
index 000000000..af766c3e4
--- /dev/null
+++ b/src/gs-screenshot-carousel.ui
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.10"/>
+ <requires lib="handy" version="1.0"/>
+ <template class="GsScreenshotCarousel" parent="GtkStack">
+ <property name="no-show-all">True</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <style>
+ <class name="frame"/>
+ <class name="view"/>
+ </style>
+ <child>
+ <object class="GtkOverlay">
+ <property name="visible">True</property>
+ <child>
+ <object class="HdyCarousel" id="carousel">
+ <property name="visible">True</property>
+ <signal name="notify::n-pages" handler="gs_screenshot_carousel_notify_n_pages_cb"
swapped="yes"/>
+ <signal name="notify::position" handler="gs_screenshot_carousel_notify_position_cb"
swapped="yes"/>
+ </object>
+ </child>
+ <child type="overlay">
+ <object class="GtkRevealer" id="button_previous_revealer">
+ <property name="halign">start</property>
+ <property name="transition-type">crossfade</property>
+ <property name="valign">center</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkButton" id="button_previous">
+ <property name="visible">True</property>
+ <signal name="clicked" handler="gs_screenshot_carousel_button_previous_clicked_cb"
swapped="yes"/>
+ <child internal-child="accessible">
+ <object class="AtkObject">
+ <!-- Translators: This is the accessible description for a button to go to the
previous screenshot in the screenshot carousel. -->
+ <property name="accessible-name" translatable="yes">Previous Screenshot</property>
+ </object>
+ </child>
+ <style>
+ <class name="circular"/>
+ <class name="image-button"/>
+ <class name="osd"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="halign">center</property>
+ <property name="icon-name">go-previous-symbolic</property>
+ <property name="icon-size">1</property>
+ <property name="valign">center</property>
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pass-through">True</property>
+ </packing>
+ </child>
+ <child type="overlay">
+ <object class="GtkRevealer" id="button_next_revealer">
+ <property name="halign">end</property>
+ <property name="transition-type">crossfade</property>
+ <property name="valign">center</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkButton" id="button_next">
+ <property name="visible">True</property>
+ <signal name="clicked" handler="gs_screenshot_carousel_button_next_clicked_cb"
swapped="yes"/>
+ <child internal-child="accessible">
+ <object class="AtkObject">
+ <!-- Translators: This is the accessible description for a button to go to the next
screenshot in the screenshot carousel. -->
+ <property name="accessible-name" translatable="yes">Next Screenshot</property>
+ </object>
+ </child>
+ <style>
+ <class name="circular"/>
+ <class name="image-button"/>
+ <class name="osd"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="halign">center</property>
+ <property name="icon-name">go-next-symbolic</property>
+ <property name="icon-size">1</property>
+ <property name="valign">center</property>
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pass-through">True</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="HdyCarouselIndicatorDots" id="carousel_indicator">
+ <property name="carousel">carousel</property>
+ <property name="visible">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">carousel</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <!-- Arbitrary sizes chosen to tile together at 16:9;
+ see https://blogs.gnome.org/hughsie/2014/07/02/blurry-screenshots-in-gnome-software/ -->
+ <property name="width_request">752</property>
+ <property name="height_request">423</property>
+ <property name="hexpand">True</property>
+ <property name="halign">fill</property>
+ <style>
+ <class name="screenshot-image"/>
+ <class name="frame"/>
+ <class name="view"/>
+ </style>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="pixel_size">64</property>
+ <property name="icon_name">camera-photo-symbolic</property>
+ <property name="valign">end</property>
+ <property name="vexpand">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="halign">center</property>
+ <property name="valign">start</property>
+ <property name="vexpand">True</property>
+ <property name="label" translatable="yes">No screenshot provided</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">fallback</property>
+ </packing>
+ </child>
+ </template>
+</interface>
diff --git a/src/gtk-style-hc.css b/src/gtk-style-hc.css
index a0f2938f3..95ad283dd 100644
--- a/src/gtk-style-hc.css
+++ b/src/gtk-style-hc.css
@@ -53,13 +53,20 @@ sidebar row.needs-attention:selected > box > label {
background-image: none;
}
-.screenshot-image, .screenshot-image-thumb {
- background-image: none;
- background-color: shade(@theme_bg_color, 0.9);
+screenshot-carousel > box {
+ border-width: 1px 0;
+}
+
+screenshot-carousel button {
+ margin: 12px;
+}
+
+.screenshot-image-main .image1, .screenshot-image-main .image2 {
+ margin-top: 6px;
+ margin-bottom: 12px;
+ margin-left: 6px;
+ margin-right: 6px;
}
-.screenshot-image { border-radius: 5px; }
-.screenshot-image-thumb { border-radius: 3px; }
-.screenshot-image-main .image1, .screenshot-image-main .image2 { margin: 12px; }
.app-tile-label {
font-size: 105%;
diff --git a/src/gtk-style.css b/src/gtk-style.css
index e49f91335..4b701bd0f 100644
--- a/src/gtk-style.css
+++ b/src/gtk-style.css
@@ -214,13 +214,20 @@ sidebar row.needs-attention:selected > box > label {
opacity: 0.25;
}
-.screenshot-image, .screenshot-image-thumb {
- background-image: none;
- background-color: shade(@theme_bg_color, 0.9);
+screenshot-carousel > box {
+ border-width: 1px 0;
+}
+
+screenshot-carousel button {
+ margin: 12px;
+}
+
+.screenshot-image-main .image1, .screenshot-image-main .image2 {
+ margin-top: 6px;
+ margin-bottom: 12px;
+ margin-left: 6px;
+ margin-right: 6px;
}
-.screenshot-image { border-radius: 5px; }
-.screenshot-image-thumb { border-radius: 3px; }
-.screenshot-image-main .image1, .screenshot-image-main .image2 { margin: 12px; }
.app-tile-label {
font-size: 105%;
diff --git a/src/meson.build b/src/meson.build
index 7925a4b2e..430eb0c4b 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -57,6 +57,7 @@ gnome_software_sources = [
'gs-review-histogram.c',
'gs-review-row.c',
'gs-rounded-bin.c',
+ 'gs-screenshot-carousel.c',
'gs-screenshot-image.c',
'gs-search-page.c',
'gs-shell.c',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]