diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml index 06b08b6f9c..81519f1c37 100644 --- a/demos/gtk-demo/demo.gresource.xml +++ b/demos/gtk-demo/demo.gresource.xml @@ -183,6 +183,7 @@ overlay2.c pagesetup.c paintable.c + paintable_animated.c panes.c pickers.c pixbufs.c diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build index ac9f5e0000..5d3a318bd1 100644 --- a/demos/gtk-demo/meson.build +++ b/demos/gtk-demo/meson.build @@ -46,6 +46,7 @@ demos = files([ 'overlay.c', 'overlay2.c', 'paintable.c', + 'paintable_animated.c', 'panes.c', 'pickers.c', 'pixbufs.c', diff --git a/demos/gtk-demo/paintable.c b/demos/gtk-demo/paintable.c index 8dd013f451..da7e22d208 100644 --- a/demos/gtk-demo/paintable.c +++ b/demos/gtk-demo/paintable.c @@ -13,6 +13,8 @@ #include +#include "paintable.h" + static GtkWidget *window = NULL; /* First, add the boilerplate for the object itself. @@ -21,14 +23,16 @@ static GtkWidget *window = NULL; #define GTK_TYPE_NUCLEAR_ICON (gtk_nuclear_icon_get_type ()) G_DECLARE_FINAL_TYPE (GtkNuclearIcon, gtk_nuclear_icon, GTK, NUCLEAR_ICON, GObject) -GdkPaintable *gtk_nuclear_icon_new (void); - -/* Declare the struct. We have no custom data, so it's very - * bare. - */ +/* Declare the struct. */ struct _GtkNuclearIcon { GObject parent_instance; + + /* We store this rotation value here. + * We are not doing with it here, but it will come in + * very useful in the followup demos. + */ + double rotation; }; struct _GtkNuclearIconClass @@ -36,21 +40,20 @@ struct _GtkNuclearIconClass GObjectClass parent_class; }; -/* Here, we implement the functionality required by the GdkPaintable interface */ -static void -gtk_nuclear_icon_snapshot (GdkPaintable *paintable, - GdkSnapshot *snapshot, - double width, - double height) +/* This is the function that draws the actual icon. + * We make it a custom function and define it in the paintable.h header + * so that it can be called from all the other demos, too. + */ +void +gtk_nuclear_snapshot (GtkSnapshot *snapshot, + double width, + double height, + double rotation) { #define RADIUS 0.3 cairo_t *cr; double size; - /* The snapshot function is the only function we need to implement. - * It does the actual drawing of the paintable. - */ - gtk_snapshot_append_color (snapshot, &(GdkRGBA) { 0.9, 0.75, 0.15, 1.0 }, &GRAPHENE_RECT_INIT (0, 0, width, height), @@ -64,6 +67,7 @@ gtk_nuclear_icon_snapshot (GdkPaintable *paintable, "Radioactive Icon"); cairo_translate (cr, width / 2.0, height / 2.0); cairo_scale (cr, size, size); + cairo_rotate (cr, rotation); cairo_arc (cr, 0, 0, 0.1, - G_PI, G_PI); cairo_fill (cr); @@ -76,6 +80,24 @@ gtk_nuclear_icon_snapshot (GdkPaintable *paintable, cairo_destroy (cr); } +/* Here, we implement the functionality required by the GdkPaintable interface */ +static void +gtk_nuclear_icon_snapshot (GdkPaintable *paintable, + GdkSnapshot *snapshot, + double width, + double height) +{ + GtkNuclearIcon *nuclear = GTK_NUCLEAR_ICON (paintable); + + /* The snapshot function is the only function we need to implement. + * It does the actual drawing of the paintable. + */ + + gtk_nuclear_snapshot (snapshot, + width, height, + nuclear->rotation); +} + static GdkPaintableFlags gtk_nuclear_icon_get_flags (GdkPaintable *paintable) { @@ -113,11 +135,19 @@ gtk_nuclear_icon_init (GtkNuclearIcon *nuclear) { } -/* And finally, we add the simple constructor we declared in the header. */ +/* And finally, we add a simple constructor. + * It is declared in the header so that the other examples + * can use it. + */ GdkPaintable * -gtk_nuclear_icon_new (void) +gtk_nuclear_icon_new (double rotation) { - return g_object_new (GTK_TYPE_NUCLEAR_ICON, NULL); + GtkNuclearIcon *nuclear; + + nuclear = g_object_new (GTK_TYPE_NUCLEAR_ICON, NULL); + nuclear->rotation = rotation; + + return GDK_PAINTABLE (nuclear); } GtkWidget * @@ -134,7 +164,7 @@ do_paintable (GtkWidget *do_widget) gtk_window_set_title (GTK_WINDOW (window), "Nuclear Icon"); gtk_window_set_default_size (GTK_WINDOW (window), 300, 200); - nuclear = gtk_nuclear_icon_new (); + nuclear = gtk_nuclear_icon_new (0.0); image = gtk_image_new_from_paintable (nuclear); gtk_container_add (GTK_CONTAINER (window), image); g_object_unref (nuclear); diff --git a/demos/gtk-demo/paintable.h b/demos/gtk-demo/paintable.h new file mode 100644 index 0000000000..0f561b2713 --- /dev/null +++ b/demos/gtk-demo/paintable.h @@ -0,0 +1,14 @@ +#ifndef __PAINTABLE_H__ +#define __PAINTABLE_H__ + +#include + +void gtk_nuclear_snapshot (GtkSnapshot *snapshot, + double width, + double height, + double rotation); + +GdkPaintable * gtk_nuclear_icon_new (double rotation); +GdkPaintable * gtk_nuclear_animation_new (void); + +#endif /* __PAINTABLE_H__ */ diff --git a/demos/gtk-demo/paintable_animated.c b/demos/gtk-demo/paintable_animated.c new file mode 100644 index 0000000000..b606764ca5 --- /dev/null +++ b/demos/gtk-demo/paintable_animated.c @@ -0,0 +1,209 @@ +/* Paintable/An animated paintable + * + * GdkPaintable also allows paintables to change. + * + * This demo code gives an example of how this could work. It builds + * on the previous simple example. + * + * Paintables can also change their size, this works similarly, but + * we will not demonstrate this here as our icon does not have any size. + */ + +#include + +#include "paintable.h" + +static GtkWidget *window = NULL; + +/* First, add the boilerplate for the object itself. + * This part would normally go in the header. + */ +#define GTK_TYPE_NUCLEAR_ANIMATION (gtk_nuclear_animation_get_type ()) +G_DECLARE_FINAL_TYPE (GtkNuclearAnimation, gtk_nuclear_animation, GTK, NUCLEAR_ANIMATION, GObject) + +/* Do a full rotation in 5 seconds. + * We will register the timeout for doing a single step to + * be executed every 10ms, which means after 1000 steps + * 10s will have elapsed. + */ +#define MAX_PROGRESS 500 + +/* Declare the struct. */ +struct _GtkNuclearAnimation +{ + GObject parent_instance; + + /* This variable stores the progress of our animation. + * We just count upwards until we hit MAX_PROGRESS and + * then start from scratch. + */ + int progress; + + /* This variable holds the ID of the timer that updates + * our progress variable. + * We need to keep track of it so that we can remove it + * again. + */ + guint source_id; +}; + +struct _GtkNuclearAnimationClass +{ + GObjectClass parent_class; +}; + +/* Again, we implement the functionality required by the GdkPaintable interface */ +static void +gtk_nuclear_animation_snapshot (GdkPaintable *paintable, + GdkSnapshot *snapshot, + double width, + double height) +{ + GtkNuclearAnimation *nuclear = GTK_NUCLEAR_ANIMATION (paintable); + + /* We call the function from the previous example here. */ + gtk_nuclear_snapshot (snapshot, + width, height, + 2 * G_PI * nuclear->progress / MAX_PROGRESS); +} + +static GdkPaintable * +gtk_nuclear_animation_get_current_image (GdkPaintable *paintable) +{ + GtkNuclearAnimation *nuclear = GTK_NUCLEAR_ANIMATION (paintable); + + /* For non-static paintables, this function needs to be implemented. + * It must return a static paintable with the same contents + * as this one currently has. + * + * Luckily we added the rotation property to the nuclear icon + * object previously, so we can just return an instance of that one. + */ + return gtk_nuclear_icon_new (2 * G_PI * nuclear->progress / MAX_PROGRESS); +} + +static GdkPaintableFlags +gtk_nuclear_animation_get_flags (GdkPaintable *paintable) +{ + /* This time, we cannot set the static contents flag because our animation + * changes the contents. + * However, our size still doesn't change, so report that flag. + */ + return GDK_PAINTABLE_STATIC_SIZE; +} + +static void +gtk_nuclear_animation_paintable_init (GdkPaintableInterface *iface) +{ + iface->snapshot = gtk_nuclear_animation_snapshot; + iface->get_current_image = gtk_nuclear_animation_get_current_image; + iface->get_flags = gtk_nuclear_animation_get_flags; +} + +/* When defining the GType, we need to implement the GdkPaintable interface */ +G_DEFINE_TYPE_WITH_CODE (GtkNuclearAnimation, gtk_nuclear_animation, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE, + gtk_nuclear_animation_paintable_init)) + +/* This time, we need to implement the finalize function, + */ +static void +gtk_nuclear_animation_finalize (GObject *object) +{ + GtkNuclearAnimation *nuclear = GTK_NUCLEAR_ANIMATION (object); + + /* Remove the timeout we registered when constructing + * the object. + */ + g_source_remove (nuclear->source_id); + + /* Don't forget to chain up to the parent class' implementation + * of the finalize function. + */ + G_OBJECT_CLASS (gtk_nuclear_animation_parent_class)->finalize (object); +} + +/* In the class declaration, we need to add our finalize function. + */ +static void +gtk_nuclear_animation_class_init (GtkNuclearAnimationClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = gtk_nuclear_animation_finalize; +} + +static gboolean +gtk_nuclear_animation_step (gpointer data) +{ + GtkNuclearAnimation *nuclear = data; + + /* Add 1 to the progress and reset it when we've reached + * the maximum value. + * The animation will rotate by 360 degrees at MAX_PROGRESS + * so it will be identical to the original unrotated one. + */ + nuclear->progress = (nuclear->progress + 1) % MAX_PROGRESS; + + /* Now we need to tell all listeners that we've changed out contents + * so that they can redraw this paintable. + */ + gdk_paintable_invalidate_contents (GDK_PAINTABLE (nuclear)); + + /* We want this timeout function to be called repeatedly, + * so we return this value here. + * If this was a single-shot timeout, we could also + * return G_SOURCE_REMOVE here to get rid of it. + */ + return G_SOURCE_CONTINUE; +} + +static void +gtk_nuclear_animation_init (GtkNuclearAnimation *nuclear) +{ + /* Add a timer here that constantly updates our animations. + * We want to update it often enough to guarantee a smooth animation. + * + * Ideally, we'd attach to the frame clock, but because we do + * not have it available here, we just use a regular timeout + * that hopefully triggers often enough to be smooth. + */ + nuclear->source_id = g_timeout_add (10, + gtk_nuclear_animation_step, + nuclear); +} + +/* And finally, we add the simple constructor we declared in the header. */ +GdkPaintable * +gtk_nuclear_animation_new (void) +{ + return g_object_new (GTK_TYPE_NUCLEAR_ANIMATION, NULL); +} + +GtkWidget * +do_paintable_animated (GtkWidget *do_widget) +{ + GdkPaintable *nuclear; + GtkWidget *image; + + if (!window) + { + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_display (GTK_WINDOW (window), + gtk_widget_get_display (do_widget)); + gtk_window_set_title (GTK_WINDOW (window), "Nuclear Animation"); + gtk_window_set_default_size (GTK_WINDOW (window), 300, 200); + + nuclear = gtk_nuclear_animation_new (); + image = gtk_image_new_from_paintable (nuclear); + gtk_container_add (GTK_CONTAINER (window), image); + g_object_unref (nuclear); + } + + if (!gtk_widget_get_visible (window)) + gtk_widget_show (window); + else + gtk_widget_destroy (window); + + return window; +}