Animate animations
This commit is contained in:
parent
33f24fa184
commit
cfe3dc55c6
@ -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;
|
||||
|
149
fastiv-view.c
149
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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user