aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2021-11-28 20:05:36 +0100
committerPřemysl Eric Janouch <p@janouch.name>2021-11-28 23:41:09 +0100
commitcfe3dc55c6e1133b0fbe342e63965aec2437a7c3 (patch)
tree0c22c01385b045315151882ec41302f378c9cbec
parent33f24fa184257572cd7924e2e7d0c256af102644 (diff)
downloadfiv-cfe3dc55c6e1133b0fbe342e63965aec2437a7c3.tar.gz
fiv-cfe3dc55c6e1133b0fbe342e63965aec2437a7c3.tar.xz
fiv-cfe3dc55c6e1133b0fbe342e63965aec2437a7c3.zip
Animate animations
-rw-r--r--fastiv-io.h2
-rw-r--r--fastiv-view.c149
2 files changed, 135 insertions, 16 deletions
diff --git a/fastiv-io.h b/fastiv-io.h
index db94a46..c8a256b 100644
--- a/fastiv-io.h
+++ b/fastiv-io.h
@@ -27,7 +27,7 @@ char **fastiv_io_all_supported_media_types(void);
// Userdata are typically attached to all Cairo surfaces in an animation.
-/// GBytes with plain Exif data.
+/// GBytes with plain Exif/TIFF data.
extern cairo_user_data_key_t fastiv_io_key_exif;
/// FastivIoOrientation, as a uintptr_t.
extern cairo_user_data_key_t fastiv_io_key_orientation;
diff --git a/fastiv-view.c b/fastiv-view.c
index c8bce86..3b845e4 100644
--- a/fastiv-view.c
+++ b/fastiv-view.c
@@ -34,10 +34,14 @@ struct _FastivView {
cairo_surface_t *image; ///< The loaded image (sequence)
cairo_surface_t *page; ///< Current page within image, weak
cairo_surface_t *frame; ///< Current frame within page, weak
- FastivIoOrientation orientation; ///< Current orientation
- bool filter;
- bool scale_to_fit;
- double scale;
+ FastivIoOrientation orientation; ///< Current page orientation
+ bool filter; ///< Smooth scaling toggle
+ bool scale_to_fit; ///< Image no larger than the allocation
+ double scale; ///< Scaling factor
+
+ int remaining_loops; ///< Greater than zero if limited
+ gint64 frame_time; ///< Current frame's start, µs precision
+ gulong frame_update_connection; ///< GdkFrameClock::update
};
G_DEFINE_TYPE(FastivView, fastiv_view, GTK_TYPE_WIDGET)
@@ -426,6 +430,123 @@ fastiv_view_scroll_event(GtkWidget *widget, GdkEventScroll *event)
}
}
+static void
+stop_animating(FastivView *self)
+{
+ GdkFrameClock *clock = gtk_widget_get_frame_clock(GTK_WIDGET(self));
+ if (!clock || !self->frame_update_connection)
+ return;
+
+ g_signal_handler_disconnect(clock, self->frame_update_connection);
+ gdk_frame_clock_end_updating(clock);
+
+ self->frame_time = 0;
+ self->frame_update_connection = 0;
+ self->remaining_loops = 0;
+}
+
+static gboolean
+advance_frame(FastivView *self)
+{
+ cairo_surface_t *next =
+ cairo_surface_get_user_data(self->frame, &fastiv_io_key_frame_next);
+ if (next) {
+ self->frame = next;
+ } else {
+ if (self->remaining_loops && !--self->remaining_loops)
+ return FALSE;
+
+ self->frame = self->page;
+ }
+ return TRUE;
+}
+
+static gboolean
+advance_animation(FastivView *self, GdkFrameClock *clock)
+{
+ gint64 now = gdk_frame_clock_get_frame_time(clock);
+ while (true) {
+ // TODO(p): See if infinite frames can actually happen, and how.
+ intptr_t duration = (intptr_t) cairo_surface_get_user_data(
+ self->frame, &fastiv_io_key_frame_duration);
+ if (duration < 0)
+ return FALSE;
+
+ // Do not busy loop. GIF timings are given in hundredths of a second.
+ if (duration == 0)
+ duration = gdk_frame_timings_get_refresh_interval(
+ gdk_frame_clock_get_current_timings(clock)) / 1000;
+ if (duration == 0)
+ duration = 1;
+
+ gint64 then = self->frame_time + duration * 1000;
+ if (then > now)
+ return TRUE;
+ if (!advance_frame(self))
+ return FALSE;
+
+ self->frame_time = then;
+ gtk_widget_queue_draw(GTK_WIDGET(self));
+ }
+}
+
+static void
+on_frame_clock_update(GdkFrameClock *clock, gpointer user_data)
+{
+ FastivView *self = FASTIV_VIEW(user_data);
+ if (!advance_animation(self, clock))
+ stop_animating(self);
+}
+
+static void
+start_animating(FastivView *self)
+{
+ stop_animating(self);
+
+ GdkFrameClock *clock = gtk_widget_get_frame_clock(GTK_WIDGET(self));
+ if (!clock ||
+ !cairo_surface_get_user_data(self->page, &fastiv_io_key_frame_next))
+ return;
+
+ self->frame_time = gdk_frame_clock_get_frame_time(clock);
+ self->frame_update_connection = g_signal_connect(
+ clock, "update", G_CALLBACK(on_frame_clock_update), self);
+ self->remaining_loops = (uintptr_t) cairo_surface_get_user_data(
+ self->page, &fastiv_io_key_loops);
+
+ gdk_frame_clock_begin_updating(clock);
+}
+
+static void
+switch_page(FastivView *self, cairo_surface_t *page)
+{
+ GtkWidget *widget = GTK_WIDGET(self);
+ self->frame = self->page = page;
+ if ((self->orientation = (uintptr_t) cairo_surface_get_user_data(
+ self->page, &fastiv_io_key_orientation)) ==
+ FastivIoOrientationUnknown)
+ self->orientation = FastivIoOrientation0;
+
+ start_animating(self);
+ gtk_widget_queue_resize(widget);
+}
+
+static void
+fastiv_view_map(GtkWidget *widget)
+{
+ GTK_WIDGET_CLASS(fastiv_view_parent_class)->map(widget);
+
+ // Loading before mapping will fail to obtain a GdkFrameClock.
+ start_animating(FASTIV_VIEW(widget));
+}
+
+void
+fastiv_view_unmap(GtkWidget *widget)
+{
+ stop_animating(FASTIV_VIEW(widget));
+ GTK_WIDGET_CLASS(fastiv_view_parent_class)->unmap(widget);
+}
+
static gboolean
fastiv_view_key_press_event(GtkWidget *widget, GdkEventKey *event)
{
@@ -467,26 +588,26 @@ fastiv_view_key_press_event(GtkWidget *widget, GdkEventKey *event)
cairo_surface_t *page = cairo_surface_get_user_data(
self->page, &fastiv_io_key_page_previous);
if (page)
- self->frame = self->page = page;
- gtk_widget_queue_resize(widget);
+ switch_page(self, page);
return TRUE;
}
case GDK_KEY_bracketright: {
cairo_surface_t *page = cairo_surface_get_user_data(
self->page, &fastiv_io_key_page_next);
if (page)
- self->frame = self->page = page;
- gtk_widget_queue_resize(widget);
+ switch_page(self, page);
return TRUE;
}
case GDK_KEY_braceleft:
+ stop_animating(self);
if (!(self->frame = cairo_surface_get_user_data(
self->frame, &fastiv_io_key_frame_previous)))
self->frame = self->page;
gtk_widget_queue_draw(widget);
return TRUE;
case GDK_KEY_braceright:
+ stop_animating(self);
if (!(self->frame = cairo_surface_get_user_data(
self->frame, &fastiv_io_key_frame_next)))
self->frame = self->page;
@@ -516,6 +637,8 @@ fastiv_view_class_init(FastivViewClass *klass)
widget_class->get_preferred_height = fastiv_view_get_preferred_height;
widget_class->get_preferred_width = fastiv_view_get_preferred_width;
widget_class->size_allocate = fastiv_view_size_allocate;
+ widget_class->map = fastiv_view_map;
+ widget_class->unmap = fastiv_view_unmap;
widget_class->realize = fastiv_view_realize;
widget_class->draw = fastiv_view_draw;
widget_class->button_press_event = fastiv_view_button_press_event;
@@ -548,13 +671,9 @@ fastiv_view_open(FastivView *self, const gchar *path, GError **error)
if (self->image)
cairo_surface_destroy(self->image);
- self->frame = self->page = self->image = surface;
+ self->frame = self->page = NULL;
+ self->image = surface;
+ switch_page(self, self->image);
set_scale_to_fit(self, true);
-
- // TODO(p): This is actually per-page.
- if ((self->orientation = (uintptr_t) cairo_surface_get_user_data(
- self->image, &fastiv_io_key_orientation)) ==
- FastivIoOrientationUnknown)
- self->orientation = FastivIoOrientation0;
return TRUE;
}