1/*
2 * Copyright (c) 2016 Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "config.h"
19
20#include "recorder.h"
21
22#include <gtk/gtkbinlayout.h>
23#include <gtk/gtkbox.h>
24#include <gtk/gtkdragsource.h>
25#include <gtk/gtkeventcontroller.h>
26#include <gtk/gtkfilechooserdialog.h>
27#include <gtk/gtksignallistitemfactory.h>
28#include <gtk/gtklabel.h>
29#include <gtk/gtklistbox.h>
30#include <gtk/gtklistitem.h>
31#include <gtk/gtklistview.h>
32#include <gtk/gtkliststore.h>
33#include <gtk/gtkmessagedialog.h>
34#include <gtk/gtkpicture.h>
35#include <gtk/gtkpopover.h>
36#include <gtk/gtksingleselection.h>
37#include <gtk/gtktogglebutton.h>
38#include <gtk/gtktreeexpander.h>
39#include <gtk/gtktreelistmodel.h>
40#include <gtk/gtktreemodel.h>
41#include <gtk/gtktreeview.h>
42#include <gtk/gtkstack.h>
43#include <gsk/gskrendererprivate.h>
44#include <gsk/gskrendernodeprivate.h>
45#include <gsk/gskroundedrectprivate.h>
46#include <gsk/gsktransformprivate.h>
47
48#include <glib/gi18n-lib.h>
49#include <gdk/gdktextureprivate.h>
50#include "gtk/gtkdebug.h"
51#include "gtk/gtkbuiltiniconprivate.h"
52#include "gtk/gtkrendernodepaintableprivate.h"
53
54#include "recording.h"
55#include "renderrecording.h"
56#include "startrecording.h"
57#include "eventrecording.h"
58#include "recorderrow.h"
59
60struct _GtkInspectorRecorder
61{
62 GtkWidget parent;
63
64 GListModel *recordings;
65 GtkTreeListModel *render_node_model;
66 GListStore *render_node_root_model;
67 GtkSingleSelection *render_node_selection;
68
69 GtkWidget *box;
70 GtkWidget *recordings_list;
71 GtkWidget *render_node_view;
72 GtkWidget *render_node_list;
73 GtkWidget *render_node_save_button;
74 GtkWidget *render_node_clip_button;
75 GtkWidget *node_property_tree;
76 GtkWidget *recording_data_stack;
77 GtkTreeModel *render_node_properties;
78 GtkTreeModel *event_properties;
79 GtkWidget *event_property_tree;
80 GtkWidget *event_view;
81
82 GtkInspectorRecording *recording; /* start recording if recording or NULL if not */
83 gint64 start_time;
84
85 gboolean debug_nodes;
86 gboolean highlight_sequences;
87
88 GdkEventSequence *selected_sequence;
89};
90
91typedef struct _GtkInspectorRecorderClass
92{
93 GtkWidgetClass parent;
94} GtkInspectorRecorderClass;
95
96
97enum
98{
99 PROP_0,
100 PROP_RECORDING,
101 PROP_DEBUG_NODES,
102 PROP_HIGHLIGHT_SEQUENCES,
103 PROP_SELECTED_SEQUENCE,
104 LAST_PROP
105};
106
107static GParamSpec *props[LAST_PROP] = { NULL, };
108
109G_DEFINE_TYPE (GtkInspectorRecorder, gtk_inspector_recorder, GTK_TYPE_WIDGET)
110
111static GListModel *
112create_render_node_list_model (GskRenderNode **nodes,
113 guint n_nodes)
114{
115 GListStore *store;
116 guint i;
117
118 /* can't put render nodes into list models - they're not GObjects */
119 store = g_list_store_new (GDK_TYPE_PAINTABLE);
120
121 for (i = 0; i < n_nodes; i++)
122 {
123 graphene_rect_t bounds;
124
125 gsk_render_node_get_bounds (node: nodes[i], bounds: &bounds);
126 GdkPaintable *paintable = gtk_render_node_paintable_new (node: nodes[i], bounds: &bounds);
127 g_list_store_append (store, item: paintable);
128 g_object_unref (object: paintable);
129 }
130
131 return G_LIST_MODEL (ptr: store);
132}
133
134static GListModel *
135create_list_model_for_render_node (GskRenderNode *node)
136{
137 switch (gsk_render_node_get_node_type (node))
138 {
139 default:
140 case GSK_NOT_A_RENDER_NODE:
141 g_assert_not_reached ();
142 return NULL;
143
144 case GSK_CAIRO_NODE:
145 case GSK_TEXT_NODE:
146 case GSK_TEXTURE_NODE:
147 case GSK_COLOR_NODE:
148 case GSK_LINEAR_GRADIENT_NODE:
149 case GSK_REPEATING_LINEAR_GRADIENT_NODE:
150 case GSK_RADIAL_GRADIENT_NODE:
151 case GSK_REPEATING_RADIAL_GRADIENT_NODE:
152 case GSK_CONIC_GRADIENT_NODE:
153 case GSK_BORDER_NODE:
154 case GSK_INSET_SHADOW_NODE:
155 case GSK_OUTSET_SHADOW_NODE:
156 /* no children */
157 return NULL;
158
159 case GSK_TRANSFORM_NODE:
160 return create_render_node_list_model (nodes: (GskRenderNode *[1]) { gsk_transform_node_get_child (node) }, n_nodes: 1);
161
162 case GSK_OPACITY_NODE:
163 return create_render_node_list_model (nodes: (GskRenderNode *[1]) { gsk_opacity_node_get_child (node) }, n_nodes: 1);
164
165 case GSK_COLOR_MATRIX_NODE:
166 return create_render_node_list_model (nodes: (GskRenderNode *[1]) { gsk_color_matrix_node_get_child (node) }, n_nodes: 1);
167
168 case GSK_BLUR_NODE:
169 return create_render_node_list_model (nodes: (GskRenderNode *[1]) { gsk_blur_node_get_child (node) }, n_nodes: 1);
170
171 case GSK_REPEAT_NODE:
172 return create_render_node_list_model (nodes: (GskRenderNode *[1]) { gsk_repeat_node_get_child (node) }, n_nodes: 1);
173
174 case GSK_CLIP_NODE:
175 return create_render_node_list_model (nodes: (GskRenderNode *[1]) { gsk_clip_node_get_child (node) }, n_nodes: 1);
176
177 case GSK_ROUNDED_CLIP_NODE:
178 return create_render_node_list_model (nodes: (GskRenderNode *[1]) { gsk_rounded_clip_node_get_child (node) }, n_nodes: 1);
179
180 case GSK_SHADOW_NODE:
181 return create_render_node_list_model (nodes: (GskRenderNode *[1]) { gsk_shadow_node_get_child (node) }, n_nodes: 1);
182
183 case GSK_BLEND_NODE:
184 return create_render_node_list_model (nodes: (GskRenderNode *[2]) { gsk_blend_node_get_bottom_child (node),
185 gsk_blend_node_get_top_child (node) }, n_nodes: 2);
186
187 case GSK_CROSS_FADE_NODE:
188 return create_render_node_list_model (nodes: (GskRenderNode *[2]) { gsk_cross_fade_node_get_start_child (node),
189 gsk_cross_fade_node_get_end_child (node) }, n_nodes: 2);
190
191 case GSK_GL_SHADER_NODE:
192 {
193 GListStore *store = g_list_store_new (GDK_TYPE_PAINTABLE);
194
195 for (guint i = 0; i < gsk_gl_shader_node_get_n_children (node); i++)
196 {
197 GskRenderNode *child = gsk_gl_shader_node_get_child (node, idx: i);
198 graphene_rect_t bounds;
199 GdkPaintable *paintable;
200
201 gsk_render_node_get_bounds (node: child, bounds: &bounds);
202 paintable = gtk_render_node_paintable_new (node: child, bounds: &bounds);
203 g_list_store_append (store, item: paintable);
204 g_object_unref (object: paintable);
205 }
206
207 return G_LIST_MODEL (ptr: store);
208 }
209
210 case GSK_CONTAINER_NODE:
211 {
212 GListStore *store;
213 guint i;
214
215 /* can't put render nodes into list models - they're not GObjects */
216 store = g_list_store_new (GDK_TYPE_PAINTABLE);
217
218 for (i = 0; i < gsk_container_node_get_n_children (node); i++)
219 {
220 GskRenderNode *child = gsk_container_node_get_child (node, idx: i);
221 graphene_rect_t bounds;
222 GdkPaintable *paintable;
223
224 gsk_render_node_get_bounds (node: child, bounds: &bounds);
225 paintable = gtk_render_node_paintable_new (node: child, bounds: &bounds);
226 g_list_store_append (store, item: paintable);
227 g_object_unref (object: paintable);
228 }
229
230 return G_LIST_MODEL (ptr: store);
231 }
232
233 case GSK_DEBUG_NODE:
234 return create_render_node_list_model (nodes: (GskRenderNode *[1]) { gsk_debug_node_get_child (node) }, n_nodes: 1);
235 }
236}
237
238static GListModel *
239create_list_model_for_render_node_paintable (gpointer paintable,
240 gpointer unused)
241{
242 GskRenderNode *node = gtk_render_node_paintable_get_render_node (self: paintable);
243
244 return create_list_model_for_render_node (node);
245}
246
247static void
248recordings_clear_all (GtkButton *button,
249 GtkInspectorRecorder *recorder)
250{
251 g_list_store_remove_all (store: G_LIST_STORE (ptr: recorder->recordings));
252}
253
254static const char *
255node_type_name (GskRenderNodeType type)
256{
257 switch (type)
258 {
259 case GSK_NOT_A_RENDER_NODE:
260 default:
261 g_assert_not_reached ();
262 return "Unknown";
263 case GSK_CONTAINER_NODE:
264 return "Container";
265 case GSK_DEBUG_NODE:
266 return "Debug";
267 case GSK_CAIRO_NODE:
268 return "Cairo";
269 case GSK_COLOR_NODE:
270 return "Color";
271 case GSK_LINEAR_GRADIENT_NODE:
272 return "Linear Gradient";
273 case GSK_REPEATING_LINEAR_GRADIENT_NODE:
274 return "Repeating Linear Gradient";
275 case GSK_RADIAL_GRADIENT_NODE:
276 return "Radial Gradient";
277 case GSK_REPEATING_RADIAL_GRADIENT_NODE:
278 return "Repeating Radial Gradient";
279 case GSK_CONIC_GRADIENT_NODE:
280 return "Conic Gradient";
281 case GSK_BORDER_NODE:
282 return "Border";
283 case GSK_TEXTURE_NODE:
284 return "Texture";
285 case GSK_INSET_SHADOW_NODE:
286 return "Inset Shadow";
287 case GSK_OUTSET_SHADOW_NODE:
288 return "Outset Shadow";
289 case GSK_TRANSFORM_NODE:
290 return "Transform";
291 case GSK_OPACITY_NODE:
292 return "Opacity";
293 case GSK_COLOR_MATRIX_NODE:
294 return "Color Matrix";
295 case GSK_REPEAT_NODE:
296 return "Repeat";
297 case GSK_CLIP_NODE:
298 return "Clip";
299 case GSK_ROUNDED_CLIP_NODE:
300 return "Rounded Clip";
301 case GSK_SHADOW_NODE:
302 return "Shadow";
303 case GSK_BLEND_NODE:
304 return "Blend";
305 case GSK_CROSS_FADE_NODE:
306 return "CrossFade";
307 case GSK_TEXT_NODE:
308 return "Text";
309 case GSK_BLUR_NODE:
310 return "Blur";
311 case GSK_GL_SHADER_NODE:
312 return "GL Shader";
313 }
314}
315
316static char *
317node_name (GskRenderNode *node)
318{
319 switch (gsk_render_node_get_node_type (node))
320 {
321 case GSK_NOT_A_RENDER_NODE:
322 default:
323 g_assert_not_reached ();
324 case GSK_CONTAINER_NODE:
325 case GSK_CAIRO_NODE:
326 case GSK_LINEAR_GRADIENT_NODE:
327 case GSK_REPEATING_LINEAR_GRADIENT_NODE:
328 case GSK_RADIAL_GRADIENT_NODE:
329 case GSK_REPEATING_RADIAL_GRADIENT_NODE:
330 case GSK_CONIC_GRADIENT_NODE:
331 case GSK_BORDER_NODE:
332 case GSK_INSET_SHADOW_NODE:
333 case GSK_OUTSET_SHADOW_NODE:
334 case GSK_TRANSFORM_NODE:
335 case GSK_OPACITY_NODE:
336 case GSK_COLOR_MATRIX_NODE:
337 case GSK_REPEAT_NODE:
338 case GSK_CLIP_NODE:
339 case GSK_ROUNDED_CLIP_NODE:
340 case GSK_SHADOW_NODE:
341 case GSK_BLEND_NODE:
342 case GSK_CROSS_FADE_NODE:
343 case GSK_TEXT_NODE:
344 case GSK_BLUR_NODE:
345 case GSK_GL_SHADER_NODE:
346 return g_strdup (str: node_type_name (type: gsk_render_node_get_node_type (node)));
347
348 case GSK_DEBUG_NODE:
349 return g_strdup (str: gsk_debug_node_get_message (node));
350
351 case GSK_COLOR_NODE:
352 return gdk_rgba_to_string (rgba: gsk_color_node_get_color (node));
353
354 case GSK_TEXTURE_NODE:
355 {
356 GdkTexture *texture = gsk_texture_node_get_texture (node);
357 return g_strdup_printf (format: "%dx%d Texture", gdk_texture_get_width (texture), gdk_texture_get_height (texture));
358 }
359 }
360}
361
362static GdkContentProvider *
363prepare_render_node_drag (GtkDragSource *source,
364 double x,
365 double y,
366 GtkListItem *list_item)
367{
368 GtkTreeListRow *row_item;
369 GdkPaintable *paintable;
370 GskRenderNode *node;
371
372 row_item = gtk_list_item_get_item (self: list_item);
373 if (row_item == NULL)
374 return NULL;
375
376 paintable = gtk_tree_list_row_get_item (self: row_item);
377 node = gtk_render_node_paintable_get_render_node (self: GTK_RENDER_NODE_PAINTABLE (ptr: paintable));
378
379 return gdk_content_provider_new_typed (GSK_TYPE_RENDER_NODE, node);
380}
381
382static void
383setup_widget_for_render_node (GtkSignalListItemFactory *factory,
384 GtkListItem *list_item)
385{
386 GtkWidget *expander, *box, *child;
387 GtkDragSource *source;
388
389 /* expander */
390 expander = gtk_tree_expander_new ();
391 gtk_list_item_set_child (self: list_item, child: expander);
392 source = gtk_drag_source_new ();
393 g_signal_connect (source, "prepare", G_CALLBACK (prepare_render_node_drag), list_item);
394 gtk_widget_add_controller (widget: expander, GTK_EVENT_CONTROLLER (source));
395
396 box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 3);
397 gtk_tree_expander_set_child (self: GTK_TREE_EXPANDER (ptr: expander), child: box);
398
399 /* icon */
400 child = gtk_image_new ();
401 gtk_box_append (GTK_BOX (box), child);
402
403 /* name */
404 child = gtk_label_new (NULL);
405 gtk_box_append (GTK_BOX (box), child);
406}
407
408static void
409bind_widget_for_render_node (GtkSignalListItemFactory *factory,
410 GtkListItem *list_item)
411{
412 GdkPaintable *paintable;
413 GskRenderNode *node;
414 GtkTreeListRow *row_item;
415 GtkWidget *expander, *box, *child;
416 char *name;
417
418 row_item = gtk_list_item_get_item (self: list_item);
419 paintable = gtk_tree_list_row_get_item (self: row_item);
420 node = gtk_render_node_paintable_get_render_node (self: GTK_RENDER_NODE_PAINTABLE (ptr: paintable));
421
422 /* expander */
423 expander = gtk_list_item_get_child (self: list_item);
424 gtk_tree_expander_set_list_row (self: GTK_TREE_EXPANDER (ptr: expander), list_row: row_item);
425 box = gtk_tree_expander_get_child (self: GTK_TREE_EXPANDER (ptr: expander));
426
427 /* icon */
428 child = gtk_widget_get_first_child (widget: box);
429 gtk_image_set_from_paintable (GTK_IMAGE (child), paintable);
430
431 /* name */
432 name = node_name (node);
433 child = gtk_widget_get_last_child (widget: box);
434 gtk_label_set_label (GTK_LABEL (child), str: name);
435 g_free (mem: name);
436
437 g_object_unref (object: paintable);
438}
439
440static void
441show_render_node (GtkInspectorRecorder *recorder,
442 GskRenderNode *node)
443{
444 graphene_rect_t bounds;
445 GdkPaintable *paintable;
446
447 gsk_render_node_get_bounds (node, bounds: &bounds);
448 paintable = gtk_render_node_paintable_new (node, bounds: &bounds);
449
450 if (strcmp (s1: gtk_stack_get_visible_child_name (GTK_STACK (recorder->recording_data_stack)), s2: "frame_data") == 0)
451 {
452 gtk_picture_set_paintable (self: GTK_PICTURE (ptr: recorder->render_node_view), paintable);
453
454 g_list_store_splice (store: recorder->render_node_root_model,
455 position: 0, n_removals: g_list_model_get_n_items (list: G_LIST_MODEL (ptr: recorder->render_node_root_model)),
456 additions: (gpointer[1]) { paintable },
457 n_additions: 1);
458 }
459 else
460 {
461 gtk_picture_set_paintable (self: GTK_PICTURE (ptr: recorder->event_view), paintable);
462 }
463
464 g_object_unref (object: paintable);
465}
466
467static GskRenderNode *
468make_dot (double x, double y)
469{
470 GskRenderNode *fill, *dot;
471 GdkRGBA red = (GdkRGBA){ 1, 0, 0, 1 };
472 graphene_rect_t rect = GRAPHENE_RECT_INIT (x - 3, y - 3, 6, 6);
473 graphene_size_t corner = GRAPHENE_SIZE_INIT (3, 3);
474 GskRoundedRect clip;
475
476 fill = gsk_color_node_new (rgba: &red, bounds: &rect);
477 dot = gsk_rounded_clip_node_new (child: fill, clip: gsk_rounded_rect_init (self: &clip, bounds: &rect,
478 top_left: &corner, top_right: &corner, bottom_right: &corner, bottom_left: &corner));
479 gsk_render_node_unref (node: fill);
480
481 return dot;
482}
483
484static void
485show_event (GtkInspectorRecorder *recorder,
486 GskRenderNode *node,
487 GdkEvent *event)
488{
489 GskRenderNode *temp;
490 double x, y;
491
492 if (gdk_event_get_position (event, x: &x, y: &y))
493 {
494 GskRenderNode *dot = make_dot (x, y);
495 temp = gsk_container_node_new (children: (GskRenderNode *[]) { node, dot }, n_children: 2);
496 gsk_render_node_unref (node: dot);
497 }
498 else
499 temp = gsk_render_node_ref (node);
500
501 show_render_node (recorder, node: temp);
502
503 gsk_render_node_unref (node: temp);
504}
505
506static void populate_event_properties (GtkListStore *store,
507 GdkEvent *event);
508
509static void
510recording_selected (GtkSingleSelection *selection,
511 GParamSpec *pspec,
512 GtkInspectorRecorder *recorder)
513{
514 GtkInspectorRecording *recording;
515 GdkEventSequence *selected_sequence = NULL;
516
517 if (recorder->recordings == NULL)
518 {
519 gtk_stack_set_visible_child_name (GTK_STACK (recorder->recording_data_stack), name: "no_data");
520 return;
521 }
522
523 recording = gtk_single_selection_get_selected_item (self: selection);
524
525 if (GTK_INSPECTOR_IS_RENDER_RECORDING (recording))
526 {
527 GskRenderNode *node;
528
529 gtk_stack_set_visible_child_name (GTK_STACK (recorder->recording_data_stack), name: "frame_data");
530
531 node = gtk_inspector_render_recording_get_node (GTK_INSPECTOR_RENDER_RECORDING (recording));
532 show_render_node (recorder, node);
533 }
534 else if (GTK_INSPECTOR_IS_EVENT_RECORDING (recording))
535 {
536 GdkEvent *event;
537
538 gtk_stack_set_visible_child_name (GTK_STACK (recorder->recording_data_stack), name: "event_data");
539
540 event = gtk_inspector_event_recording_get_event (GTK_INSPECTOR_EVENT_RECORDING (recording));
541
542 for (guint pos = gtk_single_selection_get_selected (self: selection) - 1; pos > 0; pos--)
543 {
544 GtkInspectorRecording *item = g_list_model_get_item (list: G_LIST_MODEL (ptr: selection), position: pos);
545
546 g_object_unref (object: item);
547 if (GTK_INSPECTOR_IS_RENDER_RECORDING (item))
548 {
549 GskRenderNode *node;
550
551 node = gtk_inspector_render_recording_get_node (GTK_INSPECTOR_RENDER_RECORDING (item));
552 show_event (recorder, node, event);
553 break;
554 }
555 }
556
557 populate_event_properties (GTK_LIST_STORE (recorder->event_properties), event);
558
559 if (recorder->highlight_sequences)
560 selected_sequence = gdk_event_get_event_sequence (event);
561 }
562 else
563 {
564 gtk_stack_set_visible_child_name (GTK_STACK (recorder->recording_data_stack), name: "no_data");
565
566 gtk_picture_set_paintable (self: GTK_PICTURE (ptr: recorder->render_node_view), NULL);
567 g_list_store_remove_all (store: recorder->render_node_root_model);
568 }
569
570 gtk_inspector_recorder_set_selected_sequence (recorder, sequence: selected_sequence);
571}
572
573static GdkTexture *
574get_color_texture (const GdkRGBA *color)
575{
576 GdkTexture *texture;
577 guchar pixel[4];
578 guchar *data;
579 GBytes *bytes;
580 int width = 30;
581 int height = 30;
582 int i;
583
584 pixel[0] = round (x: color->red * 255);
585 pixel[1] = round (x: color->green * 255);
586 pixel[2] = round (x: color->blue * 255);
587 pixel[3] = round (x: color->alpha * 255);
588
589 data = g_malloc (n_bytes: 4 * width * height);
590 for (i = 0; i < width * height; i++)
591 {
592 memcpy (dest: data + 4 * i, src: pixel, n: 4);
593 }
594
595 bytes = g_bytes_new_take (data, size: 4 * width * height);
596 texture = gdk_memory_texture_new (width,
597 height,
598 format: GDK_MEMORY_R8G8B8A8,
599 bytes,
600 stride: width * 4);
601 g_bytes_unref (bytes);
602
603 return texture;
604}
605
606static GdkTexture *
607get_linear_gradient_texture (gsize n_stops, const GskColorStop *stops)
608{
609 cairo_surface_t *surface;
610 cairo_t *cr;
611 cairo_pattern_t *pattern;
612 GdkTexture *texture;
613 int i;
614
615 surface = cairo_image_surface_create (format: CAIRO_FORMAT_ARGB32, width: 90, height: 30);
616 cr = cairo_create (target: surface);
617
618 pattern = cairo_pattern_create_linear (x0: 0, y0: 0, x1: 90, y1: 0);
619 for (i = 0; i < n_stops; i++)
620 {
621 cairo_pattern_add_color_stop_rgba (pattern,
622 offset: stops[i].offset,
623 red: stops[i].color.red,
624 green: stops[i].color.green,
625 blue: stops[i].color.blue,
626 alpha: stops[i].color.alpha);
627 }
628
629 cairo_set_source (cr, source: pattern);
630 cairo_pattern_destroy (pattern);
631 cairo_rectangle (cr, x: 0, y: 0, width: 90, height: 30);
632 cairo_fill (cr);
633 cairo_destroy (cr);
634
635 texture = gdk_texture_new_for_surface (surface);
636 cairo_surface_destroy (surface);
637
638 return texture;
639}
640
641static void
642add_text_row (GtkListStore *store,
643 const char *name,
644 const char *text)
645{
646 gtk_list_store_insert_with_values (list_store: store, NULL, position: -1,
647 0, name,
648 1, text,
649 2, FALSE,
650 3, NULL,
651 -1);
652}
653
654static void
655add_color_row (GtkListStore *store,
656 const char *name,
657 const GdkRGBA *color)
658{
659 char *text;
660 GdkTexture *texture;
661
662 text = gdk_rgba_to_string (rgba: color);
663 texture = get_color_texture (color);
664 gtk_list_store_insert_with_values (list_store: store, NULL, position: -1,
665 0, name,
666 1, text,
667 2, TRUE,
668 3, texture,
669 -1);
670 g_free (mem: text);
671 g_object_unref (object: texture);
672}
673
674static void
675add_int_row (GtkListStore *store,
676 const char *name,
677 int value)
678{
679 char *text = g_strdup_printf (format: "%d", value);
680 add_text_row (store, name, text);
681 g_free (mem: text);
682}
683
684static void
685add_uint_row (GtkListStore *store,
686 const char *name,
687 guint value)
688{
689 char *text = g_strdup_printf (format: "%u", value);
690 add_text_row (store, name, text);
691 g_free (mem: text);
692}
693
694static void
695add_boolean_row (GtkListStore *store,
696 const char *name,
697 gboolean value)
698{
699 add_text_row (store, name, text: value ? "TRUE" : "FALSE");
700}
701
702static void
703add_float_row (GtkListStore *store,
704 const char *name,
705 float value)
706{
707 char *text = g_strdup_printf (format: "%.2f", value);
708 add_text_row (store, name, text);
709 g_free (mem: text);
710}
711
712static void
713populate_render_node_properties (GtkListStore *store,
714 GskRenderNode *node)
715{
716 graphene_rect_t bounds;
717 char *tmp;
718
719 gtk_list_store_clear (list_store: store);
720
721 gsk_render_node_get_bounds (node, bounds: &bounds);
722
723 add_text_row (store, name: "Type", text: node_type_name (type: gsk_render_node_get_node_type (node)));
724
725 tmp = g_strdup_printf (format: "%.2f x %.2f + %.2f + %.2f",
726 bounds.size.width,
727 bounds.size.height,
728 bounds.origin.x,
729 bounds.origin.y);
730 add_text_row (store, name: "Bounds", text: tmp);
731 g_free (mem: tmp);
732
733 switch (gsk_render_node_get_node_type (node))
734 {
735 case GSK_CAIRO_NODE:
736 {
737 GdkTexture *texture;
738 cairo_surface_t *drawn_surface;
739 cairo_t *cr;
740 gboolean show_inline;
741
742 drawn_surface = cairo_image_surface_create (format: CAIRO_FORMAT_ARGB32,
743 width: ceilf (x: node->bounds.size.width),
744 height: ceilf (x: node->bounds.size.height));
745 cr = cairo_create (target: drawn_surface);
746 cairo_save (cr);
747 cairo_translate (cr, tx: -node->bounds.origin.x, ty: -node->bounds.origin.y);
748 gsk_render_node_draw (node, cr);
749 cairo_restore (cr);
750
751 cairo_destroy (cr);
752
753 texture = gdk_texture_new_for_surface (surface: drawn_surface);
754 cairo_surface_destroy (surface: drawn_surface);
755
756 show_inline = gdk_texture_get_height (texture) <= 40 &&
757 gdk_texture_get_width (texture) <= 100;
758
759 gtk_list_store_insert_with_values (list_store: store, NULL, position: -1,
760 0, "Surface",
761 1, show_inline ? "" : "Yes (click to show)",
762 2, show_inline,
763 3, texture,
764 -1);
765 }
766 break;
767
768 case GSK_TEXTURE_NODE:
769 {
770 GdkTexture *texture = g_object_ref (gsk_texture_node_get_texture (node));
771 gboolean show_inline;
772
773 show_inline = gdk_texture_get_height (texture) <= 40 &&
774 gdk_texture_get_width (texture) <= 100;
775
776 gtk_list_store_insert_with_values (list_store: store, NULL, position: -1,
777 0, "Texture",
778 1, show_inline ? "" : "Yes (click to show)",
779 2, show_inline,
780 3, texture,
781 -1);
782 }
783 break;
784
785 case GSK_COLOR_NODE:
786 add_color_row (store, name: "Color", color: gsk_color_node_get_color (node));
787 break;
788
789 case GSK_LINEAR_GRADIENT_NODE:
790 case GSK_REPEATING_LINEAR_GRADIENT_NODE:
791 {
792 const graphene_point_t *start = gsk_linear_gradient_node_get_start (node);
793 const graphene_point_t *end = gsk_linear_gradient_node_get_end (node);
794 const gsize n_stops = gsk_linear_gradient_node_get_n_color_stops (node);
795 const GskColorStop *stops = gsk_linear_gradient_node_get_color_stops (node, NULL);
796 int i;
797 GString *s;
798 GdkTexture *texture;
799
800 tmp = g_strdup_printf (format: "%.2f %.2f ⟶ %.2f %.2f", start->x, start->y, end->x, end->y);
801 add_text_row (store, name: "Direction", text: tmp);
802 g_free (mem: tmp);
803
804 s = g_string_new (init: "");
805 for (i = 0; i < n_stops; i++)
806 {
807 tmp = gdk_rgba_to_string (rgba: &stops[i].color);
808 g_string_append_printf (string: s, format: "%.2f, %s\n", stops[i].offset, tmp);
809 g_free (mem: tmp);
810 }
811
812 texture = get_linear_gradient_texture (n_stops, stops);
813 gtk_list_store_insert_with_values (list_store: store, NULL, position: -1,
814 0, "Color Stops",
815 1, s->str,
816 2, TRUE,
817 3, texture,
818 -1);
819 g_string_free (string: s, TRUE);
820 g_object_unref (object: texture);
821 }
822 break;
823
824 case GSK_RADIAL_GRADIENT_NODE:
825 case GSK_REPEATING_RADIAL_GRADIENT_NODE:
826 {
827 const graphene_point_t *center = gsk_radial_gradient_node_get_center (node);
828 const float start = gsk_radial_gradient_node_get_start (node);
829 const float end = gsk_radial_gradient_node_get_end (node);
830 const float hradius = gsk_radial_gradient_node_get_hradius (node);
831 const float vradius = gsk_radial_gradient_node_get_vradius (node);
832 const gsize n_stops = gsk_radial_gradient_node_get_n_color_stops (node);
833 const GskColorStop *stops = gsk_radial_gradient_node_get_color_stops (node, NULL);
834 int i;
835 GString *s;
836 GdkTexture *texture;
837
838 tmp = g_strdup_printf (format: "%.2f, %.2f", center->x, center->y);
839 add_text_row (store, name: "Center", text: tmp);
840 g_free (mem: tmp);
841
842 tmp = g_strdup_printf (format: "%.2f ⟶ %.2f", start, end);
843 add_text_row (store, name: "Direction", text: tmp);
844 g_free (mem: tmp);
845
846 tmp = g_strdup_printf (format: "%.2f, %.2f", hradius, vradius);
847 add_text_row (store, name: "Radius", text: tmp);
848 g_free (mem: tmp);
849
850 s = g_string_new (init: "");
851 for (i = 0; i < n_stops; i++)
852 {
853 tmp = gdk_rgba_to_string (rgba: &stops[i].color);
854 g_string_append_printf (string: s, format: "%.2f, %s\n", stops[i].offset, tmp);
855 g_free (mem: tmp);
856 }
857
858 texture = get_linear_gradient_texture (n_stops, stops);
859 gtk_list_store_insert_with_values (list_store: store, NULL, position: -1,
860 0, "Color Stops",
861 1, s->str,
862 2, TRUE,
863 3, texture,
864 -1);
865 g_string_free (string: s, TRUE);
866 g_object_unref (object: texture);
867 }
868 break;
869
870 case GSK_CONIC_GRADIENT_NODE:
871 {
872 const graphene_point_t *center = gsk_conic_gradient_node_get_center (node);
873 const float rotation = gsk_conic_gradient_node_get_rotation (node);
874 const gsize n_stops = gsk_conic_gradient_node_get_n_color_stops (node);
875 const GskColorStop *stops = gsk_conic_gradient_node_get_color_stops (node, NULL);
876 gsize i;
877 GString *s;
878 GdkTexture *texture;
879
880 tmp = g_strdup_printf (format: "%.2f, %.2f", center->x, center->y);
881 add_text_row (store, name: "Center", text: tmp);
882 g_free (mem: tmp);
883
884 tmp = g_strdup_printf (format: "%.2f", rotation);
885 add_text_row (store, name: "Rotation", text: tmp);
886 g_free (mem: tmp);
887
888 s = g_string_new (init: "");
889 for (i = 0; i < n_stops; i++)
890 {
891 tmp = gdk_rgba_to_string (rgba: &stops[i].color);
892 g_string_append_printf (string: s, format: "%.2f, %s\n", stops[i].offset, tmp);
893 g_free (mem: tmp);
894 }
895
896 texture = get_linear_gradient_texture (n_stops, stops);
897 gtk_list_store_insert_with_values (list_store: store, NULL, position: -1,
898 0, "Color Stops",
899 1, s->str,
900 2, TRUE,
901 3, texture,
902 -1);
903 g_string_free (string: s, TRUE);
904 g_object_unref (object: texture);
905 }
906 break;
907
908 case GSK_TEXT_NODE:
909 {
910 const PangoFont *font = gsk_text_node_get_font (node);
911 const GdkRGBA *color = gsk_text_node_get_color (node);
912 const graphene_point_t *offset = gsk_text_node_get_offset (node);
913 PangoFontDescription *desc;
914 GString *s;
915
916 desc = pango_font_describe (font: (PangoFont *)font);
917 tmp = pango_font_description_to_string (desc);
918 add_text_row (store, name: "Font", text: tmp);
919 g_free (mem: tmp);
920 pango_font_description_free (desc);
921
922 s = g_string_sized_new (dfl_size: 0);
923 gsk_text_node_serialize_glyphs (self: node, str: s);
924 add_text_row (store, name: "Glyphs", text: s->str);
925 g_string_free (string: s, TRUE);
926
927 tmp = g_strdup_printf (format: "%.2f %.2f", offset->x, offset->y);
928 add_text_row (store, name: "Position", text: tmp);
929 g_free (mem: tmp);
930
931 add_color_row (store, name: "Color", color);
932 }
933 break;
934
935 case GSK_BORDER_NODE:
936 {
937 const char *name[4] = { "Top", "Right", "Bottom", "Left" };
938 const float *widths = gsk_border_node_get_widths (node);
939 const GdkRGBA *colors = gsk_border_node_get_colors (node);
940 int i;
941
942 for (i = 0; i < 4; i++)
943 {
944 GdkTexture *texture;
945 char *text;
946
947 texture = get_color_texture (color: &colors[i]);
948 text = gdk_rgba_to_string (rgba: &colors[i]);
949 tmp = g_strdup_printf (format: "%.2f, %s", widths[i], text);
950 gtk_list_store_insert_with_values (list_store: store, NULL, position: -1,
951 0, name[i],
952 1, tmp,
953 2, TRUE,
954 3, texture,
955 -1);
956 g_free (mem: text);
957 g_free (mem: tmp);
958 g_object_unref (object: texture);
959 }
960 }
961 break;
962
963 case GSK_OPACITY_NODE:
964 add_float_row (store, name: "Opacity", value: gsk_opacity_node_get_opacity (node));
965 break;
966
967 case GSK_CROSS_FADE_NODE:
968 add_float_row (store, name: "Progress", value: gsk_cross_fade_node_get_progress (node));
969 break;
970
971 case GSK_BLEND_NODE:
972 {
973 GskBlendMode mode = gsk_blend_node_get_blend_mode (node);
974 tmp = g_enum_to_string (g_enum_type: GSK_TYPE_BLEND_MODE, value: mode);
975 add_text_row (store, name: "Blendmode", text: tmp);
976 g_free (mem: tmp);
977 }
978 break;
979
980 case GSK_BLUR_NODE:
981 add_float_row (store, name: "Radius", value: gsk_blur_node_get_radius (node));
982 break;
983
984 case GSK_GL_SHADER_NODE:
985 {
986 GskGLShader *shader = gsk_gl_shader_node_get_shader (node);
987 GBytes *args = gsk_gl_shader_node_get_args (node);
988 int i;
989
990 add_int_row (store, name: "Required textures", value: gsk_gl_shader_get_n_textures (shader));
991 for (i = 0; i < gsk_gl_shader_get_n_uniforms (shader); i++)
992 {
993 const char *name;
994 char *title;
995
996 name = gsk_gl_shader_get_uniform_name (shader, idx: i);
997 title = g_strdup_printf (format: "Uniform %s", name);
998
999 switch (gsk_gl_shader_get_uniform_type (shader, idx: i))
1000 {
1001 case GSK_GL_UNIFORM_TYPE_NONE:
1002 default:
1003 g_assert_not_reached ();
1004 break;
1005
1006 case GSK_GL_UNIFORM_TYPE_FLOAT:
1007 add_float_row (store, name: title,
1008 value: gsk_gl_shader_get_arg_float (shader, args, idx: i));
1009 break;
1010
1011 case GSK_GL_UNIFORM_TYPE_INT:
1012 add_int_row (store, name: title,
1013 value: gsk_gl_shader_get_arg_int (shader, args, idx: i));
1014 break;
1015
1016 case GSK_GL_UNIFORM_TYPE_UINT:
1017 add_uint_row (store, name: title,
1018 value: gsk_gl_shader_get_arg_uint (shader, args, idx: i));
1019 break;
1020
1021 case GSK_GL_UNIFORM_TYPE_BOOL:
1022 add_boolean_row (store, name: title,
1023 value: gsk_gl_shader_get_arg_bool (shader, args, idx: i));
1024 break;
1025
1026 case GSK_GL_UNIFORM_TYPE_VEC2:
1027 {
1028 graphene_vec2_t v;
1029 gsk_gl_shader_get_arg_vec2 (shader, args, idx: i, out_value: &v);
1030 float x = graphene_vec2_get_x (v: &v);
1031 float y = graphene_vec2_get_x (v: &v);
1032 char *s = g_strdup_printf (format: "%.2f %.2f", x, y);
1033 add_text_row (store, name: title, text: s);
1034 g_free (mem: s);
1035 }
1036 break;
1037
1038 case GSK_GL_UNIFORM_TYPE_VEC3:
1039 {
1040 graphene_vec3_t v;
1041 gsk_gl_shader_get_arg_vec3 (shader, args, idx: i, out_value: &v);
1042 float x = graphene_vec3_get_x (v: &v);
1043 float y = graphene_vec3_get_y (v: &v);
1044 float z = graphene_vec3_get_z (v: &v);
1045 char *s = g_strdup_printf (format: "%.2f %.2f %.2f", x, y, z);
1046 add_text_row (store, name: title, text: s);
1047 g_free (mem: s);
1048 }
1049 break;
1050
1051 case GSK_GL_UNIFORM_TYPE_VEC4:
1052 {
1053 graphene_vec4_t v;
1054 gsk_gl_shader_get_arg_vec4 (shader, args, idx: i, out_value: &v);
1055 float x = graphene_vec4_get_x (v: &v);
1056 float y = graphene_vec4_get_y (v: &v);
1057 float z = graphene_vec4_get_z (v: &v);
1058 float w = graphene_vec4_get_w (v: &v);
1059 char *s = g_strdup_printf (format: "%.2f %.2f %.2f %.2f", x, y, z, w);
1060 add_text_row (store, name: title, text: s);
1061 g_free (mem: s);
1062 }
1063 break;
1064 }
1065 g_free (mem: title);
1066 }
1067 }
1068 break;
1069
1070 case GSK_INSET_SHADOW_NODE:
1071 {
1072 const GdkRGBA *color = gsk_inset_shadow_node_get_color (node);
1073 float dx = gsk_inset_shadow_node_get_dx (node);
1074 float dy = gsk_inset_shadow_node_get_dy (node);
1075 float spread = gsk_inset_shadow_node_get_spread (node);
1076 float radius = gsk_inset_shadow_node_get_blur_radius (node);
1077
1078 add_color_row (store, name: "Color", color);
1079
1080 tmp = g_strdup_printf (format: "%.2f %.2f", dx, dy);
1081 add_text_row (store, name: "Offset", text: tmp);
1082 g_free (mem: tmp);
1083
1084 add_float_row (store, name: "Spread", value: spread);
1085 add_float_row (store, name: "Radius", value: radius);
1086 }
1087 break;
1088
1089 case GSK_OUTSET_SHADOW_NODE:
1090 {
1091 const GskRoundedRect *outline = gsk_outset_shadow_node_get_outline (node);
1092 const GdkRGBA *color = gsk_outset_shadow_node_get_color (node);
1093 float dx = gsk_outset_shadow_node_get_dx (node);
1094 float dy = gsk_outset_shadow_node_get_dy (node);
1095 float spread = gsk_outset_shadow_node_get_spread (node);
1096 float radius = gsk_outset_shadow_node_get_blur_radius (node);
1097 float rect[12];
1098
1099 gsk_rounded_rect_to_float (self: outline, rect);
1100 tmp = g_strdup_printf (format: "%.2f x %.2f + %.2f + %.2f",
1101 rect[2], rect[3], rect[0], rect[1]);
1102 add_text_row (store, name: "Outline", text: tmp);
1103 g_free (mem: tmp);
1104
1105 add_color_row (store, name: "Color", color);
1106
1107 tmp = g_strdup_printf (format: "%.2f %.2f", dx, dy);
1108 add_text_row (store, name: "Offset", text: tmp);
1109 g_free (mem: tmp);
1110
1111 add_float_row (store, name: "Spread", value: spread);
1112 add_float_row (store, name: "Radius", value: radius);
1113 }
1114 break;
1115
1116 case GSK_REPEAT_NODE:
1117 {
1118 const graphene_rect_t *child_bounds = gsk_repeat_node_get_child_bounds (node);
1119
1120 tmp = g_strdup_printf (format: "%.2f x %.2f + %.2f + %.2f",
1121 child_bounds->size.width,
1122 child_bounds->size.height,
1123 child_bounds->origin.x,
1124 child_bounds->origin.y);
1125 add_text_row (store, name: "Child Bounds", text: tmp);
1126 g_free (mem: tmp);
1127 }
1128 break;
1129
1130 case GSK_COLOR_MATRIX_NODE:
1131 {
1132 const graphene_matrix_t *matrix = gsk_color_matrix_node_get_color_matrix (node);
1133 const graphene_vec4_t *offset = gsk_color_matrix_node_get_color_offset (node);
1134
1135 tmp = g_strdup_printf (format: "% .2f % .2f % .2f % .2f\n"
1136 "% .2f % .2f % .2f % .2f\n"
1137 "% .2f % .2f % .2f % .2f\n"
1138 "% .2f % .2f % .2f % .2f",
1139 graphene_matrix_get_value (m: matrix, row: 0, col: 0),
1140 graphene_matrix_get_value (m: matrix, row: 0, col: 1),
1141 graphene_matrix_get_value (m: matrix, row: 0, col: 2),
1142 graphene_matrix_get_value (m: matrix, row: 0, col: 3),
1143 graphene_matrix_get_value (m: matrix, row: 1, col: 0),
1144 graphene_matrix_get_value (m: matrix, row: 1, col: 1),
1145 graphene_matrix_get_value (m: matrix, row: 1, col: 2),
1146 graphene_matrix_get_value (m: matrix, row: 1, col: 3),
1147 graphene_matrix_get_value (m: matrix, row: 2, col: 0),
1148 graphene_matrix_get_value (m: matrix, row: 2, col: 1),
1149 graphene_matrix_get_value (m: matrix, row: 2, col: 2),
1150 graphene_matrix_get_value (m: matrix, row: 2, col: 3),
1151 graphene_matrix_get_value (m: matrix, row: 3, col: 0),
1152 graphene_matrix_get_value (m: matrix, row: 3, col: 1),
1153 graphene_matrix_get_value (m: matrix, row: 3, col: 2),
1154 graphene_matrix_get_value (m: matrix, row: 3, col: 3));
1155 add_text_row (store, name: "Matrix", text: tmp);
1156 g_free (mem: tmp);
1157 tmp = g_strdup_printf (format: "%.2f %.2f %.2f %.2f",
1158 graphene_vec4_get_x (v: offset),
1159 graphene_vec4_get_y (v: offset),
1160 graphene_vec4_get_z (v: offset),
1161 graphene_vec4_get_w (v: offset));
1162 add_text_row (store, name: "Offset", text: tmp);
1163 g_free (mem: tmp);
1164 }
1165 break;
1166
1167 case GSK_CLIP_NODE:
1168 {
1169 const graphene_rect_t *clip = gsk_clip_node_get_clip (node);
1170 tmp = g_strdup_printf (format: "%.2f x %.2f + %.2f + %.2f",
1171 clip->size.width,
1172 clip->size.height,
1173 clip->origin.x,
1174 clip->origin.y);
1175 add_text_row (store, name: "Clip", text: tmp);
1176 g_free (mem: tmp);
1177 }
1178 break;
1179
1180 case GSK_ROUNDED_CLIP_NODE:
1181 {
1182 const GskRoundedRect *clip = gsk_rounded_clip_node_get_clip (node);
1183 tmp = g_strdup_printf (format: "%.2f x %.2f + %.2f + %.2f",
1184 clip->bounds.size.width,
1185 clip->bounds.size.height,
1186 clip->bounds.origin.x,
1187 clip->bounds.origin.y);
1188 add_text_row (store, name: "Clip", text: tmp);
1189 g_free (mem: tmp);
1190
1191 tmp = g_strdup_printf (format: "%.2f x %.2f", clip->corner[0].width, clip->corner[0].height);
1192 add_text_row (store, name: "Top Left Corner Size", text: tmp);
1193 g_free (mem: tmp);
1194
1195 tmp = g_strdup_printf (format: "%.2f x %.2f", clip->corner[1].width, clip->corner[1].height);
1196 add_text_row (store, name: "Top Right Corner Size", text: tmp);
1197 g_free (mem: tmp);
1198
1199 tmp = g_strdup_printf (format: "%.2f x %.2f", clip->corner[2].width, clip->corner[2].height);
1200 add_text_row (store, name: "Bottom Right Corner Size", text: tmp);
1201 g_free (mem: tmp);
1202
1203 tmp = g_strdup_printf (format: "%.2f x %.2f", clip->corner[3].width, clip->corner[3].height);
1204 add_text_row (store, name: "Bottom Left Corner Size", text: tmp);
1205 g_free (mem: tmp);
1206 }
1207 break;
1208
1209 case GSK_CONTAINER_NODE:
1210 tmp = g_strdup_printf (format: "%d", gsk_container_node_get_n_children (node));
1211 add_text_row (store, name: "Children", text: tmp);
1212 g_free (mem: tmp);
1213 break;
1214
1215 case GSK_DEBUG_NODE:
1216 add_text_row (store, name: "Message", text: gsk_debug_node_get_message (node));
1217 break;
1218
1219 case GSK_SHADOW_NODE:
1220 {
1221 int i;
1222
1223 for (i = 0; i < gsk_shadow_node_get_n_shadows (node); i++)
1224 {
1225 char *label;
1226 char *value;
1227 const GskShadow *shadow = gsk_shadow_node_get_shadow (node, i);
1228
1229 label = g_strdup_printf (format: "Color %d", i);
1230 add_color_row (store, name: label, color: &shadow->color);
1231 g_free (mem: label);
1232
1233 label = g_strdup_printf (format: "Offset %d", i);
1234 value = g_strdup_printf (format: "%.2f %.2f", shadow->dx, shadow->dy);
1235 add_text_row (store, name: label, text: value);
1236 g_free (mem: value);
1237 g_free (mem: label);
1238
1239 label = g_strdup_printf (format: "Radius %d", i);
1240 add_float_row (store, name: label, value: shadow->radius);
1241 g_free (mem: label);
1242 }
1243 }
1244 break;
1245
1246 case GSK_TRANSFORM_NODE:
1247 {
1248 static const char * category_names[] = {
1249 [GSK_TRANSFORM_CATEGORY_UNKNOWN] = "unknown",
1250 [GSK_TRANSFORM_CATEGORY_ANY] = "any",
1251 [GSK_TRANSFORM_CATEGORY_3D] = "3D",
1252 [GSK_TRANSFORM_CATEGORY_2D] = "2D",
1253 [GSK_TRANSFORM_CATEGORY_2D_AFFINE] = "2D affine",
1254 [GSK_TRANSFORM_CATEGORY_2D_TRANSLATE] = "2D translate",
1255 [GSK_TRANSFORM_CATEGORY_IDENTITY] = "identity"
1256 };
1257 GskTransform *transform;
1258 char *s;
1259
1260 transform = gsk_transform_node_get_transform (node);
1261 s = gsk_transform_to_string (self: transform);
1262 add_text_row (store, name: "Matrix", text: s);
1263 g_free (mem: s);
1264 add_text_row (store, name: "Category", text: category_names[gsk_transform_get_category (transform)]);
1265 }
1266 break;
1267
1268 case GSK_NOT_A_RENDER_NODE:
1269 default:
1270 break;
1271 }
1272}
1273
1274static const char *
1275event_type_name (GdkEventType type)
1276{
1277 const char *event_name[] = {
1278 "Delete",
1279 "Motion",
1280 "Button Press",
1281 "Button Release",
1282 "Key Press",
1283 "Key Release",
1284 "Enter",
1285 "Leave",
1286 "Focus",
1287 "Proximity In",
1288 "Proximity Out",
1289 "Drag Enter",
1290 "Drag Leave",
1291 "Drag Motion",
1292 "Drop Start",
1293 "Scroll",
1294 "Grab Broken",
1295 "Touch Begin",
1296 "Touch Update",
1297 "Touch End",
1298 "Touch Cancel",
1299 "Touchpad Swipe",
1300 "Touchpad Pinch",
1301 "Pad Button Press",
1302 "Pad Button Release",
1303 "Pad Rind",
1304 "Pad Strip",
1305 "Pad Group Mode"
1306 };
1307
1308 return event_name[type];
1309}
1310
1311static const char *
1312scroll_direction_name (GdkScrollDirection dir)
1313{
1314 const char *scroll_dir[] = {
1315 "Up", "Down", "Left", "Right", "Smooth"
1316 };
1317 return scroll_dir[dir];
1318}
1319
1320static char *
1321modifier_names (GdkModifierType state)
1322{
1323 struct {
1324 const char *name;
1325 int mask;
1326 } mods[] = {
1327 { "Shift", GDK_SHIFT_MASK },
1328 { "Lock", GDK_LOCK_MASK },
1329 { "Control", GDK_CONTROL_MASK },
1330 { "Alt", GDK_ALT_MASK },
1331 { "Button1", GDK_BUTTON1_MASK },
1332 { "Button2", GDK_BUTTON2_MASK },
1333 { "Button3", GDK_BUTTON3_MASK },
1334 { "Button4", GDK_BUTTON4_MASK },
1335 { "Button5", GDK_BUTTON5_MASK },
1336 { "Super", GDK_SUPER_MASK },
1337 { "Hyper", GDK_HYPER_MASK },
1338 { "Meta", GDK_META_MASK },
1339 };
1340 GString *s;
1341
1342 s = g_string_new (init: "");
1343
1344 for (int i = 0; i < G_N_ELEMENTS (mods); i++)
1345 {
1346 if (state & mods[i].mask)
1347 {
1348 if (s->len > 0)
1349 g_string_append (string: s, val: " ");
1350 g_string_append (string: s, val: mods[i].name);
1351 }
1352 }
1353
1354 return g_string_free (string: s, FALSE);
1355}
1356
1357static char *
1358key_event_string (GdkEvent *event)
1359{
1360 guint keyval;
1361 gunichar c;
1362 char buf[5] = { 0, };
1363
1364 keyval = gdk_key_event_get_keyval (event);
1365 c = gdk_keyval_to_unicode (keyval);
1366 if (c)
1367 {
1368 g_unichar_to_utf8 (c, outbuf: buf);
1369 return g_strdup (str: buf);
1370 }
1371
1372 return g_strdup (str: gdk_keyval_name (keyval));
1373}
1374
1375static const char *
1376device_tool_name (GdkDeviceTool *tool)
1377{
1378 const char *name[] = {
1379 "Unknown",
1380 "Pen",
1381 "Eraser",
1382 "Brush",
1383 "Pencil",
1384 "Airbrush",
1385 "Mouse",
1386 "Lens"
1387 };
1388
1389 return name[gdk_device_tool_get_tool_type (tool)];
1390}
1391
1392static const char *
1393axis_name (GdkAxisUse axis)
1394{
1395 const char *name[] = {
1396 "",
1397 "X",
1398 "Y",
1399 "Delta X",
1400 "Delta Y",
1401 "Pressure",
1402 "X Tilt",
1403 "Y Tilt",
1404 "Wheel",
1405 "Distance",
1406 "Rotation",
1407 "Slider"
1408 };
1409
1410 return name[axis];
1411}
1412
1413static const char *
1414gesture_phase_name (GdkTouchpadGesturePhase phase)
1415{
1416 const char *name[] = {
1417 "Begin",
1418 "Update",
1419 "End",
1420 "Cancel"
1421 };
1422
1423 return name[phase];
1424}
1425
1426static void
1427populate_event_properties (GtkListStore *store,
1428 GdkEvent *event)
1429{
1430 GdkEventType type;
1431 GdkDevice *device;
1432 GdkDeviceTool *tool;
1433 double x, y;
1434 double dx, dy;
1435 char *tmp;
1436 GdkModifierType state;
1437
1438 gtk_list_store_clear (list_store: store);
1439
1440 type = gdk_event_get_event_type (event);
1441
1442 add_text_row (store, name: "Type", text: event_type_name (type));
1443 if (gdk_event_get_event_sequence (event) != NULL)
1444 {
1445 tmp = g_strdup_printf (format: "%p", gdk_event_get_event_sequence (event));
1446 add_text_row (store, name: "Sequence", text: tmp);
1447 g_free (mem: tmp);
1448 }
1449 add_int_row (store, name: "Timestamp", value: gdk_event_get_time (event));
1450
1451 device = gdk_event_get_device (event);
1452 if (device)
1453 add_text_row (store, name: "Device", text: gdk_device_get_name (device));
1454
1455 tool = gdk_event_get_device_tool (event);
1456 if (tool)
1457 add_text_row (store, name: "Device Tool", text: device_tool_name (tool));
1458
1459 if (gdk_event_get_position (event, x: &x, y: &y))
1460 {
1461 tmp = g_strdup_printf (format: "%.2f %.2f", x, y);
1462 add_text_row (store, name: "Position", text: tmp);
1463 g_free (mem: tmp);
1464 }
1465
1466 if (tool)
1467 {
1468 GdkAxisFlags axes = gdk_device_tool_get_axes (tool);
1469
1470 /* We report position and scroll delta separately, so skip them here */
1471 axes &= ~(GDK_AXIS_FLAG_X|GDK_AXIS_FLAG_Y|GDK_AXIS_FLAG_DELTA_X|GDK_AXIS_FLAG_DELTA_Y);
1472
1473 for (int i = 1; i < GDK_AXIS_LAST; i++)
1474 {
1475 if (axes & (1 << i))
1476 {
1477 double val;
1478 gdk_event_get_axis (event, axis_use: i, value: &val);
1479 tmp = g_strdup_printf (format: "%.2f", val);
1480 add_text_row (store, name: axis_name (axis: i), text: tmp);
1481 g_free (mem: tmp);
1482 }
1483 }
1484 }
1485
1486 state = gdk_event_get_modifier_state (event);
1487 if (state != 0)
1488 {
1489 tmp = modifier_names (state);
1490 add_text_row (store, name: "State", text: tmp);
1491 g_free (mem: tmp);
1492 }
1493
1494 switch ((int)type)
1495 {
1496 case GDK_BUTTON_PRESS:
1497 case GDK_BUTTON_RELEASE:
1498 add_int_row (store, name: "Button", value: gdk_button_event_get_button (event));
1499 break;
1500
1501 case GDK_KEY_PRESS:
1502 case GDK_KEY_RELEASE:
1503 add_int_row (store, name: "Keycode", value: gdk_key_event_get_keycode (event));
1504 add_int_row (store, name: "Keyval", value: gdk_key_event_get_keyval (event));
1505 tmp = key_event_string (event);
1506 add_text_row (store, name: "Key", text: tmp);
1507 g_free (mem: tmp);
1508 add_int_row (store, name: "Layout", value: gdk_key_event_get_layout (event));
1509 add_int_row (store, name: "Level", value: gdk_key_event_get_level (event));
1510 add_boolean_row (store, name: "Is Modifier", value: gdk_key_event_is_modifier (event));
1511 break;
1512
1513 case GDK_SCROLL:
1514 if (gdk_scroll_event_get_direction (event) == GDK_SCROLL_SMOOTH)
1515 {
1516 gdk_scroll_event_get_deltas (event, delta_x: &x, delta_y: &y);
1517 tmp = g_strdup_printf (format: "%.2f %.2f", x, y);
1518 add_text_row (store, name: "Delta", text: tmp);
1519 g_free (mem: tmp);
1520 }
1521 else
1522 {
1523 add_text_row (store, name: "Direction", text: scroll_direction_name (dir: gdk_scroll_event_get_direction (event)));
1524 }
1525 add_boolean_row (store, name: "Is Stop", value: gdk_scroll_event_is_stop (event));
1526 break;
1527
1528 case GDK_FOCUS_CHANGE:
1529 add_text_row (store, name: "Direction", text: gdk_focus_event_get_in (event) ? "In" : "Out");
1530 break;
1531
1532 case GDK_ENTER_NOTIFY:
1533 case GDK_LEAVE_NOTIFY:
1534 add_int_row (store, name: "Mode", value: gdk_crossing_event_get_mode (event));
1535 add_int_row (store, name: "Detail", value: gdk_crossing_event_get_detail (event));
1536 add_boolean_row (store, name: "Is Focus", value: gdk_crossing_event_get_focus (event));
1537 break;
1538
1539 case GDK_GRAB_BROKEN:
1540 add_boolean_row (store, name: "Implicit", value: gdk_grab_broken_event_get_implicit (event));
1541 break;
1542
1543 case GDK_TOUCHPAD_SWIPE:
1544 case GDK_TOUCHPAD_PINCH:
1545 add_text_row (store, name: "Phase", text: gesture_phase_name (phase: gdk_touchpad_event_get_gesture_phase (event)));
1546 add_int_row (store, name: "Fingers", value: gdk_touchpad_event_get_n_fingers (event));
1547 gdk_touchpad_event_get_deltas (event, dx: &dx, dy: &dy);
1548 tmp = g_strdup_printf (format: "%.2f %.f2", dx, dy);
1549 add_text_row (store, name: "Delta", text: tmp);
1550 g_free (mem: tmp);
1551 if (type == GDK_TOUCHPAD_PINCH)
1552 {
1553 tmp = g_strdup_printf (format: "%.2f", gdk_touchpad_event_get_pinch_angle_delta (event));
1554 add_text_row (store, name: "Angle Delta", text: tmp);
1555 g_free (mem: tmp);
1556 tmp = g_strdup_printf (format: "%.2f", gdk_touchpad_event_get_pinch_scale (event));
1557 add_text_row (store, name: "Scale", text: tmp);
1558 g_free (mem: tmp);
1559 }
1560 break;
1561
1562 default:
1563 /* FIXME */
1564 ;
1565 }
1566
1567 if (type == GDK_MOTION_NOTIFY || type == GDK_SCROLL)
1568 {
1569 GdkTimeCoord *history;
1570 guint n_coords;
1571
1572 history = gdk_event_get_history (event, out_n_coords: &n_coords);
1573 if (history)
1574 {
1575 GString *s = g_string_new (init: "");
1576
1577 for (int i = 0; i < n_coords; i++)
1578 {
1579 if (i > 0)
1580 g_string_append (string: s, val: "\n");
1581
1582 g_string_append_printf (string: s, format: "%d", history[i].time);
1583
1584 if (history[i].flags & (GDK_AXIS_FLAG_X|GDK_AXIS_FLAG_Y))
1585 g_string_append_printf (string: s, format: " Position %.2f %.2f", history[i].axes[GDK_AXIS_X], history[i].axes[GDK_AXIS_Y]);
1586
1587 if (history[i].flags & (GDK_AXIS_FLAG_DELTA_X|GDK_AXIS_FLAG_DELTA_Y))
1588 g_string_append_printf (string: s, format: " Delta %.2f %.2f", history[i].axes[GDK_AXIS_DELTA_X], history[i].axes[GDK_AXIS_DELTA_Y]);
1589
1590 for (int j = GDK_AXIS_PRESSURE; j < GDK_AXIS_LAST; j++)
1591 {
1592 if (history[i].flags & (1 << j))
1593 g_string_append_printf (string: s, format: " %s %.2f", axis_name (axis: j), history[i].axes[j]);
1594 }
1595 }
1596
1597 add_text_row (store, name: "History", text: s->str);
1598
1599 g_string_free (string: s, TRUE);
1600 g_free (mem: history);
1601 }
1602 }
1603}
1604
1605static GskRenderNode *
1606get_selected_node (GtkInspectorRecorder *recorder)
1607{
1608 GtkTreeListRow *row_item;
1609 GdkPaintable *paintable;
1610 GskRenderNode *node;
1611
1612 row_item = gtk_single_selection_get_selected_item (self: recorder->render_node_selection);
1613 if (row_item == NULL)
1614 return NULL;
1615
1616 paintable = gtk_tree_list_row_get_item (self: row_item);
1617 node = gtk_render_node_paintable_get_render_node (self: GTK_RENDER_NODE_PAINTABLE (ptr: paintable));
1618 g_object_unref (object: paintable);
1619
1620 return node;
1621}
1622
1623static void
1624render_node_list_selection_changed (GtkListBox *list,
1625 GtkListBoxRow *row,
1626 GtkInspectorRecorder *recorder)
1627{
1628 GskRenderNode *node;
1629 GdkPaintable *paintable;
1630 GtkTreeListRow *row_item;
1631
1632 row_item = gtk_single_selection_get_selected_item (self: recorder->render_node_selection);
1633
1634 gtk_widget_set_sensitive (widget: recorder->render_node_save_button, sensitive: row_item != NULL);
1635 gtk_widget_set_sensitive (widget: recorder->render_node_clip_button, sensitive: row_item != NULL);
1636
1637 if (row_item == NULL)
1638 return;
1639
1640 paintable = gtk_tree_list_row_get_item (self: row_item);
1641
1642 gtk_picture_set_paintable (self: GTK_PICTURE (ptr: recorder->render_node_view), paintable);
1643 node = gtk_render_node_paintable_get_render_node (self: GTK_RENDER_NODE_PAINTABLE (ptr: paintable));
1644 populate_render_node_properties (GTK_LIST_STORE (recorder->render_node_properties), node);
1645
1646 g_object_unref (object: paintable);
1647}
1648
1649static void
1650render_node_save_response (GtkWidget *dialog,
1651 int response,
1652 GskRenderNode *node)
1653{
1654 gtk_widget_hide (widget: dialog);
1655
1656 if (response == GTK_RESPONSE_ACCEPT)
1657 {
1658 GBytes *bytes = gsk_render_node_serialize (node);
1659 GError *error = NULL;
1660
1661 if (!g_file_replace_contents (file: gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)),
1662 contents: g_bytes_get_data (bytes, NULL),
1663 length: g_bytes_get_size (bytes),
1664 NULL,
1665 FALSE,
1666 flags: 0,
1667 NULL,
1668 NULL,
1669 error: &error))
1670 {
1671 GtkWidget *message_dialog;
1672
1673 message_dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_window_get_transient_for (GTK_WINDOW (dialog))),
1674 flags: GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
1675 type: GTK_MESSAGE_INFO,
1676 buttons: GTK_BUTTONS_OK,
1677 _("Saving RenderNode failed"));
1678 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (message_dialog),
1679 message_format: "%s", error->message);
1680 g_signal_connect (message_dialog, "response", G_CALLBACK (gtk_window_destroy), NULL);
1681 gtk_widget_show (widget: message_dialog);
1682 g_error_free (error);
1683 }
1684
1685 g_bytes_unref (bytes);
1686 }
1687
1688 gtk_window_destroy (GTK_WINDOW (dialog));
1689}
1690
1691static void
1692render_node_save (GtkButton *button,
1693 GtkInspectorRecorder *recorder)
1694{
1695 GskRenderNode *node;
1696 GtkWidget *dialog;
1697 char *filename, *nodename;
1698
1699 node = get_selected_node (recorder);
1700 if (node == NULL)
1701 return;
1702
1703 dialog = gtk_file_chooser_dialog_new (title: "",
1704 GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (recorder))),
1705 action: GTK_FILE_CHOOSER_ACTION_SAVE,
1706 _("_Cancel"), GTK_RESPONSE_CANCEL,
1707 _("_Save"), GTK_RESPONSE_ACCEPT,
1708 NULL);
1709 nodename = node_name (node);
1710 filename = g_strdup_printf (format: "%s.node", nodename);
1711 g_free (mem: nodename);
1712 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), name: filename);
1713 g_free (mem: filename);
1714 gtk_dialog_set_default_response (GTK_DIALOG (dialog), response_id: GTK_RESPONSE_ACCEPT);
1715 gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
1716 g_signal_connect (dialog, "response", G_CALLBACK (render_node_save_response), node);
1717 gtk_widget_show (widget: dialog);
1718}
1719
1720static void
1721render_node_clip (GtkButton *button,
1722 GtkInspectorRecorder *recorder)
1723{
1724 GskRenderNode *node;
1725 GdkClipboard *clipboard;
1726 GBytes *bytes;
1727 GdkContentProvider *content;
1728
1729 node = get_selected_node (recorder);
1730 if (node == NULL)
1731 return;
1732
1733 bytes = gsk_render_node_serialize (node);
1734 content = gdk_content_provider_new_for_bytes (mime_type: "text/plain;charset=utf-8", bytes);
1735
1736 clipboard = gtk_widget_get_clipboard (GTK_WIDGET (recorder));
1737
1738 gdk_clipboard_set_content (clipboard, provider: content);
1739
1740 g_object_unref (object: content);
1741 g_bytes_unref (bytes);
1742}
1743
1744static void
1745toggle_dark_mode (GtkToggleButton *button,
1746 GParamSpec *pspec,
1747 gpointer data)
1748{
1749 GtkWidget *picture = data;
1750
1751 if (gtk_toggle_button_get_active (toggle_button: button))
1752 {
1753 gtk_widget_add_css_class (widget: picture, css_class: "dark");
1754 gtk_widget_remove_css_class (widget: picture, css_class: "light");
1755 }
1756 else
1757 {
1758 gtk_widget_remove_css_class (widget: picture, css_class: "dark");
1759 gtk_widget_add_css_class (widget: picture, css_class: "light");
1760 }
1761}
1762
1763static void
1764setup_widget_for_recording (GtkListItemFactory *factory,
1765 GtkListItem *item,
1766 gpointer data)
1767{
1768 GtkWidget *row, *box, *label;
1769
1770 row = g_object_new (GTK_TYPE_INSPECTOR_RECORDER_ROW, NULL);
1771
1772 box = gtk_box_new (orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 6);
1773 label = gtk_label_new (str: "");
1774 gtk_label_set_xalign (GTK_LABEL (label), xalign: 0.0f);
1775 gtk_widget_set_hexpand (widget: label, TRUE);
1776 gtk_box_append (GTK_BOX (box), child: label);
1777
1778 label = gtk_label_new (str: "");
1779 gtk_box_append (GTK_BOX (box), child: label);
1780
1781 gtk_widget_set_margin_start (widget: box, margin: 6);
1782 gtk_widget_set_margin_end (widget: box, margin: 6);
1783 gtk_widget_set_margin_top (widget: box, margin: 6);
1784 gtk_widget_set_margin_bottom (widget: box, margin: 6);
1785
1786 gtk_widget_set_parent (widget: box, parent: row);
1787
1788 gtk_list_item_set_child (self: item, child: row);
1789}
1790
1791static char *
1792get_event_summary (GdkEvent *event)
1793{
1794 double x, y;
1795 GdkEventType type;
1796 const char *name;
1797
1798 gdk_event_get_position (event, x: &x, y: &y);
1799 type = gdk_event_get_event_type (event);
1800 name = event_type_name (type);
1801
1802 switch (type)
1803 {
1804 case GDK_ENTER_NOTIFY:
1805 case GDK_LEAVE_NOTIFY:
1806 case GDK_MOTION_NOTIFY:
1807 case GDK_DRAG_ENTER:
1808 case GDK_DRAG_LEAVE:
1809 case GDK_DRAG_MOTION:
1810 case GDK_DROP_START:
1811 case GDK_TOUCH_BEGIN:
1812 case GDK_TOUCH_UPDATE:
1813 case GDK_TOUCH_END:
1814 case GDK_TOUCH_CANCEL:
1815 case GDK_TOUCHPAD_SWIPE:
1816 case GDK_TOUCHPAD_PINCH:
1817 case GDK_TOUCHPAD_HOLD:
1818 case GDK_BUTTON_PRESS:
1819 case GDK_BUTTON_RELEASE:
1820 return g_strdup_printf (format: "%s (%.2f %.2f)", name, x, y);
1821
1822 case GDK_KEY_PRESS:
1823 case GDK_KEY_RELEASE:
1824 {
1825 char *tmp, *ret;
1826 tmp = key_event_string (event);
1827 ret = g_strdup_printf (format: "%s %s\n", name, tmp);
1828 g_free (mem: tmp);
1829 return ret;
1830 }
1831
1832 case GDK_FOCUS_CHANGE:
1833 return g_strdup_printf (format: "%s %s", name, gdk_focus_event_get_in (event) ? "In" : "Out");
1834
1835 case GDK_GRAB_BROKEN:
1836 case GDK_PROXIMITY_IN:
1837 case GDK_PROXIMITY_OUT:
1838 case GDK_PAD_BUTTON_PRESS:
1839 case GDK_PAD_BUTTON_RELEASE:
1840 case GDK_PAD_RING:
1841 case GDK_PAD_STRIP:
1842 case GDK_PAD_GROUP_MODE:
1843 case GDK_DELETE:
1844 return g_strdup_printf (format: "%s", name);
1845
1846 case GDK_SCROLL:
1847 if (gdk_scroll_event_get_direction (event) == GDK_SCROLL_SMOOTH)
1848 {
1849 gdk_scroll_event_get_deltas (event, delta_x: &x, delta_y: &y);
1850 return g_strdup_printf (format: "%s %.2f %.2f", name, x, y);
1851 }
1852 else
1853 {
1854 return g_strdup_printf (format: "%s %s", name, scroll_direction_name (dir: gdk_scroll_event_get_direction (event)));
1855 }
1856 break;
1857
1858 case GDK_EVENT_LAST:
1859 default:
1860 g_assert_not_reached ();
1861 }
1862}
1863
1864static void
1865bind_widget_for_recording (GtkListItemFactory *factory,
1866 GtkListItem *item,
1867 gpointer data)
1868{
1869 GtkInspectorRecorder *recorder = GTK_INSPECTOR_RECORDER (data);
1870 GtkInspectorRecording *recording = gtk_list_item_get_item (self: item);
1871 GtkWidget *row, *box, *label, *label2;
1872 char *text;
1873
1874 row = gtk_list_item_get_child (self: item);
1875 box = gtk_widget_get_first_child (widget: row);
1876 label = gtk_widget_get_first_child (widget: box);
1877 label2 = gtk_widget_get_next_sibling (widget: label);
1878
1879 g_object_set (object: row, first_property_name: "sequence", NULL, NULL);
1880 g_object_bind_property (source: recorder, source_property: "selected-sequence", target: row, target_property: "match-sequence", flags: G_BINDING_SYNC_CREATE);
1881
1882 gtk_label_set_use_markup (GTK_LABEL (label), FALSE);
1883
1884 if (GTK_INSPECTOR_IS_RENDER_RECORDING (recording))
1885 {
1886 gtk_label_set_label (GTK_LABEL (label), str: "Frame");
1887 gtk_label_set_use_markup (GTK_LABEL (label), FALSE);
1888
1889 text = g_strdup_printf (format: "%.3f", gtk_inspector_recording_get_timestamp (recording) / 1000.0);
1890 gtk_label_set_label (GTK_LABEL (label2), str: text);
1891 g_free (mem: text);
1892 }
1893 else if (GTK_INSPECTOR_IS_EVENT_RECORDING (recording))
1894 {
1895 GdkEvent *event = gtk_inspector_event_recording_get_event (GTK_INSPECTOR_EVENT_RECORDING (recording));
1896
1897 g_object_set (object: row, first_property_name: "sequence", gdk_event_get_event_sequence (event), NULL);
1898
1899 text = get_event_summary (event);
1900 gtk_label_set_label (GTK_LABEL (label), str: text);
1901 g_free (mem: text);
1902
1903 text = g_strdup_printf (format: "%.3f", gtk_inspector_recording_get_timestamp (recording) / 1000.0);
1904 gtk_label_set_label (GTK_LABEL (label2), str: text);
1905 g_free (mem: text);
1906 }
1907 else
1908 {
1909 gtk_label_set_label (GTK_LABEL (label), str: "<b>Start of Recording</b>");
1910 gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
1911 gtk_label_set_label (GTK_LABEL (label2), str: "");
1912 }
1913}
1914
1915static void
1916node_property_activated (GtkTreeView *tv,
1917 GtkTreePath *path,
1918 GtkTreeViewColumn *col,
1919 GtkInspectorRecorder *recorder)
1920{
1921 GtkTreeIter iter;
1922 GdkRectangle rect;
1923 GdkTexture *texture;
1924 gboolean visible;
1925 GtkWidget *popover;
1926 GtkWidget *image;
1927
1928 gtk_tree_model_get_iter (GTK_TREE_MODEL (recorder->render_node_properties), iter: &iter, path);
1929 gtk_tree_model_get (GTK_TREE_MODEL (recorder->render_node_properties), iter: &iter,
1930 2, &visible,
1931 3, &texture,
1932 -1);
1933 gtk_tree_view_get_cell_area (tree_view: tv, path, column: col, rect: &rect);
1934 gtk_tree_view_convert_bin_window_to_widget_coords (tree_view: tv, bx: rect.x, by: rect.y, wx: &rect.x, wy: &rect.y);
1935
1936 if (texture == NULL || visible)
1937 return;
1938
1939 popover = gtk_popover_new ();
1940 gtk_widget_set_parent (widget: popover, GTK_WIDGET (tv));
1941 gtk_popover_set_pointing_to (GTK_POPOVER (popover), rect: &rect);
1942
1943 image = gtk_image_new_from_paintable (paintable: GDK_PAINTABLE (ptr: texture));
1944 gtk_widget_set_margin_start (widget: image, margin: 20);
1945 gtk_widget_set_margin_end (widget: image, margin: 20);
1946 gtk_widget_set_margin_top (widget: image, margin: 20);
1947 gtk_widget_set_margin_bottom (widget: image, margin: 20);
1948 gtk_popover_set_child (GTK_POPOVER (popover), child: image);
1949 gtk_popover_popup (GTK_POPOVER (popover));
1950
1951 g_signal_connect (popover, "unmap", G_CALLBACK (gtk_widget_unparent), NULL);
1952
1953 g_object_unref (object: texture);
1954}
1955
1956static void
1957gtk_inspector_recorder_get_property (GObject *object,
1958 guint param_id,
1959 GValue *value,
1960 GParamSpec *pspec)
1961{
1962 GtkInspectorRecorder *recorder = GTK_INSPECTOR_RECORDER (object);
1963
1964 switch (param_id)
1965 {
1966 case PROP_RECORDING:
1967 g_value_set_boolean (value, v_boolean: recorder->recording != NULL);
1968 break;
1969
1970 case PROP_DEBUG_NODES:
1971 g_value_set_boolean (value, v_boolean: recorder->debug_nodes);
1972 break;
1973
1974 case PROP_HIGHLIGHT_SEQUENCES:
1975 g_value_set_boolean (value, v_boolean: recorder->highlight_sequences);
1976 break;
1977
1978 case PROP_SELECTED_SEQUENCE:
1979 g_value_set_pointer (value, v_pointer: recorder->selected_sequence);
1980 break;
1981
1982 default:
1983 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
1984 break;
1985 }
1986}
1987
1988static void
1989gtk_inspector_recorder_set_property (GObject *object,
1990 guint param_id,
1991 const GValue *value,
1992 GParamSpec *pspec)
1993{
1994 GtkInspectorRecorder *recorder = GTK_INSPECTOR_RECORDER (object);
1995
1996 switch (param_id)
1997 {
1998 case PROP_RECORDING:
1999 gtk_inspector_recorder_set_recording (recorder, record: g_value_get_boolean (value));
2000 break;
2001
2002 case PROP_DEBUG_NODES:
2003 gtk_inspector_recorder_set_debug_nodes (recorder, debug_nodes: g_value_get_boolean (value));
2004 break;
2005
2006 case PROP_HIGHLIGHT_SEQUENCES:
2007 gtk_inspector_recorder_set_highlight_sequences (recorder, highlight_sequences: g_value_get_boolean (value));
2008 break;
2009
2010 case PROP_SELECTED_SEQUENCE:
2011 recorder->selected_sequence = g_value_get_pointer (value);
2012 break;
2013
2014 default:
2015 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
2016 break;
2017 }
2018}
2019
2020static void
2021gtk_inspector_recorder_dispose (GObject *object)
2022{
2023 GtkInspectorRecorder *recorder = GTK_INSPECTOR_RECORDER (object);
2024
2025 g_clear_pointer (&recorder->box, gtk_widget_unparent);
2026 g_clear_object (&recorder->render_node_model);
2027 g_clear_object (&recorder->render_node_root_model);
2028 g_clear_object (&recorder->render_node_selection);
2029
2030 G_OBJECT_CLASS (gtk_inspector_recorder_parent_class)->dispose (object);
2031}
2032
2033static void
2034gtk_inspector_recorder_class_init (GtkInspectorRecorderClass *klass)
2035{
2036 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2037 GObjectClass *object_class = G_OBJECT_CLASS (klass);
2038
2039 object_class->get_property = gtk_inspector_recorder_get_property;
2040 object_class->set_property = gtk_inspector_recorder_set_property;
2041 object_class->dispose = gtk_inspector_recorder_dispose;
2042
2043 props[PROP_RECORDING] =
2044 g_param_spec_boolean (name: "recording",
2045 nick: "Recording",
2046 blurb: "Whether the recorder is currently recording",
2047 FALSE,
2048 flags: G_PARAM_READWRITE);
2049 props[PROP_DEBUG_NODES] =
2050 g_param_spec_boolean (name: "debug-nodes",
2051 nick: "Debug nodes",
2052 blurb: "Whether to insert extra debug nodes in the tree",
2053 FALSE,
2054 flags: G_PARAM_READWRITE);
2055
2056 props[PROP_HIGHLIGHT_SEQUENCES] = g_param_spec_boolean (name: "highlight-sequences", nick: "", blurb: "", FALSE, flags: G_PARAM_READWRITE);
2057 props[PROP_SELECTED_SEQUENCE] = g_param_spec_pointer (name: "selected-sequence", nick: "", blurb: "", flags: G_PARAM_READWRITE);
2058
2059 g_object_class_install_properties (oclass: object_class, n_pspecs: LAST_PROP, pspecs: props);
2060
2061 gtk_widget_class_set_template_from_resource (widget_class, resource_name: "/org/gtk/libgtk/inspector/recorder.ui");
2062
2063 gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, box);
2064 gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, recordings);
2065 gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, recordings_list);
2066 gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, render_node_view);
2067 gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, render_node_list);
2068 gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, render_node_save_button);
2069 gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, render_node_clip_button);
2070 gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, node_property_tree);
2071 gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, recording_data_stack);
2072 gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, event_view);
2073 gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, event_property_tree);
2074
2075 gtk_widget_class_bind_template_callback (widget_class, recordings_clear_all);
2076 gtk_widget_class_bind_template_callback (widget_class, recording_selected);
2077 gtk_widget_class_bind_template_callback (widget_class, render_node_save);
2078 gtk_widget_class_bind_template_callback (widget_class, render_node_clip);
2079 gtk_widget_class_bind_template_callback (widget_class, node_property_activated);
2080 gtk_widget_class_bind_template_callback (widget_class, toggle_dark_mode);
2081
2082 gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
2083}
2084
2085static void
2086gtk_inspector_recorder_init (GtkInspectorRecorder *recorder)
2087{
2088 GtkListItemFactory *factory;
2089
2090 gtk_widget_init_template (GTK_WIDGET (recorder));
2091
2092 factory = gtk_signal_list_item_factory_new ();
2093 g_signal_connect (factory, "setup", G_CALLBACK (setup_widget_for_recording), recorder);
2094 g_signal_connect (factory, "bind", G_CALLBACK (bind_widget_for_recording), recorder);
2095 gtk_list_view_set_factory (GTK_LIST_VIEW (recorder->recordings_list), factory);
2096 g_object_unref (object: factory);
2097
2098 recorder->render_node_root_model = g_list_store_new (GDK_TYPE_PAINTABLE);
2099 recorder->render_node_model = gtk_tree_list_model_new (g_object_ref (G_LIST_MODEL (recorder->render_node_root_model)),
2100 FALSE,
2101 TRUE,
2102 create_func: create_list_model_for_render_node_paintable,
2103 NULL, NULL);
2104 recorder->render_node_selection = gtk_single_selection_new (g_object_ref (G_LIST_MODEL (recorder->render_node_model)));
2105 g_signal_connect (recorder->render_node_selection, "notify::selected-item", G_CALLBACK (render_node_list_selection_changed), recorder);
2106
2107 factory = gtk_signal_list_item_factory_new ();
2108 g_signal_connect (factory, "setup", G_CALLBACK (setup_widget_for_render_node), NULL);
2109 g_signal_connect (factory, "bind", G_CALLBACK (bind_widget_for_render_node), NULL);
2110
2111 gtk_list_view_set_factory (GTK_LIST_VIEW (recorder->render_node_list), factory);
2112 g_object_unref (object: factory);
2113 gtk_list_view_set_model (GTK_LIST_VIEW (recorder->render_node_list),
2114 model: GTK_SELECTION_MODEL (ptr: recorder->render_node_selection));
2115
2116 recorder->render_node_properties = GTK_TREE_MODEL (gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, GDK_TYPE_TEXTURE));
2117 gtk_tree_view_set_model (GTK_TREE_VIEW (recorder->node_property_tree), model: recorder->render_node_properties);
2118 g_object_unref (object: recorder->render_node_properties);
2119
2120 recorder->event_properties = GTK_TREE_MODEL (gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, GDK_TYPE_TEXTURE));
2121 gtk_tree_view_set_model (GTK_TREE_VIEW (recorder->event_property_tree), model: recorder->event_properties);
2122 g_object_unref (object: recorder->event_properties);
2123}
2124
2125static void
2126gtk_inspector_recorder_add_recording (GtkInspectorRecorder *recorder,
2127 GtkInspectorRecording *recording)
2128{
2129 g_list_store_append (store: G_LIST_STORE (ptr: recorder->recordings), item: recording);
2130}
2131
2132void
2133gtk_inspector_recorder_set_recording (GtkInspectorRecorder *recorder,
2134 gboolean recording)
2135{
2136 if (gtk_inspector_recorder_is_recording (recorder) == recording)
2137 return;
2138
2139 if (recording)
2140 {
2141 recorder->recording = gtk_inspector_start_recording_new ();
2142 recorder->start_time = 0;
2143 gtk_inspector_recorder_add_recording (recorder, recording: recorder->recording);
2144 }
2145 else
2146 {
2147 g_clear_object (&recorder->recording);
2148 }
2149
2150 g_object_notify_by_pspec (G_OBJECT (recorder), pspec: props[PROP_RECORDING]);
2151}
2152
2153gboolean
2154gtk_inspector_recorder_is_recording (GtkInspectorRecorder *recorder)
2155{
2156 return recorder->recording != NULL;
2157}
2158
2159void
2160gtk_inspector_recorder_record_render (GtkInspectorRecorder *recorder,
2161 GtkWidget *widget,
2162 GskRenderer *renderer,
2163 GdkSurface *surface,
2164 const cairo_region_t *region,
2165 GskRenderNode *node)
2166{
2167 GtkInspectorRecording *recording;
2168 GdkFrameClock *frame_clock;
2169 gint64 frame_time;
2170
2171 if (!gtk_inspector_recorder_is_recording (recorder))
2172 return;
2173
2174 frame_clock = gtk_widget_get_frame_clock (widget);
2175 frame_time = gdk_frame_clock_get_frame_time (frame_clock);
2176
2177 if (recorder->start_time == 0)
2178 {
2179 recorder->start_time = frame_time;
2180 frame_time = 0;
2181 }
2182 else
2183 {
2184 frame_time = frame_time - recorder->start_time;
2185 }
2186
2187 recording = gtk_inspector_render_recording_new (timestamp: frame_time,
2188 profiler: gsk_renderer_get_profiler (renderer),
2189 area: &(GdkRectangle) { 0, 0,
2190 gdk_surface_get_width (surface),
2191 gdk_surface_get_height (surface) },
2192 clip_region: region,
2193 node);
2194 gtk_inspector_recorder_add_recording (recorder, recording);
2195 g_object_unref (object: recording);
2196}
2197
2198void
2199gtk_inspector_recorder_record_event (GtkInspectorRecorder *recorder,
2200 GtkWidget *widget,
2201 GdkEvent *event)
2202{
2203 GtkInspectorRecording *recording;
2204 GdkFrameClock *frame_clock;
2205 gint64 frame_time;
2206
2207 if (!gtk_inspector_recorder_is_recording (recorder))
2208 return;
2209
2210 frame_clock = gtk_widget_get_frame_clock (widget);
2211 frame_time = gdk_frame_clock_get_frame_time (frame_clock);
2212
2213 if (recorder->start_time == 0)
2214 {
2215 recorder->start_time = frame_time;
2216 frame_time = 0;
2217 }
2218 else
2219 {
2220 frame_time = frame_time - recorder->start_time;
2221 }
2222
2223 recording = gtk_inspector_event_recording_new (timestamp: frame_time, event);
2224 gtk_inspector_recorder_add_recording (recorder, recording);
2225 g_object_unref (object: recording);
2226}
2227
2228void
2229gtk_inspector_recorder_set_debug_nodes (GtkInspectorRecorder *recorder,
2230 gboolean debug_nodes)
2231{
2232 guint flags;
2233
2234 if (recorder->debug_nodes == debug_nodes)
2235 return;
2236
2237 recorder->debug_nodes = debug_nodes;
2238
2239 flags = gtk_get_debug_flags ();
2240
2241 if (debug_nodes)
2242 flags |= GTK_DEBUG_SNAPSHOT;
2243 else
2244 flags &= ~GTK_DEBUG_SNAPSHOT;
2245
2246 gtk_set_debug_flags (flags);
2247
2248 g_object_notify_by_pspec (G_OBJECT (recorder), pspec: props[PROP_DEBUG_NODES]);
2249}
2250
2251void
2252gtk_inspector_recorder_set_highlight_sequences (GtkInspectorRecorder *recorder,
2253 gboolean highlight_sequences)
2254{
2255 GdkEventSequence *sequence = NULL;
2256
2257 if (recorder->highlight_sequences == highlight_sequences)
2258 return;
2259
2260 recorder->highlight_sequences = highlight_sequences;
2261
2262 if (highlight_sequences)
2263 {
2264 GtkSingleSelection *selection;
2265 GtkInspectorRecording *recording;
2266 GdkEvent *event;
2267
2268 selection = GTK_SINGLE_SELECTION (ptr: gtk_list_view_get_model (GTK_LIST_VIEW (recorder->recordings_list)));
2269 recording = gtk_single_selection_get_selected_item (self: selection);
2270
2271 if (GTK_INSPECTOR_IS_EVENT_RECORDING (recording))
2272 {
2273 event = gtk_inspector_event_recording_get_event (GTK_INSPECTOR_EVENT_RECORDING (recording));
2274 sequence = gdk_event_get_event_sequence (event);
2275 }
2276 }
2277
2278 gtk_inspector_recorder_set_selected_sequence (recorder, sequence);
2279
2280 g_object_notify_by_pspec (G_OBJECT (recorder), pspec: props[PROP_HIGHLIGHT_SEQUENCES]);
2281}
2282
2283void
2284gtk_inspector_recorder_set_selected_sequence (GtkInspectorRecorder *recorder,
2285 GdkEventSequence *sequence)
2286{
2287 if (recorder->selected_sequence == sequence)
2288 return;
2289
2290 recorder->selected_sequence = sequence;
2291
2292 g_object_notify_by_pspec (G_OBJECT (recorder), pspec: props[PROP_SELECTED_SEQUENCE]);
2293}
2294
2295
2296

source code of gtk/gtk/inspector/recorder.c