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 | |
23 | static GtkWidget *demo_window = NULL; |
24 | |
25 | static void |
26 | close_window (GtkWidget *widget) |
27 | { |
28 | /* Reset the state */ |
29 | demo_window = NULL; |
30 | } |
31 | |
32 | static void |
33 | text_changed (GtkTextBuffer *buffer, |
34 | GtkWidget *button) |
35 | { |
36 | gtk_widget_show (widget: button); |
37 | } |
38 | |
39 | static void |
40 | apply_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 | |
64 | static void |
65 | go_back (GtkWidget *button, |
66 | GtkWidget *stack) |
67 | { |
68 | gtk_shader_stack_transition (self: GTK_SHADER_STACK (ptr: stack), FALSE); |
69 | } |
70 | |
71 | static void |
72 | go_forward (GtkWidget *button, |
73 | GtkWidget *stack) |
74 | { |
75 | gtk_shader_stack_transition (self: GTK_SHADER_STACK (ptr: stack), TRUE); |
76 | } |
77 | |
78 | static void |
79 | clicked_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 | |
88 | static GtkWidget * |
89 | ripple_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 | |
102 | static GtkWidget * |
103 | new_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 | |
117 | static gboolean |
118 | update_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 | |
132 | static GtkWidget * |
133 | make_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 | |
277 | static GtkWidget * |
278 | create_gltransition_window (GtkWidget *do_widget) |
279 | { |
280 | GtkWidget *window, *, *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 | |
339 | GtkWidget * |
340 | do_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 | |