1/* OpenGL/Transitions and Effects
2 * #Keywords: OpenGL, shader, effect
3 *
4 * Create transitions between pages using a custom fragment shader.
5 *
6 * The example transitions here are taken from gl-transitions.com, and you
7 * can edit the shader code itself on the last page of the stack.
8 *
9 * The transitions work with arbitrary content. We use images, shaders
10 * GL areas and plain old widgets to demonstrate this.
11 *
12 * The demo also shows some over-the-top effects like wobbly widgets,
13 * and animated backgrounds.
14 */
15
16#include <math.h>
17#include <gtk/gtk.h>
18#include "gtkshaderstack.h"
19#include "gtkshaderbin.h"
20#include "gtkshadertoy.h"
21#include "gskshaderpaintable.h"
22
23static GtkWidget *demo_window = NULL;
24
25static void
26close_window (GtkWidget *widget)
27{
28 /* Reset the state */
29 demo_window = NULL;
30}
31
32static void
33text_changed (GtkTextBuffer *buffer,
34 GtkWidget *button)
35{
36 gtk_widget_show (widget: button);
37}
38
39static void
40apply_text (GtkWidget *button,
41 GtkTextBuffer *buffer)
42{
43 GtkWidget *stack;
44 GskGLShader *shader;
45 GtkTextIter start, end;
46 char *text;
47
48 stack = g_object_get_data (G_OBJECT (button), key: "the-stack");
49
50 gtk_text_buffer_get_bounds (buffer, start: &start, end: &end);
51 text = gtk_text_buffer_get_text (buffer, start: &start, end: &end, TRUE);
52
53 GBytes *bytes = g_bytes_new_take (data: text, size: strlen (s: text));
54 shader = gsk_gl_shader_new_from_bytes (sourcecode: bytes);
55
56 gtk_shader_stack_set_shader (self: GTK_SHADER_STACK (ptr: stack), shader);
57
58 g_object_unref (object: shader);
59 g_bytes_unref (bytes);
60
61 gtk_widget_hide (widget: button);
62}
63
64static void
65go_back (GtkWidget *button,
66 GtkWidget *stack)
67{
68 gtk_shader_stack_transition (self: GTK_SHADER_STACK (ptr: stack), FALSE);
69}
70
71static void
72go_forward (GtkWidget *button,
73 GtkWidget *stack)
74{
75 gtk_shader_stack_transition (self: GTK_SHADER_STACK (ptr: stack), TRUE);
76}
77
78static void
79clicked_cb (GtkGestureClick *gesture,
80 guint n_pressed,
81 double x,
82 double y,
83 gpointer data)
84{
85 gtk_gesture_set_state (GTK_GESTURE (gesture), state: GTK_EVENT_SEQUENCE_CLAIMED);
86}
87
88static GtkWidget *
89ripple_bin_new (void)
90{
91 GtkWidget *bin = gtk_shader_bin_new ();
92 static GskGLShader *shader = NULL;
93
94 if (shader == NULL)
95 shader = gsk_gl_shader_new_from_resource (resource_path: "/gltransition/ripple.glsl");
96
97 gtk_shader_bin_add_shader (self: GTK_SHADER_BIN (ptr: bin), shader, state: GTK_STATE_FLAG_PRELIGHT, state_mask: GTK_STATE_FLAG_PRELIGHT, extra_border: 20);
98
99 return bin;
100}
101
102static GtkWidget *
103new_shadertoy (const char *path)
104{
105 GBytes *shader;
106 GtkWidget *toy;
107
108 toy = gtk_shadertoy_new ();
109 shader = g_resources_lookup_data (path, lookup_flags: 0, NULL);
110 gtk_shadertoy_set_image_shader (GTK_SHADERTOY (toy),
111 shader: g_bytes_get_data (bytes: shader, NULL));
112 g_bytes_unref (bytes: shader);
113
114 return toy;
115}
116
117static gboolean
118update_paintable (GtkWidget *widget,
119 GdkFrameClock *frame_clock,
120 gpointer user_data)
121{
122 GskShaderPaintable *paintable;
123 gint64 frame_time;
124
125 paintable = GSK_SHADER_PAINTABLE (ptr: gtk_picture_get_paintable (self: GTK_PICTURE (ptr: widget)));
126 frame_time = gdk_frame_clock_get_frame_time (frame_clock);
127 gsk_shader_paintable_update_time (self: paintable, time_idx: 0, frame_time);
128
129 return G_SOURCE_CONTINUE;
130}
131
132static GtkWidget *
133make_shader_stack (const char *name,
134 const char *resource_path,
135 int active_child,
136 GtkWidget *scale)
137{
138 GtkWidget *stack, *child, *widget, *vbox, *hbox, *bin;
139 GtkWidget *label, *button, *tv;
140 GskGLShader *shader;
141 GObjectClass *class;
142 GParamSpecFloat *pspec;
143 GtkAdjustment *adjustment;
144 GtkTextBuffer *buffer;
145 GBytes *bytes;
146 GtkEventController *controller;
147 GtkCssProvider *provider;
148 GdkPaintable *paintable;
149
150 stack = gtk_shader_stack_new ();
151 shader = gsk_gl_shader_new_from_resource (resource_path);
152 gtk_shader_stack_set_shader (self: GTK_SHADER_STACK (ptr: stack), shader);
153 g_object_unref (object: shader);
154
155 child = gtk_picture_new_for_resource (resource_path: "/css_blendmodes/ducky.png");
156 gtk_picture_set_can_shrink (self: GTK_PICTURE (ptr: child), TRUE);
157 gtk_shader_stack_add_child (self: GTK_SHADER_STACK (ptr: stack), child);
158
159 shader = gsk_gl_shader_new_from_resource (resource_path: "/gltransition/cogs2.glsl");
160 paintable = gsk_shader_paintable_new (shader, NULL);
161
162 child = gtk_picture_new_for_paintable (paintable);
163 gtk_widget_add_tick_callback (widget: child, callback: update_paintable, NULL, NULL);
164 gtk_picture_set_can_shrink (self: GTK_PICTURE (ptr: child), TRUE);
165 gtk_shader_stack_add_child (self: GTK_SHADER_STACK (ptr: stack), child);
166
167 child = gtk_picture_new_for_resource (resource_path: "/transparent/portland-rose.jpg");
168 gtk_picture_set_can_shrink (self: GTK_PICTURE (ptr: child), TRUE);
169 gtk_shader_stack_add_child (self: GTK_SHADER_STACK (ptr: stack), child);
170
171 child = new_shadertoy (path: "/shadertoy/neon.glsl");
172 gtk_shader_stack_add_child (self: GTK_SHADER_STACK (ptr: stack), child);
173
174 child = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 6);
175
176 class = g_type_class_ref (GTK_TYPE_SHADER_STACK);
177 pspec = G_PARAM_SPEC_FLOAT (g_object_class_find_property (class, "duration"));
178
179 adjustment = gtk_range_get_adjustment (GTK_RANGE (scale));
180 if (gtk_adjustment_get_lower (adjustment) == 0.0 &&
181 gtk_adjustment_get_upper (adjustment) == 0.0)
182 {
183 gtk_adjustment_configure (adjustment,
184 value: pspec->default_value,
185 lower: pspec->minimum,
186 upper: pspec->maximum,
187 step_increment: 0.1, page_increment: 0.5, page_size: 0);
188 }
189
190 g_type_class_unref (g_class: class);
191
192 g_object_bind_property (source: adjustment, source_property: "value",
193 target: stack, target_property: "duration",
194 flags: G_BINDING_DEFAULT);
195
196 widget = gtk_scrolled_window_new ();
197 gtk_scrolled_window_set_has_frame (GTK_SCROLLED_WINDOW (widget), TRUE);
198 gtk_widget_set_hexpand (widget, TRUE);
199 gtk_widget_set_vexpand (widget, TRUE);
200
201 controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
202 gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), button: 0);
203 g_signal_connect (controller, "released", G_CALLBACK (clicked_cb), NULL);
204 gtk_event_controller_set_propagation_phase (controller, phase: GTK_PHASE_BUBBLE);
205 gtk_widget_add_controller (GTK_WIDGET (widget), controller);
206
207 tv = gtk_text_view_new ();
208 gtk_text_view_set_left_margin (GTK_TEXT_VIEW (tv), left_margin: 4);
209 gtk_text_view_set_right_margin (GTK_TEXT_VIEW (tv), right_margin: 4);
210 gtk_text_view_set_top_margin (GTK_TEXT_VIEW (tv), top_margin: 4);
211 gtk_text_view_set_bottom_margin (GTK_TEXT_VIEW (tv), bottom_margin: 4);
212 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
213 bytes = g_resources_lookup_data (path: resource_path, lookup_flags: 0, NULL);
214 gtk_text_buffer_set_text (buffer,
215 text: (const char *)g_bytes_get_data (bytes, NULL),
216 len: g_bytes_get_size (bytes));
217 g_bytes_unref (bytes);
218 gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (widget), child: tv);
219
220 gtk_box_append (GTK_BOX (child), child: widget);
221
222 gtk_shader_stack_add_child (self: GTK_SHADER_STACK (ptr: stack), child);
223
224 gtk_shader_stack_set_active (self: GTK_SHADER_STACK (ptr: stack), index: active_child);
225
226 vbox = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 6);
227
228 widget = gtk_center_box_new ();
229 label = gtk_label_new (str: name);
230 gtk_widget_add_css_class (widget: label, css_class: "title-4");
231 gtk_widget_set_size_request (widget: label, width: -1, height: 26);
232 gtk_center_box_set_center_widget (GTK_CENTER_BOX (widget), child: label);
233
234 button = gtk_button_new_from_icon_name (icon_name: "view-refresh-symbolic");
235 g_signal_connect (buffer, "changed", G_CALLBACK (text_changed), button);
236 g_object_set_data (G_OBJECT (button), key: "the-stack", data: stack);
237 g_signal_connect (button, "clicked", G_CALLBACK (apply_text), buffer);
238 provider = gtk_css_provider_new ();
239 gtk_css_provider_load_from_data (css_provider: provider, data: "button.small { padding: 0; }", length: -1);
240 gtk_style_context_add_provider (context: gtk_widget_get_style_context (widget: button),
241 GTK_STYLE_PROVIDER (provider),
242 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
243 g_object_unref (object: provider);
244 gtk_widget_set_halign (widget: button, align: GTK_ALIGN_CENTER);
245 gtk_widget_set_valign (widget: button, align: GTK_ALIGN_CENTER);
246 gtk_widget_add_css_class (widget: button, css_class: "small");
247 gtk_widget_hide (widget: button);
248 gtk_center_box_set_end_widget (GTK_CENTER_BOX (widget), child: button);
249
250 gtk_box_append (GTK_BOX (vbox), child: widget);
251
252 GtkWidget *bin2 = ripple_bin_new ();
253 gtk_shader_bin_set_child (self: GTK_SHADER_BIN (ptr: bin2), child: stack);
254
255 gtk_box_append (GTK_BOX (vbox), child: bin2);
256
257 hbox = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 6);
258 gtk_widget_set_halign (widget: hbox, align: GTK_ALIGN_CENTER);
259
260 gtk_box_append (GTK_BOX (vbox), child: hbox);
261
262 button = gtk_button_new_from_icon_name (icon_name: "go-previous-symbolic");
263 g_signal_connect (button, "clicked", G_CALLBACK (go_back), stack);
264 bin = ripple_bin_new ();
265 gtk_shader_bin_set_child (self: GTK_SHADER_BIN (ptr: bin), child: button);
266 gtk_box_append (GTK_BOX (hbox), child: bin);
267
268 button = gtk_button_new_from_icon_name (icon_name: "go-next-symbolic");
269 g_signal_connect (button, "clicked", G_CALLBACK (go_forward), stack);
270 bin = ripple_bin_new ();
271 gtk_shader_bin_set_child (self: GTK_SHADER_BIN (ptr: bin), child: button);
272 gtk_box_append (GTK_BOX (hbox), child: bin);
273
274 return vbox;
275}
276
277static GtkWidget *
278create_gltransition_window (GtkWidget *do_widget)
279{
280 GtkWidget *window, *headerbar, *scale, *outer_grid, *grid, *background;
281 GdkPaintable *paintable;
282
283 window = gtk_window_new ();
284 gtk_window_set_display (GTK_WINDOW (window), display: gtk_widget_get_display (widget: do_widget));
285 gtk_window_set_title (GTK_WINDOW (window), title: "Transitions and Effects");
286 headerbar = gtk_header_bar_new ();
287 scale = gtk_scale_new (orientation: GTK_ORIENTATION_HORIZONTAL, NULL);
288 gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE);
289 gtk_widget_set_size_request (widget: scale, width: 100, height: -1);
290 gtk_widget_set_tooltip_text (widget: scale, text: "Transition duration");
291 gtk_header_bar_pack_end (GTK_HEADER_BAR (headerbar), child: scale);
292 gtk_window_set_titlebar (GTK_WINDOW (window), titlebar: headerbar);
293 gtk_window_set_default_size (GTK_WINDOW (window), width: 800, height: 600);
294 g_signal_connect (window, "destroy", G_CALLBACK (close_window), NULL);
295
296 outer_grid = gtk_grid_new ();
297 gtk_window_set_child (GTK_WINDOW (window), child: outer_grid);
298
299 paintable = gsk_shader_paintable_new (shader: gsk_gl_shader_new_from_resource (resource_path: "/gltransition/background.glsl"), NULL);
300 background = gtk_picture_new_for_paintable (paintable);
301 gtk_widget_add_tick_callback (widget: background, callback: update_paintable, NULL, NULL);
302
303 gtk_grid_attach (GTK_GRID (outer_grid),
304 child: background,
305 column: 0, row: 0, width: 1, height: 1);
306
307 grid = gtk_grid_new ();
308 gtk_grid_attach (GTK_GRID (outer_grid),
309 child: grid,
310 column: 0, row: 0, width: 1, height: 1);
311
312 gtk_widget_set_halign (widget: grid, align: GTK_ALIGN_CENTER);
313 gtk_widget_set_valign (widget: grid, align: GTK_ALIGN_CENTER);
314 gtk_widget_set_margin_start (widget: grid, margin: 12);
315 gtk_widget_set_margin_end (widget: grid, margin: 12);
316 gtk_widget_set_margin_top (widget: grid, margin: 12);
317 gtk_widget_set_margin_bottom (widget: grid, margin: 12);
318 gtk_grid_set_row_spacing (GTK_GRID (grid), spacing: 6);
319 gtk_grid_set_column_spacing (GTK_GRID (grid), spacing: 6);
320 gtk_grid_set_row_homogeneous (GTK_GRID (grid), TRUE);
321 gtk_grid_set_column_homogeneous (GTK_GRID (grid), TRUE);
322
323 gtk_grid_attach (GTK_GRID (grid),
324 child: make_shader_stack (name: "Wind", resource_path: "/gltransition/wind.glsl", active_child: 0, scale),
325 column: 0, row: 0, width: 1, height: 1);
326 gtk_grid_attach (GTK_GRID (grid),
327 child: make_shader_stack (name: "Radial", resource_path: "/gltransition/radial.glsl", active_child: 1, scale),
328 column: 1, row: 0, width: 1, height: 1);
329 gtk_grid_attach (GTK_GRID (grid),
330 child: make_shader_stack (name: "Crosswarp", resource_path: "/gltransition/crosswarp.glsl", active_child: 2, scale),
331 column: 0, row: 1, width: 1, height: 1);
332 gtk_grid_attach (GTK_GRID (grid),
333 child: make_shader_stack (name: "Kaleidoscope", resource_path: "/gltransition/kaleidoscope.glsl", active_child: 3, scale),
334 column: 1, row: 1, width: 1, height: 1);
335
336 return window;
337}
338
339GtkWidget *
340do_gltransition (GtkWidget *do_widget)
341{
342 if (!demo_window)
343 demo_window = create_gltransition_window (do_widget);
344
345 if (!gtk_widget_get_visible (widget: demo_window))
346 gtk_widget_show (widget: demo_window);
347 else
348 gtk_window_destroy (GTK_WINDOW (demo_window));
349
350 return demo_window;
351}
352

source code of gtk/demos/gtk-demo/gltransition.c