*
* Copyright 2025 Alexander Vanhee
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "config.h"
#include "bz-screenshot-page.h"
#include "bz-screenshot.h"
#include "bz-zoom.h"
#include <glib/gi18n.h>
#define MOUSE_BACK_BUTTON 8
#define SPRING_DAMPING_RATIO 1.05
#define SPRING_MASS 1.0
#define SPRING_STIFFNESS 1000.0
#define FADE_DURATION_MS 350
struct _BzScreenshotPage
{
AdwBin parent_instance;
AdwCarousel *carousel;
AdwToastOverlay *toast_overlay;
GListModel *screenshots;
GListModel *captions;
guint current_index;
guint initial_index;
gboolean is_zoomed;
gboolean reduce_motion;
GtkWidget *source_widget;
GdkTexture *source_texture;
graphene_rect_t source_bounds_at_map;
AdwAnimation *animation;
double animation_progress;
gboolean closing;
};
G_DEFINE_FINAL_TYPE (BzScreenshotPage, bz_screenshot_page, ADW_TYPE_BIN)
enum
{
PROP_0,
PROP_SCREENSHOTS,
PROP_CURRENT_INDEX,
PROP_CURRENT_CAPTION,
PROP_IS_ZOOMED,
LAST_PROP
};
static GParamSpec *props[LAST_PROP] = { 0 };
static GdkTexture *render_widget_to_texture (GtkWidget *widget);
static void on_animation_value (AdwAnimation *animation,
GParamSpec *pspec,
BzScreenshotPage *self);
static void on_fade_animation_value (AdwAnimation *animation,
GParamSpec *pspec,
BzScreenshotPage *self);
static void on_close_animation_done (AdwAnimation *animation,
BzScreenshotPage *self);
static void on_zoom_level_changed (BzZoom *zoom,
GParamSpec *pspec,
BzScreenshotPage *self);
static void update_is_zoomed (BzScreenshotPage *self);
static BzZoom *get_current_zoom (BzScreenshotPage *self,
GtkScrolledWindow **scrolled_window);
static void populate_carousel (BzScreenshotPage *self);
static void on_button_pressed (GtkGestureClick *gesture,
int n_press,
double x,
double y,
BzScreenshotPage *self);
static void on_swipe (BzScreenshotPage *self,
gdouble vel_x,
gdouble vel_y);
static gboolean on_scroll (BzScreenshotPage *self,
gdouble dx,
gdouble dy,
GtkEventControllerScroll *controller);
static void
bz_screenshot_page_map (GtkWidget *widget)
{
BzScreenshotPage *self = BZ_SCREENSHOT_PAGE (widget);
AdwAnimationTarget *target = NULL;
AdwSpringParams *params = NULL;
GtkSettings *settings = NULL;
GtkReducedMotion reduced_motion = GTK_REDUCED_MOTION_NO_PREFERENCE;
GTK_WIDGET_CLASS (bz_screenshot_page_parent_class)->map (widget);
gtk_widget_child_focus (widget, GTK_DIR_TAB_FORWARD);
settings = gtk_widget_get_settings (widget);
g_object_get (settings, "gtk-interface-reduced-motion", &reduced_motion, NULL);
self->reduce_motion = reduced_motion != GTK_REDUCED_MOTION_NO_PREFERENCE;
self->closing = FALSE;
if (self->reduce_motion)
{
self->animation_progress = 1.0;
g_clear_object (&self->source_texture);
gtk_widget_set_opacity (widget, 0.0);
target = adw_callback_animation_target_new (
(AdwAnimationTargetFunc) gtk_widget_queue_draw, self, NULL);
self->animation = adw_timed_animation_new (widget, 0.0, 1.0, FADE_DURATION_MS, target);
g_signal_connect (self->animation, "notify::value",
G_CALLBACK (on_fade_animation_value), self);
adw_animation_play (self->animation);
return;
}
if (self->source_widget != NULL)
{
self->source_texture = render_widget_to_texture (self->source_widget);
if (!gtk_widget_compute_bounds (self->source_widget, widget, &self->source_bounds_at_map))
graphene_rect_init (&self->source_bounds_at_map, 0, 0,
gtk_widget_get_width (widget), gtk_widget_get_height (widget));
gtk_widget_set_opacity (self->source_widget, 0.0);
}
self->animation_progress = 0.0;
target = adw_callback_animation_target_new ((AdwAnimationTargetFunc) gtk_widget_queue_draw, self, NULL);
params = adw_spring_params_new (SPRING_DAMPING_RATIO, SPRING_MASS, SPRING_STIFFNESS);
self->animation = adw_spring_animation_new (widget, 0.0, 1.0, params, target);
adw_spring_animation_set_clamp (ADW_SPRING_ANIMATION (self->animation), TRUE);
adw_spring_animation_set_epsilon (ADW_SPRING_ANIMATION (self->animation), 0.0001);
adw_spring_animation_set_initial_velocity (ADW_SPRING_ANIMATION (self->animation), -1.2);
g_signal_connect (self->animation, "notify::value", G_CALLBACK (on_animation_value), self);
adw_animation_play (self->animation);
}
static void
bz_screenshot_page_unmap (GtkWidget *widget)
{
BzScreenshotPage *self = BZ_SCREENSHOT_PAGE (widget);
if (self->source_widget != NULL && !self->closing)
gtk_widget_set_opacity (self->source_widget, 1.0);
GTK_WIDGET_CLASS (bz_screenshot_page_parent_class)->unmap (widget);
}
static void
bz_screenshot_page_snapshot (GtkWidget *widget, GtkSnapshot *snapshot)
{
BzScreenshotPage *self = BZ_SCREENSHOT_PAGE (widget);
double progress = 0.0;
int width = 0;
int height = 0;
float rev = 0.0f;
float x = 0.0f;
float y = 0.0f;
float w = 0.0f;
float h = 0.0f;
graphene_rect_t lerped;
graphene_rect_t source_bounds;
progress = self->animation_progress;
width = gtk_widget_get_width (widget);
height = gtk_widget_get_height (widget);
if (self->source_texture == NULL || progress >= 1.0)
{
GTK_WIDGET_CLASS (bz_screenshot_page_parent_class)->snapshot (widget, snapshot);
return;
}
if (self->source_widget != NULL &&
!gtk_widget_compute_bounds (self->source_widget, widget, &source_bounds))
source_bounds = self->source_bounds_at_map;
else if (self->source_widget == NULL)
source_bounds = self->source_bounds_at_map;
rev = (float) (1.0 - progress);
x = source_bounds.origin.x * rev + 0.0f * (float) progress;
y = source_bounds.origin.y * rev + 0.0f * (float) progress;
w = source_bounds.size.width * rev + (float) width * (float) progress;
h = source_bounds.size.height * rev + (float) height * (float) progress;
graphene_rect_init (&lerped, x, y, w, h);
gtk_snapshot_push_clip (snapshot, &lerped);
gtk_snapshot_push_cross_fade (snapshot, progress);
gtk_snapshot_save (snapshot);
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (x, y));
gtk_snapshot_scale (snapshot,
w / source_bounds.size.width,
h / source_bounds.size.height);
gdk_paintable_snapshot (GDK_PAINTABLE (self->source_texture), snapshot,
source_bounds.size.width,
source_bounds.size.height);
gtk_snapshot_restore (snapshot);
gtk_snapshot_pop (snapshot);
gtk_snapshot_save (snapshot);
gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (x, y));
gtk_snapshot_scale (snapshot,
w / (float) width,
h / (float) height);
GTK_WIDGET_CLASS (bz_screenshot_page_parent_class)->snapshot (widget, snapshot);
gtk_snapshot_restore (snapshot);
gtk_snapshot_pop (snapshot);
gtk_snapshot_pop (snapshot);
}
static void
back_clicked (BzScreenshotPage *self)
{
AdwAnimationTarget *target = NULL;
AdwSpringParams *params = NULL;
GtkWidget *parent = NULL;
if (self->closing)
return;
if (self->source_widget != NULL)
{
self->closing = TRUE;
gtk_widget_set_can_target (GTK_WIDGET (self), FALSE);
if (self->animation != NULL)
{
g_signal_handlers_disconnect_by_func (self->animation, on_animation_value, self);
g_signal_handlers_disconnect_by_func (self->animation, on_fade_animation_value, self);
g_clear_object (&self->animation);
}
target = adw_callback_animation_target_new (
(AdwAnimationTargetFunc) gtk_widget_queue_draw, self, NULL);
if (self->reduce_motion)
{
self->animation = adw_timed_animation_new (
GTK_WIDGET (self), gtk_widget_get_opacity (GTK_WIDGET (self)), 0.0,
FADE_DURATION_MS, target);
g_signal_connect (self->animation, "notify::value",
G_CALLBACK (on_fade_animation_value), self);
}
else
{
params = adw_spring_params_new (SPRING_DAMPING_RATIO, SPRING_MASS, SPRING_STIFFNESS);
self->animation = adw_spring_animation_new (
GTK_WIDGET (self), self->animation_progress, 0.0, params, target);
adw_spring_animation_set_clamp (ADW_SPRING_ANIMATION (self->animation), TRUE);
adw_spring_animation_set_epsilon (ADW_SPRING_ANIMATION (self->animation), 0.0001);
adw_spring_animation_set_initial_velocity (ADW_SPRING_ANIMATION (self->animation), -1.2);
g_signal_connect (self->animation, "notify::value", G_CALLBACK (on_animation_value), self);
}
g_signal_connect (self->animation, "done", G_CALLBACK (on_close_animation_done), self);
adw_animation_play (self->animation);
}
else
{
if (self->source_widget != NULL)
gtk_widget_set_opacity (self->source_widget, 1.0);
parent = gtk_widget_get_parent (GTK_WIDGET (self));
if (parent != NULL)
gtk_overlay_remove_overlay (GTK_OVERLAY (parent), GTK_WIDGET (self));
}
}
static void
zoom_in_clicked (GtkWidget *widget,
const char *action_name,
GVariant *parameter)
{
BzScreenshotPage *self = BZ_SCREENSHOT_PAGE (widget);
BzZoom *zoom = NULL;
zoom = get_current_zoom (self, NULL);
if (zoom != NULL)
bz_zoom_zoom_in (zoom);
}
static void
zoom_out_clicked (GtkWidget *widget,
const char *action_name,
GVariant *parameter)
{
BzScreenshotPage *self = BZ_SCREENSHOT_PAGE (widget);
BzZoom *zoom = NULL;
zoom = get_current_zoom (self, NULL);
if (zoom != NULL)
bz_zoom_zoom_out (zoom);
}
static void
reset_zoom_clicked (GtkWidget *widget,
const char *action_name,
GVariant *parameter)
{
BzScreenshotPage *self = BZ_SCREENSHOT_PAGE (widget);
BzZoom *zoom = NULL;
zoom = get_current_zoom (self, NULL);
if (zoom != NULL)
bz_zoom_reset (zoom);
}
static void
previous_clicked (GtkWidget *widget,
const char *action_name,
GVariant *parameter)
{
BzScreenshotPage *self = BZ_SCREENSHOT_PAGE (widget);
guint n_pages = 0;
GtkWidget *page = NULL;
n_pages = adw_carousel_get_n_pages (self->carousel);
if (n_pages == 0)
return;
if (self->current_index > 0)
page = adw_carousel_get_nth_page (self->carousel, self->current_index - 1);
else
page = adw_carousel_get_nth_page (self->carousel, n_pages - 1);
if (page != NULL)
adw_carousel_scroll_to (self->carousel, page, TRUE);
}
static void
next_clicked (GtkWidget *widget,
const char *action_name,
GVariant *parameter)
{
BzScreenshotPage *self = BZ_SCREENSHOT_PAGE (widget);
guint n_pages = 0;
GtkWidget *page = NULL;
n_pages = adw_carousel_get_n_pages (self->carousel);
if (n_pages == 0)
return;
if (self->current_index < n_pages - 1)
page = adw_carousel_get_nth_page (self->carousel, self->current_index + 1);
else
page = adw_carousel_get_nth_page (self->carousel, 0);
if (page != NULL)
adw_carousel_scroll_to (self->carousel, page, TRUE);
}
static void
on_carousel_position_changed (AdwCarousel *carousel,
GParamSpec *pspec,
BzScreenshotPage *self)
{
GtkScrolledWindow *old_scrolled_window = NULL;
BzZoom *old_zoom = NULL;
BzZoom *new_zoom = NULL;
guint new_index = 0;
guint n_pages = 0;
new_index = (guint) round (adw_carousel_get_position (carousel));
n_pages = adw_carousel_get_n_pages (carousel);
if (new_index == self->current_index || new_index >= n_pages)
return;
old_zoom = get_current_zoom (self, &old_scrolled_window);
if (old_zoom != NULL)
{
g_signal_handlers_disconnect_by_func (old_zoom, on_zoom_level_changed, self);
bz_zoom_reset (old_zoom);
gtk_scrolled_window_set_policy (old_scrolled_window, GTK_POLICY_NEVER, GTK_POLICY_NEVER);
}
self->current_index = new_index;
new_zoom = get_current_zoom (self, NULL);
g_assert (new_zoom != NULL);
g_signal_connect (new_zoom, "notify::zoom-level",
G_CALLBACK (on_zoom_level_changed), self);
update_is_zoomed (self);
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CURRENT_INDEX]);
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CURRENT_CAPTION]);
}
static void
copy_clicked (GtkWidget *widget,
const char *action_name,
GVariant *parameter)
{
BzScreenshotPage *self = BZ_SCREENSHOT_PAGE (widget);
g_autoptr (BzAsyncTexture) async_texture = NULL;
g_autoptr (GdkTexture) texture = NULL;
GdkClipboard *clipboard = NULL;
AdwToast *toast = NULL;
guint n_items = 0;
guint actual_index = 0;
if (self->screenshots == NULL)
return;
n_items = g_list_model_get_n_items (self->screenshots);
if (n_items == 0)
return;
actual_index = (self->initial_index + self->current_index) % n_items;
async_texture = g_list_model_get_item (self->screenshots, actual_index);
if (async_texture == NULL)
return;
texture = bz_async_texture_dup_texture (async_texture);
if (texture == NULL)
return;
clipboard = gdk_display_get_clipboard (gdk_display_get_default ());
gdk_clipboard_set_texture (clipboard, texture);
toast = adw_toast_new (_ ("Copied!"));
adw_toast_set_timeout (toast, 1);
adw_toast_overlay_add_toast (self->toast_overlay, toast);
}
static void
on_zoom_level_changed (BzZoom *zoom,
GParamSpec *pspec,
BzScreenshotPage *self)
{
update_is_zoomed (self);
}
static gboolean
has_multiple_screenshots (GObject *object,
GListModel *screenshots,
gpointer user_data)
{
if (screenshots == NULL)
return FALSE;
return g_list_model_get_n_items (screenshots) > 1;
}
static gboolean
invert_boolean (gpointer object,
gboolean value)
{
return !value;
}
static gboolean
is_valid_string (gpointer object,
const char *value)
{
return value != NULL && *value != '\0';
}
static void
bz_screenshot_page_dispose (GObject *object)
{
BzScreenshotPage *self = BZ_SCREENSHOT_PAGE (object);
g_clear_object (&self->screenshots);
g_clear_object (&self->captions);
g_clear_object (&self->source_texture);
g_clear_object (&self->animation);
G_OBJECT_CLASS (bz_screenshot_page_parent_class)->dispose (object);
}
static void
bz_screenshot_page_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
BzScreenshotPage *self = BZ_SCREENSHOT_PAGE (object);
switch (prop_id)
{
case PROP_SCREENSHOTS:
g_value_set_object (value, self->screenshots);
break;
case PROP_CURRENT_INDEX:
g_value_set_uint (value, self->current_index);
break;
case PROP_CURRENT_CAPTION:
{
const char *caption = NULL;
caption = bz_screenshot_page_get_current_caption (self);
g_value_set_string (value, caption);
}
break;
case PROP_IS_ZOOMED:
g_value_set_boolean (value, self->is_zoomed);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
bz_screenshot_page_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
BzScreenshotPage *self = BZ_SCREENSHOT_PAGE (object);
switch (prop_id)
{
case PROP_SCREENSHOTS:
g_set_object (&self->screenshots, g_value_get_object (value));
break;
case PROP_CURRENT_INDEX:
self->initial_index = g_value_get_uint (value);
break;
case PROP_CURRENT_CAPTION:
case PROP_IS_ZOOMED:
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
bz_screenshot_page_constructed (GObject *object)
{
BzScreenshotPage *self = BZ_SCREENSHOT_PAGE (object);
BzZoom *first_zoom = NULL;
G_OBJECT_CLASS (bz_screenshot_page_parent_class)->constructed (object);
populate_carousel (self);
self->current_index = 0;
first_zoom = get_current_zoom (self, NULL);
if (first_zoom != NULL)
g_signal_connect (first_zoom, "notify::zoom-level",
G_CALLBACK (on_zoom_level_changed), self);
update_is_zoomed (self);
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CURRENT_CAPTION]);
}
static void
bz_screenshot_page_class_init (BzScreenshotPageClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->dispose = bz_screenshot_page_dispose;
object_class->constructed = bz_screenshot_page_constructed;
object_class->get_property = bz_screenshot_page_get_property;
object_class->set_property = bz_screenshot_page_set_property;
widget_class->map = bz_screenshot_page_map;
widget_class->unmap = bz_screenshot_page_unmap;
widget_class->snapshot = bz_screenshot_page_snapshot;
props[PROP_SCREENSHOTS] =
g_param_spec_object (
"screenshots",
NULL, NULL,
G_TYPE_LIST_MODEL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
props[PROP_CURRENT_INDEX] =
g_param_spec_uint (
"current-index",
NULL, NULL,
0, G_MAXUINT, 0,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
props[PROP_CURRENT_CAPTION] =
g_param_spec_string (
"current-caption",
NULL, NULL,
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
props[PROP_IS_ZOOMED] =
g_param_spec_boolean (
"is-zoomed",
NULL, NULL,
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (object_class, LAST_PROP, props);
g_type_ensure (BZ_TYPE_ZOOM);
gtk_widget_class_set_template_from_resource (widget_class, "/io/github/kolunmi/Bazaar/bz-screenshot-page.ui");
gtk_widget_class_bind_template_child (widget_class, BzScreenshotPage, carousel);
gtk_widget_class_bind_template_child (widget_class, BzScreenshotPage, toast_overlay);
gtk_widget_class_bind_template_callback (widget_class, back_clicked);
gtk_widget_class_bind_template_callback (widget_class, on_carousel_position_changed);
gtk_widget_class_bind_template_callback (widget_class, has_multiple_screenshots);
gtk_widget_class_bind_template_callback (widget_class, invert_boolean);
gtk_widget_class_bind_template_callback (widget_class, is_valid_string);
gtk_widget_class_install_action (widget_class, "screenshot.previous", NULL, previous_clicked);
gtk_widget_class_install_action (widget_class, "screenshot.next", NULL, next_clicked);
gtk_widget_class_install_action (widget_class, "screenshot.zoom-in", NULL, zoom_in_clicked);
gtk_widget_class_install_action (widget_class, "screenshot.zoom-out", NULL, zoom_out_clicked);
gtk_widget_class_install_action (widget_class, "screenshot.zoom-reset", NULL, reset_zoom_clicked);
gtk_widget_class_install_action (widget_class, "screenshot.copy", NULL, copy_clicked);
}
static void
bz_screenshot_page_init (BzScreenshotPage *self)
{
GtkEventController *scroll = NULL;
GtkGesture *swipe = NULL;
GtkGesture *click = NULL;
gtk_widget_init_template (GTK_WIDGET (self));
swipe = gtk_gesture_swipe_new ();
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (swipe), GTK_PHASE_CAPTURE);
g_signal_connect_swapped (swipe, "swipe", G_CALLBACK (on_swipe), self);
gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (swipe), TRUE);
gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (swipe));
click = gtk_gesture_click_new ();
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (click), 0);
gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (click), GTK_PHASE_CAPTURE);
g_signal_connect (click, "pressed", G_CALLBACK (on_button_pressed), self);
gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (click));
scroll = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_VERTICAL);
gtk_event_controller_set_propagation_phase (scroll, GTK_PHASE_BUBBLE);
g_signal_connect_swapped (scroll, "scroll", G_CALLBACK (on_scroll), self);
gtk_widget_add_controller (GTK_WIDGET (self), scroll);
}
const char *
bz_screenshot_page_get_current_caption (BzScreenshotPage *self)
{
g_autoptr (GtkStringObject) caption_obj = NULL;
guint n_items = 0;
guint actual_index = 0;
g_return_val_if_fail (BZ_IS_SCREENSHOT_PAGE (self), NULL);
if (self->captions == NULL)
return "";
n_items = g_list_model_get_n_items (self->captions);
if (n_items == 0)
return "";
actual_index = (self->initial_index + self->current_index) % n_items;
caption_obj = g_list_model_get_item (self->captions, actual_index);
if (caption_obj == NULL)
return "";
return gtk_string_object_get_string (caption_obj);
}
void
bz_screenshot_page_set_captions (BzScreenshotPage *self,
GListModel *captions)
{
g_return_if_fail (BZ_IS_SCREENSHOT_PAGE (self));
g_set_object (&self->captions, captions);
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CURRENT_CAPTION]);
}
void
bz_screenshot_page_close (BzScreenshotPage *self)
{
g_return_if_fail (BZ_IS_SCREENSHOT_PAGE (self));
back_clicked (self);
}
gboolean
bz_screenshot_page_is_closing (BzScreenshotPage *self)
{
return self->closing;
}
AdwBin *
bz_screenshot_page_new (GListModel *screenshots,
GListModel *captions,
guint initial_index,
GtkWidget *source_widget)
{
BzScreenshotPage *page = NULL;
page = g_object_new (
BZ_TYPE_SCREENSHOT_PAGE,
"screenshots", screenshots,
"current-index", initial_index,
NULL);
page->source_widget = source_widget;
if (captions != NULL)
bz_screenshot_page_set_captions (page, captions);
return ADW_BIN (page);
}
static GdkTexture *
render_widget_to_texture (GtkWidget *widget)
{
g_autoptr (GtkWidgetPaintable) paintable = NULL;
g_autoptr (GtkSnapshot) snapshot = NULL;
g_autoptr (GskRenderNode) node = NULL;
GtkNative *native = NULL;
GdkTexture *texture = NULL;
paintable = GTK_WIDGET_PAINTABLE (gtk_widget_paintable_new (widget));
snapshot = gtk_snapshot_new ();
gdk_paintable_snapshot (GDK_PAINTABLE (paintable), snapshot,
gdk_paintable_get_intrinsic_width (GDK_PAINTABLE (paintable)),
gdk_paintable_get_intrinsic_height (GDK_PAINTABLE (paintable)));
node = gtk_snapshot_to_node (snapshot);
native = gtk_widget_get_native (widget);
if (node != NULL && native != NULL)
texture = gsk_renderer_render_texture (
gtk_native_get_renderer (native), node, NULL);
return texture;
}
static void
on_animation_value (AdwAnimation *animation,
GParamSpec *pspec,
BzScreenshotPage *self)
{
self->animation_progress = adw_animation_get_value (animation);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
static void
on_fade_animation_value (AdwAnimation *animation,
GParamSpec *pspec,
BzScreenshotPage *self)
{
gtk_widget_set_opacity (GTK_WIDGET (self), adw_animation_get_value (animation));
}
static void
on_close_animation_done (AdwAnimation *animation,
BzScreenshotPage *self)
{
GtkWidget *parent = NULL;
if (self->source_widget != NULL)
gtk_widget_set_opacity (self->source_widget, 1.0);
parent = gtk_widget_get_parent (GTK_WIDGET (self));
if (parent != NULL)
gtk_overlay_remove_overlay (GTK_OVERLAY (parent), GTK_WIDGET (self));
}
static void
populate_carousel (BzScreenshotPage *self)
{
guint n_items = 0;
guint i = 0;
if (self->screenshots == NULL)
return;
n_items = g_list_model_get_n_items (self->screenshots);
if (n_items == 0)
return;
for (guint offset = 0; offset < n_items; offset++)
{
g_autoptr (BzAsyncTexture) async_texture = NULL;
GtkWidget *scrolled_window = NULL;
GtkWidget *zoom_widget = NULL;
GtkWidget *screenshot = NULL;
i = (self->initial_index + offset) % n_items;
async_texture = g_list_model_get_item (self->screenshots, i);
if (async_texture == NULL)
continue;
screenshot = bz_screenshot_new ();
bz_screenshot_set_paintable (BZ_SCREENSHOT (screenshot), GDK_PAINTABLE (async_texture));
bz_screenshot_set_rounded_corners (BZ_SCREENSHOT (screenshot), FALSE);
gtk_widget_set_margin_top (screenshot, 25);
gtk_widget_set_margin_bottom (screenshot, 25);
gtk_widget_set_margin_start (screenshot, 25);
gtk_widget_set_margin_end (screenshot, 25);
zoom_widget = bz_zoom_new ();
bz_zoom_set_child (BZ_ZOOM (zoom_widget), screenshot);
scrolled_window = gtk_scrolled_window_new ();
gtk_widget_set_hexpand (scrolled_window, TRUE);
gtk_widget_set_vexpand (scrolled_window, TRUE);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_NEVER, GTK_POLICY_NEVER);
gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (scrolled_window), zoom_widget);
adw_carousel_append (self->carousel, scrolled_window);
}
}
static void
update_is_zoomed (BzScreenshotPage *self)
{
GtkWidget *screenshot = NULL;
BzZoom *zoom = NULL;
GtkScrolledWindow *scrolled_window = NULL;
double zoom_level = 0.0;
gboolean was_zoomed = FALSE;
was_zoomed = self->is_zoomed;
zoom = get_current_zoom (self, &scrolled_window);
if (zoom != NULL)
{
g_object_get (zoom, "zoom-level", &zoom_level, NULL);
screenshot = bz_zoom_get_child (zoom);
if (screenshot != NULL)
bz_screenshot_set_filter (
BZ_SCREENSHOT (screenshot),
zoom_level <= 4.5
? GSK_SCALING_FILTER_TRILINEAR
: GSK_SCALING_FILTER_NEAREST);
}
self->is_zoomed = zoom != NULL && bz_zoom_is_transformed (zoom);
if (was_zoomed != self->is_zoomed)
{
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_IS_ZOOMED]);
if (self->is_zoomed)
gtk_scrolled_window_set_policy (scrolled_window, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
else
gtk_scrolled_window_set_policy (scrolled_window, GTK_POLICY_NEVER, GTK_POLICY_NEVER);
}
}
static BzZoom *
get_current_zoom (BzScreenshotPage *self,
GtkScrolledWindow **scrolled_window)
{
guint n_pages = 0;
GtkWidget *page = NULL;
GtkWidget *zoom = NULL;
n_pages = adw_carousel_get_n_pages (self->carousel);
if (self->current_index >= n_pages)
return NULL;
page = adw_carousel_get_nth_page (self->carousel, self->current_index);
if (page == NULL)
return NULL;
zoom = gtk_scrolled_window_get_child (GTK_SCROLLED_WINDOW (page));
if (zoom != NULL)
{
if (scrolled_window != NULL)
*scrolled_window = (GtkScrolledWindow *) page;
return BZ_ZOOM (zoom);
}
else
return NULL;
}
static void
on_button_pressed (GtkGestureClick *gesture,
int n_press,
double x,
double y,
BzScreenshotPage *self)
{
if (gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)) == MOUSE_BACK_BUTTON)
back_clicked (self);
}
static void
on_swipe (BzScreenshotPage *self,
gdouble vel_x,
gdouble vel_y)
{
if (vel_y < -500.0 || vel_y > 500.0)
back_clicked (self);
}
static gboolean
on_scroll (BzScreenshotPage *self,
gdouble dx,
gdouble dy,
GtkEventControllerScroll *controller)
{
GdkEvent *event = NULL;
GdkDevice *device = NULL;
GdkInputSource source = 0;
event = gtk_event_controller_get_current_event (GTK_EVENT_CONTROLLER (controller));
device = gdk_event_get_device (event);
source = gdk_device_get_source (device);
if (source == GDK_SOURCE_TOUCHPAD && (dy < -2.0 || dy > 2.0))
back_clicked (self);
return GDK_EVENT_PROPAGATE;
}