/*
* Copyright ( c ) 2016 Red Hat , Inc .
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2 of the License , or ( at your option ) any later version .
*
* This library is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library . If not , see < http : //www.gnu.org/licenses/>.
*/
#include "config.h"
#include "recorder.h"
#include <gtk/gtkbinlayout.h>
#include <gtk/gtkbox.h>
#include <gtk/gtkdragsource.h>
#include <gtk/gtkeventcontroller.h>
#include <gtk/gtkfiledialog.h>
#include <gtk/gtkinscription.h>
#include <gtk/gtkimage.h>
#include <gtk/gtklabel.h>
#include <gtk/gtklistbox.h>
#include <gtk/gtklistitem.h>
#include <gtk/gtklistview.h>
#include <gtk/gtkalertdialog.h>
#include <gtk/gtkpicture.h>
#include <gtk/gtkpopover.h>
#include <gtk/gtksignallistitemfactory.h>
#include <gtk/gtksingleselection.h>
#include <gtk/gtktogglebutton.h>
#include <gtk/gtktreeexpander.h>
#include <gtk/gtktreelistmodel.h>
#include <gtk/gtkstack.h>
#include <gtk/gtknoselection.h>
#include <gtk/gtkcolumnview.h>
#include <gtk/gtkcolumnviewcolumn.h>
#include <gtk/gtknative.h>
#include <gtk/gtkprivate.h>
#include <gsk/gskarithmeticnodeprivate.h>
#include <gsk/gskblendnodeprivate.h>
#include <gsk/gskbordernodeprivate.h>
#include <gsk/gskcolormatrixnodeprivate.h>
#include <gsk/gskcolornodeprivate.h>
#include <gsk/gskcomponenttransfernodeprivate.h>
#include <gsk/gskcopypasteutilsprivate.h>
#include <gsk/gskdisplacementnodeprivate.h>
#include <gsk/gskinsetshadownodeprivate.h>
#include <gsk/gskoutsetshadownodeprivate.h>
#include <gsk/gskrendererprivate.h>
#include <gsk/gskrendernodeprivate.h>
#include <gsk/gskroundedrectprivate.h>
#include <gsk/gskshadownodeprivate.h>
#include <gsk/gsktextnodeprivate.h>
#include <gsk/gsktransformprivate.h>
#include <gsk/gskturbulencenodeprivate.h>
#include <gsk/gskcomponenttransferprivate.h>
#include <gsk/gskdisplacementnodeprivate.h>
#include <cairo-gobject.h>
#include <glib/gi18n-lib.h>
#include <gdk/gdkdmabufprivate.h>
#include <gdk/gdkdmabuftextureprivate.h>
#include <gdk/gdkgltextureprivate.h>
#include <gdk/gdkmemorytextureprivate.h>
#include <gdk/gdksubsurfaceprivate.h>
#include <gdk/gdksurfaceprivate.h>
#include <gdk/gdktextureprivate.h>
#include <gdk/gdkrgbaprivate.h>
#include <gdk/gdkcolorstateprivate.h>
#include "gtk/gtkbuiltiniconprivate.h"
#include "gtk/gtkdebug.h"
#include "gtk/gtkpopcountprivate.h"
#include "gtk/gtkrendernodepaintableprivate.h"
#include "gdk/gdkcairoprivate.h"
#include "nodewrapper.h"
#include "recording.h"
#include "renderrecording.h"
#include "startrecording.h"
#include "eventrecording.h"
#include "recorderrow.h"
/* {{{ ObjectProperty object */
typedef struct _ObjectProperty ObjectProperty;
G_DECLARE_FINAL_TYPE (ObjectProperty, object_property, OBJECT, PROPERTY, GObject);
struct _ObjectProperty
{
GObject parent;
char *name;
char *value;
GdkTexture *texture;
GskRenderNode *node;
};
enum {
OBJECT_PROPERTY_PROP_NAME = 1 ,
OBJECT_PROPERTY_PROP_VALUE,
OBJECT_PROPERTY_PROP_TEXTURE,
OBJECT_PROPERTY_NUM_PROPERTIES
};
static GParamSpec *obj_props[OBJECT_PROPERTY_NUM_PROPERTIES] = { NULL, };
G_DEFINE_TYPE (ObjectProperty, object_property, G_TYPE_OBJECT);
static void
object_property_init (ObjectProperty *self)
{
}
static void
object_property_finalize (GObject *object)
{
ObjectProperty *self = OBJECT_PROPERTY (object);
g_free (self->name);
g_free (self->value);
g_clear_object (&self->texture);
g_clear_pointer (&self->node, gsk_render_node_unref);
G_OBJECT_CLASS (object_property_parent_class)->finalize (object);
}
static void
object_property_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
ObjectProperty *self = OBJECT_PROPERTY (object);
switch (property_id)
{
case OBJECT_PROPERTY_PROP_NAME:
g_value_set_string (value, self->name);
break ;
case OBJECT_PROPERTY_PROP_VALUE:
g_value_set_string (value, self->value ? self->value : "" );
break ;
case OBJECT_PROPERTY_PROP_TEXTURE:
g_value_set_object (value, self->value);
break ;
default :
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break ;
}
}
static void
object_property_class_init (ObjectPropertyClass *class )
{
GObjectClass *object_class = G_OBJECT_CLASS (class );
object_class->finalize = object_property_finalize;
object_class->get_property = object_property_get_property;
obj_props[OBJECT_PROPERTY_PROP_NAME] = g_param_spec_string ("name" , NULL, NULL,
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_NAME);
obj_props[OBJECT_PROPERTY_PROP_VALUE] = g_param_spec_string ("value" , NULL, NULL,
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_NAME);
obj_props[OBJECT_PROPERTY_PROP_TEXTURE] = g_param_spec_object ("texture" , NULL, NULL,
GDK_TYPE_TEXTURE,
G_PARAM_READABLE |
G_PARAM_STATIC_NAME);
g_object_class_install_properties (object_class, OBJECT_PROPERTY_NUM_PROPERTIES, obj_props);
}
static ObjectProperty *
object_property_new (const char *name,
const char *value,
GdkTexture *texture,
GskRenderNode *node)
{
ObjectProperty *self;
self = g_object_new (object_property_get_type (), NULL);
self->name = g_strdup (name);
self->value = g_strdup (value);
g_set_object (&self->texture, texture);
if (node)
self->node = gsk_render_node_ref (node);
return self;
}
/* }}} */
struct _GtkInspectorRecorder
{
GtkWidget parent;
GListModel *recordings;
GtkTreeListModel *render_node_model;
GListStore *render_node_root_model;
GtkSingleSelection *render_node_selection;
GtkWidget *box;
GtkWidget *recordings_list;
GtkWidget *render_node_view;
GtkWidget *render_node_list;
GtkWidget *render_node_save_button;
GtkWidget *render_node_clip_button;
GtkWidget *rendering_mode_dropdown;
GtkWidget *node_property_tree;
GtkWidget *recording_data_stack;
GListStore *render_node_properties;
GListStore *event_properties;
GtkSingleSelection *event_properties_selection;
GtkWidget *event_property_tree;
GtkWidget *event_view;
GtkInspectorRecording *recording; /* start recording if recording or NULL if not */
gint64 start_time;
gboolean debug_nodes;
gboolean highlight_sequences;
gboolean record_events;
gboolean stop_after_next_frame;
gboolean save_to_file;
gboolean dark;
GdkEventSequence *selected_sequence;
GtkInspectorEventRecording *last_event_recording;
GSettings *settings;
};
typedef struct _GtkInspectorRecorderClass
{
GtkWidgetClass parent;
} GtkInspectorRecorderClass;
enum
{
PROP_0,
PROP_RECORDING,
PROP_DEBUG_NODES,
PROP_HIGHLIGHT_SEQUENCES,
PROP_SELECTED_SEQUENCE,
PROP_RECORD_EVENTS,
PROP_DARK,
LAST_PROP
};
static GParamSpec *props[LAST_PROP] = { NULL, };
G_DEFINE_TYPE (GtkInspectorRecorder, gtk_inspector_recorder, GTK_TYPE_WIDGET)
static void
recordings_clear_all (GtkButton *button,
GtkInspectorRecorder *recorder)
{
g_list_store_remove_all (G_LIST_STORE (recorder->recordings));
}
static const char *
node_type_name (GskRenderNodeType type)
{
switch (type)
{
case GSK_NOT_A_RENDER_NODE:
default :
g_assert_not_reached ();
return "Unknown" ;
case GSK_CONTAINER_NODE:
return "Container" ;
case GSK_DEBUG_NODE:
return "Debug" ;
case GSK_CAIRO_NODE:
return "Cairo" ;
case GSK_COLOR_NODE:
return "Color" ;
case GSK_LINEAR_GRADIENT_NODE:
return "Linear Gradient" ;
case GSK_REPEATING_LINEAR_GRADIENT_NODE:
return "Repeating Linear Gradient" ;
case GSK_RADIAL_GRADIENT_NODE:
return "Radial Gradient" ;
case GSK_REPEATING_RADIAL_GRADIENT_NODE:
return "Repeating Radial Gradient" ;
case GSK_CONIC_GRADIENT_NODE:
return "Conic Gradient" ;
case GSK_BORDER_NODE:
return "Border" ;
case GSK_TEXTURE_NODE:
return "Texture" ;
case GSK_TEXTURE_SCALE_NODE:
return "Scaled Texture" ;
case GSK_INSET_SHADOW_NODE:
return "Inset Shadow" ;
case GSK_OUTSET_SHADOW_NODE:
return "Outset Shadow" ;
case GSK_TRANSFORM_NODE:
return "Transform" ;
case GSK_OPACITY_NODE:
return "Opacity" ;
case GSK_COLOR_MATRIX_NODE:
return "Color Matrix" ;
case GSK_REPEAT_NODE:
return "Repeat" ;
case GSK_CLIP_NODE:
return "Clip" ;
case GSK_ROUNDED_CLIP_NODE:
return "Rounded Clip" ;
case GSK_FILL_NODE:
return "Fill" ;
case GSK_STROKE_NODE:
return "Stroke" ;
case GSK_SHADOW_NODE:
return "Shadow" ;
case GSK_BLEND_NODE:
return "Blend" ;
case GSK_MASK_NODE:
return "Mask" ;
case GSK_CROSS_FADE_NODE:
return "CrossFade" ;
case GSK_TEXT_NODE:
return "Text" ;
case GSK_BLUR_NODE:
return "Blur" ;
case GSK_GL_SHADER_NODE:
return "GL Shader" ;
case GSK_SUBSURFACE_NODE:
return "Subsurface" ;
case GSK_COMPONENT_TRANSFER_NODE:
return "Component Transfer" ;
case GSK_COPY_NODE:
return "Copy" ;
case GSK_PASTE_NODE:
return "Paste" ;
case GSK_COMPOSITE_NODE:
return "Composite" ;
case GSK_ISOLATION_NODE:
return "Isolation" ;
case GSK_DISPLACEMENT_NODE:
return "Displacement" ;
case GSK_ARITHMETIC_NODE:
return "Arithmetic" ;
case GSK_TURBULENCE_NODE:
return "Turbulence" ;
}
}
static char *
node_name (GskRenderNode *node)
{
switch (gsk_render_node_get_node_type (node))
{
case GSK_NOT_A_RENDER_NODE:
default :
g_assert_not_reached ();
case GSK_CONTAINER_NODE:
case GSK_CAIRO_NODE:
case GSK_LINEAR_GRADIENT_NODE:
case GSK_REPEATING_LINEAR_GRADIENT_NODE:
case GSK_RADIAL_GRADIENT_NODE:
case GSK_REPEATING_RADIAL_GRADIENT_NODE:
case GSK_CONIC_GRADIENT_NODE:
case GSK_BORDER_NODE:
case GSK_INSET_SHADOW_NODE:
case GSK_OUTSET_SHADOW_NODE:
case GSK_TRANSFORM_NODE:
case GSK_OPACITY_NODE:
case GSK_COLOR_MATRIX_NODE:
case GSK_REPEAT_NODE:
case GSK_CLIP_NODE:
case GSK_ROUNDED_CLIP_NODE:
case GSK_FILL_NODE:
case GSK_STROKE_NODE:
case GSK_SHADOW_NODE:
case GSK_BLEND_NODE:
case GSK_MASK_NODE:
case GSK_CROSS_FADE_NODE:
case GSK_TEXT_NODE:
case GSK_BLUR_NODE:
case GSK_GL_SHADER_NODE:
case GSK_SUBSURFACE_NODE:
case GSK_COMPONENT_TRANSFER_NODE:
case GSK_COPY_NODE:
case GSK_PASTE_NODE:
case GSK_COMPOSITE_NODE:
case GSK_ISOLATION_NODE:
case GSK_DISPLACEMENT_NODE:
case GSK_ARITHMETIC_NODE:
case GSK_TURBULENCE_NODE:
return g_strdup (node_type_name (gsk_render_node_get_node_type (node)));
case GSK_DEBUG_NODE:
return g_strdup (gsk_debug_node_get_message (node));
case GSK_COLOR_NODE:
return gdk_color_to_string (gsk_color_node_get_gdk_color (node));
case GSK_TEXTURE_NODE:
{
GdkTexture *texture = gsk_texture_node_get_texture (node);
return g_strdup_printf ("%dx%d Texture" , gdk_texture_get_width (texture), gdk_texture_get_height (texture));
}
case GSK_TEXTURE_SCALE_NODE:
{
GdkTexture *texture = gsk_texture_node_get_texture (node);
return g_strdup_printf ("%dx%d Texture, Filter %d" , gdk_texture_get_width (texture), gdk_texture_get_height (texture), gsk_texture_scale_node_get_filter (node));
}
}
}
static GdkContentProvider *
prepare_render_node_drag (GtkDragSource *source,
double x,
double y,
GtkListItem *list_item)
{
GtkTreeListRow *row_item;
GtkInspectorNodeWrapper *wrapper;
GskRenderNode *node;
row_item = gtk_list_item_get_item (list_item);
if (row_item == NULL)
return NULL;
wrapper = gtk_tree_list_row_get_item (row_item);
node = gtk_inspector_node_wrapper_get_profile_node (wrapper);
if (node == NULL)
node = gtk_inspector_node_wrapper_get_node (wrapper);
g_object_unref (wrapper);
return gdk_content_provider_new_typed (GSK_TYPE_RENDER_NODE, node);
}
static void
setup_widget_for_render_node (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *expander, *box, *child;
GtkDragSource *source;
/* expander */
expander = gtk_tree_expander_new ();
gtk_list_item_set_child (list_item, expander);
source = gtk_drag_source_new ();
g_signal_connect (source, "prepare" , G_CALLBACK (prepare_render_node_drag), list_item);
gtk_widget_add_controller (expander, GTK_EVENT_CONTROLLER (source));
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3 );
gtk_tree_expander_set_child (GTK_TREE_EXPANDER (expander), box);
/* icon */
child = gtk_image_new ();
gtk_box_append (GTK_BOX (box), child);
/* name */
child = gtk_inscription_new (NULL);
gtk_widget_set_hexpand (child, TRUE );
gtk_box_append (GTK_BOX (box), child);
}
static void
bind_widget_for_render_node (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
GtkInspectorNodeWrapper *wrapper;
GskRenderNode *node, *draw_node;
GdkPaintable *paintable;
graphene_rect_t bounds;
GtkTreeListRow *row_item;
GtkWidget *expander, *box, *child;
char *name;
row_item = gtk_list_item_get_item (list_item);
wrapper = gtk_tree_list_row_get_item (row_item);
node = gtk_inspector_node_wrapper_get_node (wrapper);
draw_node = gtk_inspector_node_wrapper_get_draw_node (wrapper);
/* expander */
expander = gtk_list_item_get_child (list_item);
gtk_tree_expander_set_list_row (GTK_TREE_EXPANDER (expander), row_item);
box = gtk_tree_expander_get_child (GTK_TREE_EXPANDER (expander));
/* icon */
gsk_render_node_get_bounds (draw_node, &bounds);
paintable = gtk_render_node_paintable_new (draw_node, &bounds);
child = gtk_widget_get_first_child (box);
gtk_image_set_from_paintable (GTK_IMAGE (child), paintable);
g_object_unref (paintable);
/* name */
name = node_name (node);
child = gtk_widget_get_last_child (box);
gtk_inscription_set_text (GTK_INSCRIPTION (child), name);
g_free (name);
g_object_unref (wrapper);
}
static void
show_render_node (GtkInspectorRecorder *recorder,
GskRenderNode *node,
GskRenderNode *profile_node)
{
GtkInspectorNodeWrapper *wrapper;
GskRenderNode *draw_node;
GdkPaintable *paintable;
graphene_rect_t bounds;
if (strcmp (gtk_stack_get_visible_child_name (GTK_STACK (recorder->recording_data_stack)), "frame_data" ) == 0 )
{
draw_node = gsk_render_node_replace_copy_paste (gsk_render_node_ref (node));
wrapper = gtk_inspector_node_wrapper_new (node, profile_node, draw_node, "Root" );
g_list_store_splice (recorder->render_node_root_model,
0 , g_list_model_get_n_items (G_LIST_MODEL (recorder->render_node_root_model)),
(gpointer[1 ]) { wrapper },
1 );
g_object_unref (wrapper);
gsk_render_node_unref (draw_node);
}
else
{
gsk_render_node_get_bounds (node, &bounds);
paintable = gtk_render_node_paintable_new (node, &bounds);
gtk_picture_set_paintable (GTK_PICTURE (recorder->event_view), paintable);
g_object_unref (paintable);
}
}
static void
setup_label (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label;
label = gtk_label_new (NULL);
gtk_label_set_xalign (GTK_LABEL (label), 0 .);
gtk_list_item_set_child (list_item, label);
}
static void
bind_property_name (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label;
ObjectProperty *property;
property = gtk_list_item_get_item (list_item);
label = gtk_list_item_get_child (list_item);
gtk_label_set_text (GTK_LABEL (label), property->name);
}
static void
setup_value_widgets (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *label;
GtkWidget *picture;
GtkWidget *box;
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10 );
label = gtk_label_new (NULL);
gtk_label_set_xalign (GTK_LABEL (label), 0 .);
gtk_label_set_selectable (GTK_LABEL (label), TRUE );
gtk_box_append (GTK_BOX (box), label);
picture = gtk_picture_new ();
gtk_picture_set_can_shrink (GTK_PICTURE (picture), TRUE );
gtk_picture_set_content_fit (GTK_PICTURE (picture), GTK_CONTENT_FIT_CONTAIN);
gtk_box_append (GTK_BOX (box), picture);
gtk_list_item_set_child (list_item, box);
}
static void
bind_value_widgets (GtkSignalListItemFactory *factory,
GtkListItem *list_item)
{
GtkWidget *box, *label, *picture;
ObjectProperty *property;
property = gtk_list_item_get_item (list_item);
box = gtk_list_item_get_child (list_item);
label = gtk_widget_get_first_child (box);
picture = gtk_widget_get_next_sibling (label);
gtk_label_set_text (GTK_LABEL (label), property->value);
gtk_widget_set_visible (picture, property->texture != NULL);
if (property->texture)
gtk_picture_set_paintable (GTK_PICTURE (picture), GDK_PAINTABLE (property->texture));
}
static GskRenderNode *
make_dot (double x, double y)
{
GskRenderNode *dot;
GskRenderNode *nodes[3 ];
GskRoundedRect rr;
float widths[4 ] = { 1 , 1 , 1 , 1 };
GdkRGBA colors[4 ] = {
{ 1 , 0 , 0 , 1 },
{ 1 , 0 , 0 , 1 },
{ 1 , 0 , 0 , 1 },
{ 1 , 0 , 0 , 1 },
};
nodes[0 ] = gsk_color_node_new (&colors[0 ], &GRAPHENE_RECT_INIT (x - 7 .5 , y - 0 .5 , 15 , 1 ));
nodes[1 ] = gsk_color_node_new (&colors[0 ], &GRAPHENE_RECT_INIT (x - 0 .5 , y - 7 .5 , 1 , 15 ));
nodes[2 ] = gsk_border_node_new (gsk_rounded_rect_init (&rr,
&GRAPHENE_RECT_INIT (x - 5 , y - 5 , 10 , 10 ),
&GRAPHENE_SIZE_INIT (5 , 5 ),
&GRAPHENE_SIZE_INIT (5 , 5 ),
&GRAPHENE_SIZE_INIT (5 , 5 ),
&GRAPHENE_SIZE_INIT (5 , 5 )),
widths,
colors);
dot = gsk_container_node_new (nodes, 3 );
gsk_render_node_unref (nodes[0 ]);
gsk_render_node_unref (nodes[1 ]);
gsk_render_node_unref (nodes[2 ]);
return dot;
}
static GskRenderNode *
make_rect (const graphene_rect_t *bounds)
{
return gsk_color_node_new (&(GdkRGBA){ 0 .2 , 0 , 0 , 0 .2 }, bounds);
}
static GskRenderNode *
make_event_node (GdkEvent *event,
GskRenderNode *frame,
const graphene_rect_t *bounds)
{
double x, y;
GskRenderNode *nodes[3 ];
int n_nodes = 0 ;
GskRenderNode *node;
if (!frame)
return NULL;
nodes[n_nodes++] = gsk_render_node_ref (frame);
if (bounds)
nodes[n_nodes++] = make_rect (bounds);
if (gdk_event_get_position (event, &x, &y))
nodes[n_nodes++] = make_dot (x, y);
if (n_nodes == 1 )
return nodes[0 ];
node = gsk_container_node_new (nodes, n_nodes);
for (int i = 0 ; i < n_nodes; i++)
gsk_render_node_unref (nodes[i]);
return node;
}
static void populate_event_properties (GListStore *store,
GskRenderNode *node,
GtkInspectorEventRecording *recording);
static void
recording_selected (GtkSingleSelection *selection,
GParamSpec *pspec,
GtkInspectorRecorder *recorder)
{
GtkInspectorRecording *recording;
GdkEventSequence *selected_sequence = NULL;
if (recorder->recordings == NULL)
{
gtk_stack_set_visible_child_name (GTK_STACK (recorder->recording_data_stack), "no_data" );
return ;
}
recording = gtk_single_selection_get_selected_item (selection);
if (GTK_INSPECTOR_IS_RENDER_RECORDING (recording))
{
gtk_stack_set_visible_child_name (GTK_STACK (recorder->recording_data_stack), "frame_data" );
show_render_node (recorder,
gtk_inspector_render_recording_get_node (GTK_INSPECTOR_RENDER_RECORDING (recording)),
gtk_inspector_render_recording_get_profile_node (GTK_INSPECTOR_RENDER_RECORDING (recording)));
}
else if (GTK_INSPECTOR_IS_EVENT_RECORDING (recording))
{
GdkEvent *event;
GskRenderNode *node = NULL;
gtk_stack_set_visible_child_name (GTK_STACK (recorder->recording_data_stack), "event_data" );
event = gtk_inspector_event_recording_get_event (GTK_INSPECTOR_EVENT_RECORDING (recording));
for (guint pos = gtk_single_selection_get_selected (selection) - 1 ; pos > 0 ; pos--)
{
GtkInspectorRecording *item = g_list_model_get_item (G_LIST_MODEL (selection), pos);
g_object_unref (item);
if (GTK_INSPECTOR_IS_RENDER_RECORDING (item) &&
gdk_event_get_surface (event) == gtk_inspector_render_recording_get_surface (GTK_INSPECTOR_RENDER_RECORDING (item)))
{
node = gtk_inspector_render_recording_get_node (GTK_INSPECTOR_RENDER_RECORDING (item));
if (node)
{
GskRenderNode *temp;
temp = make_event_node (event, node, NULL);
show_render_node (recorder, temp, NULL);
gsk_render_node_unref (temp);
}
break ;
}
}
populate_event_properties (recorder->event_properties, node, GTK_INSPECTOR_EVENT_RECORDING (recording));
if (recorder->highlight_sequences)
selected_sequence = gdk_event_get_event_sequence (event);
}
else
{
gtk_stack_set_visible_child_name (GTK_STACK (recorder->recording_data_stack), "no_data" );
gtk_picture_set_paintable (GTK_PICTURE (recorder->render_node_view), NULL);
g_list_store_remove_all (recorder->render_node_root_model);
}
gtk_inspector_recorder_set_selected_sequence (recorder, selected_sequence);
}
static GdkTexture *
get_color2_texture (const GdkColor *color)
{
GdkMemoryTextureBuilder *builder;
GdkTexture *texture;
gsize stride;
guchar *data;
GBytes *bytes;
int width = 30 ;
int height = 30 ;
stride = width * 4 * sizeof (float );
data = g_malloc (stride * height);
for (int i = 0 ; i < width * height; i++)
memcpy (data + 4 * sizeof (float ) * i, color->values, 4 * sizeof (float ));
bytes = g_bytes_new_take (data, stride * height);
builder = gdk_memory_texture_builder_new ();
gdk_memory_texture_builder_set_bytes (builder, bytes);
gdk_memory_texture_builder_set_stride (builder, stride);
gdk_memory_texture_builder_set_width (builder, 30 );
gdk_memory_texture_builder_set_height (builder, 30 );
gdk_memory_texture_builder_set_format (builder, GDK_MEMORY_R32G32B32A32_FLOAT);
gdk_memory_texture_builder_set_color_state (builder, color->color_state);
texture = gdk_memory_texture_builder_build (builder);
g_object_unref (builder);
g_bytes_unref (bytes);
return texture;
}
static GdkTexture *
get_linear_gradient_texture (gsize n_stops, const GskGradientStop *stops)
{
cairo_surface_t *surface;
cairo_t *cr;
cairo_pattern_t *pattern;
GdkTexture *texture;
int i;
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 90 , 30 );
cr = cairo_create (surface);
pattern = cairo_pattern_create_linear (0 , 0 , 90 , 0 );
for (i = 0 ; i < n_stops; i++)
gdk_cairo_pattern_add_color_stop_color (pattern, GDK_COLOR_STATE_SRGB, stops[i].offset, &stops[i].color);
cairo_set_source (cr, pattern);
cairo_pattern_destroy (pattern);
cairo_rectangle (cr, 0 , 0 , 90 , 30 );
cairo_fill (cr);
cairo_destroy (cr);
texture = gdk_texture_new_for_surface (surface);
cairo_surface_destroy (surface);
return texture;
}
static void
list_store_add_object_property (GListStore *store,
const char *name,
const char *value,
GdkTexture *texture,
GskRenderNode *node)
{
gpointer object = object_property_new (name, value, texture, node);
g_list_store_append (store, object);
g_object_unref (object);
}
static void
add_text_row (GListStore *store,
const char *name,
const char *format,
...) G_GNUC_PRINTF(3 , 4 );
static void
add_text_row (GListStore *store,
const char *name,
const char *format,
...)
{
va_list args;
char *text;
va_start (args, format);
text = g_strdup_vprintf (format, args);
va_end (args);
list_store_add_object_property (store, name, text, NULL, NULL);
g_free (text);
}
static void
add_color_row (GListStore *store,
const char *name,
const GdkColor *color)
{
char *text;
GdkTexture *texture;
text = gdk_color_to_string (color);
texture = get_color2_texture (color);
list_store_add_object_property (store, name, text, texture, NULL);
g_free (text);
g_object_unref (texture);
}
static void
add_int_row (GListStore *store,
const char *name,
int value)
{
add_text_row (store, name, "%d" , value);
}
static void
add_uint_row (GListStore *store,
const char *name,
unsigned long long value)
{
add_text_row (store, name, "%llu" , value);
}
static void
add_boolean_row (GListStore *store,
const char *name,
gboolean value)
{
add_text_row (store, name, value ? "TRUE" : "FALSE" );
}
static void
add_float_row (GListStore *store,
const char *name,
float value)
{
add_text_row (store, name, "%.2f" , value);
}
static const char *
enum_to_nick (GType type,
int value)
{
GEnumClass *class ;
GEnumValue *v;
class = g_type_class_ref (type);
v = g_enum_get_value (class , value);
g_type_class_unref (class );
return v->value_nick;
}
static const char *
hue_interpolation_to_string (GskHueInterpolation value)
{
const char *name[] = { "shorter" , "longer" , "increasing" , "decreasing" };
return name[value];
}
static void
add_texture_rows (GListStore *store,
GdkTexture *texture)
{
list_store_add_object_property (store, "Texture" , NULL, texture, NULL);
add_text_row (store, "Type" , "%s" , G_OBJECT_TYPE_NAME (texture));
add_text_row (store, "Size" , "%u x %u" , gdk_texture_get_width (texture), gdk_texture_get_height (texture));
add_text_row (store, "Format" , "%s (%s)" ,
gdk_memory_format_get_name (gdk_texture_get_format (texture)),
enum_to_nick (GDK_TYPE_MEMORY_FORMAT, gdk_texture_get_format (texture)));
add_text_row (store, "Color State" , "%s" , gdk_color_state_get_name (gdk_texture_get_color_state (texture)));
if (GDK_IS_MEMORY_TEXTURE (texture))
{
const GdkMemoryLayout *layout;
layout = gdk_memory_texture_get_layout (GDK_MEMORY_TEXTURE (texture));
add_uint_row (store, "Buffer Size" , layout->size);
switch (gdk_memory_format_get_n_planes (layout->format))
{
case 1 :
if (layout->planes[0 ].offset)
add_uint_row (store, "Offset" , layout->planes[0 ].offset);
add_uint_row (store, "Stride" , layout->planes[0 ].stride);
break ;
case 2 :
add_text_row (store, "Offsets" , "%zu, %zu" ,
layout->planes[0 ].offset, layout->planes[1 ].offset);
add_text_row (store, "Strides" , "%zu, %zu" ,
layout->planes[0 ].stride, layout->planes[1 ].stride);
break ;
case 3 :
add_text_row (store, "Offsets" , "%zu, %zu, %zu" ,
layout->planes[0 ].offset, layout->planes[1 ].offset, layout->planes[2 ].offset);
add_text_row (store, "Strides" , "%zu, %zu, %zu" ,
layout->planes[0 ].stride, layout->planes[1 ].stride, layout->planes[2 ].stride);
break ;
case 4 :
add_text_row (store, "Offsets" , "%zu, %zu, %zu, %zu" ,
layout->planes[0 ].offset, layout->planes[1 ].offset,
layout->planes[2 ].offset, layout->planes[3 ].offset);
add_text_row (store, "Strides" , "%zu, %zu, %zu, %zu" ,
layout->planes[0 ].stride, layout->planes[1 ].stride,
layout->planes[2 ].stride, layout->planes[3 ].stride);
break ;
default :
g_assert_not_reached ();
break ;
}
}
else if (GDK_IS_GL_TEXTURE (texture))
{
add_uint_row (store, "Texture Id" , gdk_gl_texture_get_id (GDK_GL_TEXTURE (texture)));
add_text_row (store, "Mipmap" , gdk_gl_texture_has_mipmap (GDK_GL_TEXTURE (texture)) ? "yes" : "no" );
add_text_row (store, "Sync" , gdk_gl_texture_get_sync (GDK_GL_TEXTURE (texture)) ? "yes" : "no" );
}
else if (GDK_IS_DMABUF_TEXTURE (texture))
{
const GdkDmabuf *dmabuf = gdk_dmabuf_texture_get_dmabuf (GDK_DMABUF_TEXTURE (texture));
unsigned i;
add_text_row (store, "Dmabuf Format" , "%.4s:%#" G_GINT64_MODIFIER "x" , (char *) &dmabuf->fourcc, dmabuf->modifier);
add_uint_row (store, "Planes" , dmabuf->n_planes);
for (i = 0 ; i < dmabuf->n_planes; i++)
{
char *name = g_strdup_printf ("File Descriptor %u" , i);
add_int_row (store, name, dmabuf->planes[i].fd);
g_free (name);
name = g_strdup_printf ("Stride %u" , i);
add_uint_row (store, name, dmabuf->planes[i].stride);
g_free (name);
name = g_strdup_printf ("Offset %u" , i);
add_uint_row (store, name, dmabuf->planes[i].offset);
g_free (name);
}
}
}
static void
add_rect_row (GListStore *store,
const char *label,
const graphene_rect_t *rect)
{
add_text_row (store, label,
"(%.2f, %.2f) to (%.2f, %.2f) - %.2f x %.2f" ,
rect->origin.x,
rect->origin.y,
rect->origin.x + rect->size.width,
rect->origin.y + rect->size.height,
rect->size.width,
rect->size.height);
}
static void G_GNUC_UNUSED
add_snap_row (GListStore *store,
const char *name,
GskRectSnap snap)
{
char *names[4 ];
gsize i;
for (i = 0 ; i < 4 ; i++)
names[i] = g_enum_to_string (GSK_TYPE_SNAP_DIRECTION, gsk_rect_snap_get_direction (snap, i));
add_text_row (store, name, "%s %s %s %s" , names[0 ], names[1 ], names[2 ], names[3 ]);
for (i = 0 ; i < 4 ; i++)
g_free (names[i]);
}
static void
populate_render_node_properties (GListStore *store,
GskRenderNode *node,
const char *role,
const GskDebugProfile *profile)
{
graphene_rect_t bounds, opaque;
g_list_store_remove_all (store);
gsk_render_node_get_bounds (node, &bounds);
if (role)
add_text_row (store, "Role" , "%s" , role);
add_text_row (store, "Type" , "%s" , node_type_name (gsk_render_node_get_node_type (node)));
add_rect_row (store, "Bounds" , &bounds);
if (gsk_render_node_get_opaque_rect (node, &opaque))
add_rect_row (store, "Opaque" , &opaque);
else
add_text_row (store, "Opaque" , "no" );
switch (gsk_render_node_get_node_type (node))
{
case GSK_CAIRO_NODE:
{
GdkTexture *texture;
cairo_surface_t *drawn_surface;
cairo_t *cr;
drawn_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
ceilf (node->bounds.size.width),
ceilf (node->bounds.size.height));
cr = cairo_create (drawn_surface);
cairo_save (cr);
cairo_translate (cr, -node->bounds.origin.x, -node->bounds.origin.y);
gsk_render_node_draw (node, cr);
cairo_restore (cr);
cairo_destroy (cr);
texture = gdk_texture_new_for_surface (drawn_surface);
cairo_surface_destroy (drawn_surface);
list_store_add_object_property (store, "Surface" , NULL, texture, NULL);
g_object_unref (texture);
}
break ;
case GSK_TEXTURE_NODE:
{
GdkTexture *texture = gsk_texture_node_get_texture (node);
add_texture_rows (store, texture);
add_snap_row (store, "Snap" , gsk_texture_node_get_snap (node));
}
break ;
case GSK_TEXTURE_SCALE_NODE:
{
GdkTexture *texture = gsk_texture_scale_node_get_texture (node);
GskScalingFilter filter = gsk_texture_scale_node_get_filter (node);
gchar *tmp;
add_texture_rows (store, texture);
add_snap_row (store, "Snap" , gsk_texture_scale_node_get_snap (node));
tmp = g_enum_to_string (GSK_TYPE_SCALING_FILTER, filter);
add_text_row (store, "Filter" , "%s" , tmp);
g_free (tmp);
}
break ;
case GSK_COLOR_NODE:
add_color_row (store, "Color" , gsk_color_node_get_gdk_color (node));
add_snap_row (store, "Snap" , gsk_color_node_get_snap (node));
break ;
case GSK_LINEAR_GRADIENT_NODE:
case GSK_REPEATING_LINEAR_GRADIENT_NODE:
{
const graphene_point_t *start = gsk_linear_gradient_node_get_start (node);
const graphene_point_t *end = gsk_linear_gradient_node_get_end (node);
const GskGradient *gradient = gsk_gradient_node_get_gradient (node);
const gsize n_stops = gsk_gradient_get_n_stops (gradient);
const GskGradientStop *stops = gsk_gradient_get_stops (gradient);
GdkColorState *interpolation = gsk_gradient_get_interpolation (gradient);
GskHueInterpolation hue_interpolation = gsk_gradient_get_hue_interpolation (gradient);
gboolean premultiplied = gsk_gradient_get_premultiplied (gradient);
int i;
GString *s;
GdkTexture *texture;
add_snap_row (store, "Snap" , gsk_linear_gradient_node_get_snap (node));
add_text_row (store, "Direction" , "%.2f %.2f ⟶ %.2f %.2f" , start->x, start->y, end->x, end->y);
add_text_row (store, "Interpolation" , "%s" , gdk_color_state_get_name (interpolation));
add_text_row (store, "Hue Interpolation" , "%s" , hue_interpolation_to_string (hue_interpolation));
add_boolean_row (store, "Premultiplied" , premultiplied);
s = g_string_new ("" );
for (i = 0 ; i < n_stops; i++)
{
if (i > 0 && stops[i].transition_hint != 0 .5 )
g_string_append_printf (s, "⟝ %.2f ⟞\n" , stops[i].transition_hint);
g_string_append_printf (s, "%.2f, " , stops[i].offset);
gdk_color_print (&stops[i].color, s);
g_string_append_c (s, '\n' );
}
texture = get_linear_gradient_texture (n_stops, stops);
list_store_add_object_property (store, "Color Stops" , s->str, texture, NULL);
g_object_unref (texture);
g_string_free (s, TRUE );
}
break ;
case GSK_RADIAL_GRADIENT_NODE:
case GSK_REPEATING_RADIAL_GRADIENT_NODE:
{
const graphene_point_t *center = gsk_radial_gradient_node_get_center (node);
const float start = gsk_radial_gradient_node_get_start (node);
const float end = gsk_radial_gradient_node_get_end (node);
const float hradius = gsk_radial_gradient_node_get_hradius (node);
const float vradius = gsk_radial_gradient_node_get_vradius (node);
const GskGradient *gradient = gsk_gradient_node_get_gradient (node);
const gsize n_stops = gsk_gradient_get_n_stops (gradient);
const GskGradientStop *stops = gsk_gradient_get_stops (gradient);
GdkColorState *interpolation = gsk_gradient_get_interpolation (gradient);
GskHueInterpolation hue_interpolation = gsk_gradient_get_hue_interpolation (gradient);
gboolean premultiplied = gsk_gradient_get_premultiplied (gradient);
int i;
GString *s;
GdkTexture *texture;
add_snap_row (store, "Snap" , gsk_radial_gradient_node_get_snap (node));
add_text_row (store, "Center" , "%.2f, %.2f" , center->x, center->y);
add_text_row (store, "Direction" , "%.2f ⟶ %.2f" , start, end);
add_text_row (store, "Radius" , "%.2f, %.2f" , hradius, vradius);
add_text_row (store, "Interpolation" , "%s" , gdk_color_state_get_name (interpolation));
add_text_row (store, "Hue Interpolation" , "%s" , hue_interpolation_to_string (hue_interpolation));
add_boolean_row (store, "Premultiplied" , premultiplied);
s = g_string_new ("" );
for (i = 0 ; i < n_stops; i++)
{
if (i > 0 && stops[i].transition_hint != 0 .5 )
g_string_append_printf (s, "⟝ %.2f ⟞\n" , stops[i].transition_hint);
g_string_append_printf (s, "%.2f, " , stops[i].offset);
gdk_color_print (&stops[i].color, s);
g_string_append_c (s, '\n' );
}
texture = get_linear_gradient_texture (n_stops, stops);
list_store_add_object_property (store, "Color Stops" , s->str, texture, NULL);
g_object_unref (texture);
g_string_free (s, TRUE );
}
break ;
case GSK_CONIC_GRADIENT_NODE:
{
const graphene_point_t *center = gsk_conic_gradient_node_get_center (node);
const float rotation = gsk_conic_gradient_node_get_rotation (node);
const GskGradient *gradient = gsk_gradient_node_get_gradient (node);
const gsize n_stops = gsk_gradient_get_n_stops (gradient);
const GskGradientStop *stops = gsk_gradient_get_stops (gradient);
GdkColorState *interpolation = gsk_gradient_get_interpolation (gradient);
GskHueInterpolation hue_interpolation = gsk_gradient_get_hue_interpolation (gradient);
gboolean premultiplied = gsk_gradient_get_premultiplied (gradient);
gsize i;
GString *s;
GdkTexture *texture;
add_snap_row (store, "Snap" , gsk_conic_gradient_node_get_snap (node));
add_text_row (store, "Center" , "%.2f, %.2f" , center->x, center->y);
add_text_row (store, "Rotation" , "%.2f" , rotation);
add_text_row (store, "Interpolation" , "%s" , gdk_color_state_get_name (interpolation));
add_text_row (store, "Hue Interpolation" , "%s" , hue_interpolation_to_string (hue_interpolation));
add_boolean_row (store, "Premultiplied" , premultiplied);
s = g_string_new ("" );
for (i = 0 ; i < n_stops; i++)
{
if (i > 0 && stops[i].transition_hint != 0 .5 )
g_string_append_printf (s, "⟝ %.2f ⟞\n" , stops[i].transition_hint);
g_string_append_printf (s, "%.2f, " , stops[i].offset);
gdk_color_print (&stops[i].color, s);
g_string_append_c (s, '\n' );
}
texture = get_linear_gradient_texture (n_stops, stops);
list_store_add_object_property (store, "Color Stops" , s->str, texture, NULL);
g_object_unref (texture);
g_string_free (s, TRUE );
}
break ;
case GSK_TEXT_NODE:
{
const PangoFont *font = gsk_text_node_get_font (node);
const graphene_point_t *offset = gsk_text_node_get_offset (node);
PangoFontDescription *desc;
GString *s;
gchar *tmp;
cairo_scaled_font_t *sf;
cairo_font_options_t *options;
cairo_hint_style_t hint_style;
cairo_antialias_t antialias;
cairo_hint_metrics_t hint_metrics;
desc = pango_font_describe ((PangoFont *)font);
tmp = pango_font_description_to_string (desc);
add_text_row (store, "Font" , "%s" , tmp);
g_free (tmp);
pango_font_description_free (desc);
sf = pango_cairo_font_get_scaled_font (PANGO_CAIRO_FONT (font));
options = cairo_font_options_create ();
cairo_scaled_font_get_font_options (sf, options);
hint_style = cairo_font_options_get_hint_style (options);
antialias = cairo_font_options_get_antialias (options);
hint_metrics = cairo_font_options_get_hint_metrics (options);
cairo_font_options_destroy (options);
add_text_row (store, "Hint Style" , "%s" , enum_to_nick (CAIRO_GOBJECT_TYPE_HINT_STYLE, hint_style));
add_text_row (store, "Antialias" , "%s" , enum_to_nick (CAIRO_GOBJECT_TYPE_ANTIALIAS, antialias));
add_text_row (store, "Hint Metrics" , "%s" , enum_to_nick (CAIRO_GOBJECT_TYPE_HINT_METRICS, hint_metrics));
s = g_string_sized_new (0 );
gsk_text_node_serialize_glyphs (node, s);
add_text_row (store, "Glyphs" , "%s" , s->str);
g_string_free (s, TRUE );
add_text_row (store, "Position" , "%.2f %.2f" , offset->x, offset->y);
add_color_row (store, "Color" , gsk_text_node_get_gdk_color (node));
}
break ;
case GSK_BORDER_NODE:
{
const char *name[4 ] = { "Top" , "Right" , "Bottom" , "Left" };
const float *widths = gsk_border_node_get_widths (node);
const GdkColor *colors = gsk_border_node_get_gdk_colors (node);
int i;
add_snap_row (store, "Snap" , gsk_border_node_get_snap (node));
for (i = 0 ; i < 4 ; i++)
{
GdkTexture *texture;
char *text, *tmp;
text = gdk_color_to_string (&colors[i]);
tmp = g_strdup_printf ("%.2f, %s" , widths[i], text);
texture = get_color2_texture (&colors[i]);
list_store_add_object_property (store, name[i], tmp, texture, NULL);
g_object_unref (texture);
g_free (text);
g_free (tmp);
}
add_snap_row (store, "Border Snap" , gsk_border_node_get_border_snap (node));
}
break ;
case GSK_OPACITY_NODE:
add_float_row (store, "Opacity" , gsk_opacity_node_get_opacity (node));
break ;
case GSK_CROSS_FADE_NODE:
add_float_row (store, "Progress" , gsk_cross_fade_node_get_progress (node));
break ;
case GSK_BLEND_NODE:
{
GskBlendMode mode = gsk_blend_node_get_blend_mode (node);
add_text_row (store, "Blendmode" , "%s" , enum_to_nick (GSK_TYPE_BLEND_MODE, mode));
add_text_row (store, "Color State" , "%s" , gdk_color_state_get_name (gsk_blend_node_get_color_state (node)));
}
break ;
case GSK_MASK_NODE:
{
GskMaskMode mode = gsk_mask_node_get_mask_mode (node);
gchar *tmp = g_enum_to_string (GSK_TYPE_MASK_MODE, mode);
add_text_row (store, "Mask mode" , "%s" , tmp);
g_free (tmp);
}
break ;
case GSK_BLUR_NODE:
add_float_row (store, "Radius" , gsk_blur_node_get_radius (node));
break ;
case GSK_GL_SHADER_NODE:
{
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
GskGLShader *shader = gsk_gl_shader_node_get_shader (node);
GBytes *args = gsk_gl_shader_node_get_args (node);
int i;
add_int_row (store, "Required textures" , gsk_gl_shader_get_n_textures (shader));
for (i = 0 ; i < gsk_gl_shader_get_n_uniforms (shader); i++)
{
const char *name;
char *title;
name = gsk_gl_shader_get_uniform_name (shader, i);
title = g_strdup_printf ("Uniform %s" , name);
switch (gsk_gl_shader_get_uniform_type (shader, i))
{
case GSK_GL_UNIFORM_TYPE_NONE:
default :
g_assert_not_reached ();
break ;
case GSK_GL_UNIFORM_TYPE_FLOAT:
add_float_row (store, title,
gsk_gl_shader_get_arg_float (shader, args, i));
break ;
case GSK_GL_UNIFORM_TYPE_INT:
add_int_row (store, title,
gsk_gl_shader_get_arg_int (shader, args, i));
break ;
case GSK_GL_UNIFORM_TYPE_UINT:
add_uint_row (store, title,
gsk_gl_shader_get_arg_uint (shader, args, i));
break ;
case GSK_GL_UNIFORM_TYPE_BOOL:
add_boolean_row (store, title,
gsk_gl_shader_get_arg_bool (shader, args, i));
break ;
case GSK_GL_UNIFORM_TYPE_VEC2:
{
graphene_vec2_t v;
gsk_gl_shader_get_arg_vec2 (shader, args, i, &v);
float x = graphene_vec2_get_x (&v);
float y = graphene_vec2_get_x (&v);
add_text_row (store, title, "%.2f %.2f" , x, y);
}
break ;
case GSK_GL_UNIFORM_TYPE_VEC3:
{
graphene_vec3_t v;
gsk_gl_shader_get_arg_vec3 (shader, args, i, &v);
float x = graphene_vec3_get_x (&v);
float y = graphene_vec3_get_y (&v);
float z = graphene_vec3_get_z (&v);
add_text_row (store, title, "%.2f %.2f %.2f" , x, y, z);
}
break ;
case GSK_GL_UNIFORM_TYPE_VEC4:
{
graphene_vec4_t v;
gsk_gl_shader_get_arg_vec4 (shader, args, i, &v);
float x = graphene_vec4_get_x (&v);
float y = graphene_vec4_get_y (&v);
float z = graphene_vec4_get_z (&v);
float w = graphene_vec4_get_w (&v);
add_text_row (store, title, "%.2f %.2f %.2f %.2f" , x, y, z, w);
}
break ;
}
g_free (title);
}
G_GNUC_END_IGNORE_DEPRECATIONS
}
break ;
case GSK_INSET_SHADOW_NODE:
{
const GdkColor *color = gsk_inset_shadow_node_get_gdk_color (node);
float dx = gsk_inset_shadow_node_get_dx (node);
float dy = gsk_inset_shadow_node_get_dy (node);
float spread = gsk_inset_shadow_node_get_spread (node);
float radius = gsk_inset_shadow_node_get_blur_radius (node);
add_color_row (store, "Color" , color);
add_text_row (store, "Offset" , "%.2f %.2f" , dx, dy);
add_snap_row (store, "Snap" , gsk_inset_shadow_node_get_snap (node));
add_float_row (store, "Spread" , spread);
add_float_row (store, "Radius" , radius);
}
break ;
case GSK_OUTSET_SHADOW_NODE:
{
const GskRoundedRect *outline = gsk_outset_shadow_node_get_outline (node);
const GdkColor *color = gsk_outset_shadow_node_get_gdk_color (node);
float dx = gsk_outset_shadow_node_get_dx (node);
float dy = gsk_outset_shadow_node_get_dy (node);
float spread = gsk_outset_shadow_node_get_spread (node);
float radius = gsk_outset_shadow_node_get_blur_radius (node);
float rect[12 ];
gsk_rounded_rect_to_float (outline, graphene_point_zero (), rect);
add_rect_row (store, "Outline" , &outline->bounds);
add_snap_row (store, "Snap" , gsk_outset_shadow_node_get_snap (node));
add_color_row (store, "Color" , color);
add_text_row (store, "Offset" , "%.2f %.2f" , dx, dy);
add_float_row (store, "Spread" , spread);
add_float_row (store, "Radius" , radius);
}
break ;
case GSK_REPEAT_NODE:
{
add_snap_row (store, "Snap" , gsk_repeat_node_get_snap (node));
add_rect_row (store, "Child Bounds" , gsk_repeat_node_get_child_bounds (node));
add_snap_row (store, "Child Snap" , gsk_repeat_node_get_child_snap (node));
}
break ;
case GSK_COLOR_MATRIX_NODE:
{
const graphene_matrix_t *matrix = gsk_color_matrix_node_get_color_matrix (node);
const graphene_vec4_t *offset = gsk_color_matrix_node_get_color_offset (node);
add_snap_row (store, "Snap" , gsk_color_matrix_node_get_snap (node));
add_text_row (store, "Matrix" ,
"% .2f % .2f % .2f % .2f\n"
"% .2f % .2f % .2f % .2f\n"
"% .2f % .2f % .2f % .2f\n"
"% .2f % .2f % .2f % .2f" ,
graphene_matrix_get_value (matrix, 0 , 0 ),
graphene_matrix_get_value (matrix, 0 , 1 ),
graphene_matrix_get_value (matrix, 0 , 2 ),
graphene_matrix_get_value (matrix, 0 , 3 ),
graphene_matrix_get_value (matrix, 1 , 0 ),
graphene_matrix_get_value (matrix, 1 , 1 ),
graphene_matrix_get_value (matrix, 1 , 2 ),
graphene_matrix_get_value (matrix, 1 , 3 ),
graphene_matrix_get_value (matrix, 2 , 0 ),
graphene_matrix_get_value (matrix, 2 , 1 ),
graphene_matrix_get_value (matrix, 2 , 2 ),
graphene_matrix_get_value (matrix, 2 , 3 ),
graphene_matrix_get_value (matrix, 3 , 0 ),
graphene_matrix_get_value (matrix, 3 , 1 ),
graphene_matrix_get_value (matrix, 3 , 2 ),
graphene_matrix_get_value (matrix, 3 , 3 ));
add_text_row (store, "Offset" ,
"%.2f %.2f %.2f %.2f" ,
graphene_vec4_get_x (offset),
graphene_vec4_get_y (offset),
graphene_vec4_get_z (offset),
graphene_vec4_get_w (offset));
add_text_row (store, "Color State" , "%s" , gdk_color_state_get_name (gsk_color_matrix_node_get_color_state (node)));
}
break ;
case GSK_CLIP_NODE:
{
const graphene_rect_t *clip = gsk_clip_node_get_clip (node);
add_rect_row (store, "Clip" , clip);
add_snap_row (store, "Snap" , gsk_clip_node_get_snap (node));
}
break ;
case GSK_ROUNDED_CLIP_NODE:
{
const GskRoundedRect *clip = gsk_rounded_clip_node_get_clip (node);
add_rect_row (store, "Clip" , &clip->bounds);
add_snap_row (store, "Snap" , gsk_rounded_clip_node_get_snap (node));
add_text_row (store, "Top Left Corner Size" , "%.2f x %.2f" , clip->corner[0 ].width, clip->corner[0 ].height);
add_text_row (store, "Top Right Corner Size" , "%.2f x %.2f" , clip->corner[1 ].width, clip->corner[1 ].height);
add_text_row (store, "Bottom Right Corner Size" , "%.2f x %.2f" , clip->corner[2 ].width, clip->corner[2 ].height);
add_text_row (store, "Bottom Left Corner Size" , "%.2f x %.2f" , clip->corner[3 ].width, clip->corner[3 ].height);
}
break ;
case GSK_FILL_NODE:
{
GskPath *path = gsk_fill_node_get_path (node);
GskFillRule fill_rule = gsk_fill_node_get_fill_rule (node);
char *tmp;
tmp = gsk_path_to_string (path);
add_text_row (store, "Path" , "%s" , tmp);
g_free (tmp);
add_text_row (store, "Fill rule" , "%s" , enum_to_nick (GSK_TYPE_FILL_RULE, fill_rule));
}
break ;
case GSK_STROKE_NODE:
{
GskPath *path = gsk_stroke_node_get_path (node);
const GskStroke *stroke = gsk_stroke_node_get_stroke (node);
GskLineCap line_cap = gsk_stroke_get_line_cap (stroke);
GskLineJoin line_join = gsk_stroke_get_line_join (stroke);
char *tmp;
tmp = gsk_path_to_string (path);
add_text_row (store, "Path" , "%s" , tmp);
g_free (tmp);
add_text_row (store, "Line width" , "%.2f" , gsk_stroke_get_line_width (stroke));
add_text_row (store, "Line cap" , "%s" , enum_to_nick (GSK_TYPE_LINE_CAP, line_cap));
add_text_row (store, "Line join" , "%s" , enum_to_nick (GSK_TYPE_LINE_JOIN, line_join));
}
break ;
case GSK_CONTAINER_NODE:
add_uint_row (store, "Children" , gsk_container_node_get_n_children (node));
break ;
case GSK_DEBUG_NODE:
add_text_row (store, "Message" , "%s" , gsk_debug_node_get_message (node));
break ;
case GSK_SHADOW_NODE:
{
int i;
for (i = 0 ; i < gsk_shadow_node_get_n_shadows (node); i++)
{
char *label;
const GskShadowEntry *shadow = gsk_shadow_node_get_shadow_entry (node, i);
label = g_strdup_printf ("Color %d" , i);
add_color_row (store, label, &shadow->color);
g_free (label);
label = g_strdup_printf ("Offset %d" , i);
add_text_row (store, label, "%.2f %.2f" , shadow->offset.x, shadow->offset.y);
g_free (label);
label = g_strdup_printf ("Radius %d" , i);
add_float_row (store, label, shadow->radius);
g_free (label);
}
}
break ;
case GSK_TRANSFORM_NODE:
{
static const char * category_names[] = {
[GSK_FINE_TRANSFORM_CATEGORY_UNKNOWN] = "unknown" ,
[GSK_FINE_TRANSFORM_CATEGORY_ANY] = "any" ,
[GSK_FINE_TRANSFORM_CATEGORY_3D] = "3D" ,
[GSK_FINE_TRANSFORM_CATEGORY_2D] = "2D" ,
[GSK_FINE_TRANSFORM_CATEGORY_2D_DIHEDRAL] = "2D dihedral" ,
[GSK_FINE_TRANSFORM_CATEGORY_2D_NEGATIVE_AFFINE] = "2D negative affine" ,
[GSK_FINE_TRANSFORM_CATEGORY_2D_AFFINE] = "2D affine" ,
[GSK_FINE_TRANSFORM_CATEGORY_2D_TRANSLATE] = "2D translate" ,
[GSK_FINE_TRANSFORM_CATEGORY_IDENTITY] = "identity"
};
GskTransform *transform;
char *s;
transform = gsk_transform_node_get_transform (node);
s = gsk_transform_to_string (transform);
add_text_row (store, "Matrix" , "%s" , s);
g_free (s);
add_text_row (store, "Category" , "%s" , category_names[gsk_transform_get_fine_category (transform)]);
}
break ;
case GSK_SUBSURFACE_NODE:
{
GdkSubsurface *subsurface = gsk_subsurface_node_get_subsurface (node);
add_text_row (store, "Subsurface" , "%p" , subsurface);
}
break ;
case GSK_COMPONENT_TRANSFER_NODE:
{
const char *component[] = { "Red" , "Green" , "Blue" , "Alpha" };
GString *s = g_string_new ("" );
for (guint i = 0 ; i < 4 ; i++)
{
g_string_set_size (s, 0 );
gsk_component_transfer_print (gsk_component_transfer_node_get_transfer (node, i), s);
add_text_row (store, component[i], "%s" , s->str);
}
add_text_row (store, "Color State" , "%s" , gdk_color_state_get_name (gsk_component_transfer_node_get_color_state (node)));
g_string_free (s, TRUE );
}
break ;
case GSK_COPY_NODE:
break ;
case GSK_PASTE_NODE:
add_snap_row (store, "Snap" , gsk_paste_node_get_snap (node));
add_uint_row (store, "Copy to paste" , gsk_paste_node_get_depth (node));
break ;
case GSK_COMPOSITE_NODE:
{
GskPorterDuff operator = gsk_composite_node_get_operator (node);
gchar *tmp = g_enum_to_string (GSK_TYPE_PORTER_DUFF, operator );
add_text_row (store, "Operator" , "%s" , tmp);
g_free (tmp);
}
break ;
case GSK_ISOLATION_NODE:
{
GskIsolation isolations = gsk_isolation_node_get_isolations (node);
gchar *tmp;
if (gtk_popcount (GSK_ISOLATION_ALL & ~isolations) < gtk_popcount (isolations))
{
isolations = GSK_ISOLATION_ALL & ~isolations;
tmp = g_flags_to_string (GSK_TYPE_ISOLATION, isolations);
add_text_row (store, "Disabled features" , "%s" , tmp);
}
else
{
tmp = g_flags_to_string (GSK_TYPE_ISOLATION, isolations);
add_text_row (store, "Enabled features" , "%s" , tmp);
}
g_free (tmp);
}
break ;
case GSK_DISPLACEMENT_NODE:
{
const graphene_size_t *s;
const graphene_point_t *p;
const GdkColorChannel *channels;
add_snap_row (store, "Snap" , gsk_displacement_node_get_snap (node));
s = gsk_displacement_node_get_max (node);
add_text_row (store, "Max" , "%f %f" , s->width, s->height);
s = gsk_displacement_node_get_scale (node);
add_text_row (store, "Scale" , "%f %f" , s->width, s->height);
p = gsk_displacement_node_get_offset (node);
add_text_row (store, "Offset" , "%f %f" , p->x, p->y);
channels = gsk_displacement_node_get_channels (node);
add_text_row (store, "Channels" , "%s %s" ,
enum_to_nick (GDK_TYPE_COLOR_CHANNEL, channels[0 ]),
enum_to_nick (GDK_TYPE_COLOR_CHANNEL, channels[1 ]));
}
break ;
case GSK_ARITHMETIC_NODE:
{
const float *k;
char *tmp;
add_snap_row (store, "Snap" , gsk_arithmetic_node_get_snap (node));
k = gsk_arithmetic_node_get_factors (node);
tmp = g_strdup_printf ("%f %f %f %f" , k[0 ], k[1 ], k[2 ], k[3 ]);
add_text_row (store, "Factors" , "%s" , tmp);
add_text_row (store, "Color State" , "%s" , gdk_color_state_get_name (gsk_arithmetic_node_get_color_state (node)));
g_free (tmp);
}
break ;
case GSK_TURBULENCE_NODE:
{
const graphene_size_t *s;
const char *noise_types[] = { "Fractal Noise" , "Turbulence" };
s = gsk_turbulence_node_get_base_frequency (node);
add_text_row (store, "Base Frequency" , "%f %f" , s->width, s->height);
add_int_row (store, "Octaves" , gsk_turbulence_node_get_num_octaves (node));
add_int_row (store, "Seed" , gsk_turbulence_node_get_seed (node));
add_text_row (store, "Noise Type" , "%s" , noise_types[gsk_turbulence_node_get_noise_type (node)]);
add_boolean_row (store, "Stitch Tiles" , gsk_turbulence_node_get_stitch_tiles (node));
}
break ;
case GSK_NOT_A_RENDER_NODE:
default :
break ;
}
if (profile)
{
add_text_row (store, "Performance" , "%s" , "" );
add_text_row (store, "CPU total" , "%'lluns" , (unsigned long long ) profile->total.cpu_ns);
add_text_row (store, "CPU self" , "%'lluns" , (unsigned long long ) profile->self.cpu_ns);
add_text_row (store, "GPU total" , "%'lluns" , (unsigned long long ) profile->total.gpu_ns);
add_text_row (store, "GPU self" , "%'lluns" , (unsigned long long ) profile->self.gpu_ns);
add_text_row (store, "pixels total" , "%'llu" , (unsigned long long ) profile->total.gpu_pixels);
add_text_row (store, "pixels self" , "%'llu" , (unsigned long long ) profile->self.gpu_pixels);
add_text_row (store, "offscreen total" , "%zu @ %'llu" ,
profile->total.n_offscreens, (unsigned long long ) profile->total.offscreen_pixels);
add_text_row (store, "offscreen self" , "%zu @ %'llu" ,
profile->self.n_offscreens, (unsigned long long ) profile->self.offscreen_pixels);
add_text_row (store, "upload total" , "%zu @ %'llu" ,
profile->total.n_uploads, (unsigned long long ) profile->total.upload_pixels);
add_text_row (store, "upload self" , "%zu @ %'llu" ,
profile->self.n_uploads, (unsigned long long ) profile->self.upload_pixels);
add_text_row (store, "base total" , "%zu @ %'llu" ,
profile->total.n_bases, (unsigned long long ) profile->total.base_pixels);
add_text_row (store, "base self" , "%zu @ %'llu" ,
profile->self.n_bases, (unsigned long long ) profile->self.base_pixels);
}
}
static const char *
event_type_name (GdkEventType type)
{
const char *event_name[] = {
"Delete" ,
"Motion" ,
"Button Press" ,
"Button Release" ,
"Key Press" ,
"Key Release" ,
"Enter" ,
"Leave" ,
"Focus" ,
"Proximity In" ,
"Proximity Out" ,
"Drag Enter" ,
"Drag Leave" ,
"Drag Motion" ,
"Drop Start" ,
"Scroll" ,
"Grab Broken" ,
"Touch Begin" ,
"Touch Update" ,
"Touch End" ,
"Touch Cancel" ,
"Touchpad Swipe" ,
"Touchpad Pinch" ,
"Pad Button Press" ,
"Pad Button Release" ,
"Pad Rind" ,
"Pad Strip" ,
"Pad Group Mode" ,
"Touchpad Hold" ,
"Pad Dial" ,
};
G_STATIC_ASSERT (G_N_ELEMENTS (event_name) == GDK_EVENT_LAST);
return event_name[type];
}
static const char *
scroll_direction_name (GdkScrollDirection dir)
{
const char *scroll_dir[] = {
"Up" , "Down" , "Left" , "Right" , "Smooth"
};
return scroll_dir[dir];
}
static char *
modifier_names (GdkModifierType state)
{
struct {
const char *name;
int mask;
} mods[] = {
{ "Shift" , GDK_SHIFT_MASK },
{ "Lock" , GDK_LOCK_MASK },
{ "Control" , GDK_CONTROL_MASK },
{ "Alt" , GDK_ALT_MASK },
{ "Button1" , GDK_BUTTON1_MASK },
{ "Button2" , GDK_BUTTON2_MASK },
{ "Button3" , GDK_BUTTON3_MASK },
{ "Button4" , GDK_BUTTON4_MASK },
{ "Button5" , GDK_BUTTON5_MASK },
{ "Super" , GDK_SUPER_MASK },
{ "Hyper" , GDK_HYPER_MASK },
{ "Meta" , GDK_META_MASK },
};
GString *s;
s = g_string_new ("" );
for (int i = 0 ; i < G_N_ELEMENTS (mods); i++)
{
if (state & mods[i].mask)
{
if (s->len > 0 )
g_string_append (s, " " );
g_string_append (s, mods[i].name);
}
}
return g_string_free (s, FALSE );
}
static char *
key_event_string (GdkEvent *event)
{
guint keyval;
gunichar c;
char buf[5 ] = { 0 , };
keyval = gdk_key_event_get_keyval (event);
c = gdk_keyval_to_unicode (keyval);
if (c)
{
g_unichar_to_utf8 (c, buf);
return g_strdup (buf);
}
return g_strdup (gdk_keyval_name (keyval));
}
static const char *
device_tool_name (GdkDeviceTool *tool)
{
const char *name[] = {
"Unknown" ,
"Pen" ,
"Eraser" ,
"Brush" ,
"Pencil" ,
"Airbrush" ,
"Mouse" ,
"Lens"
};
return name[gdk_device_tool_get_tool_type (tool)];
}
static const char *
axis_name (GdkAxisUse axis)
{
const char *name[] = {
"" ,
"X" ,
"Y" ,
"Delta X" ,
"Delta Y" ,
"Pressure" ,
"X Tilt" ,
"Y Tilt" ,
"Wheel" ,
"Distance" ,
"Rotation" ,
"Slider"
};
return name[axis];
}
static const char *
gesture_phase_name (GdkTouchpadGesturePhase phase)
{
const char *name[] = {
"Begin" ,
"Update" ,
"End" ,
"Cancel"
};
return name[phase];
}
static const char *
scroll_unit_name (GdkScrollUnit unit)
{
if (unit == GDK_SCROLL_UNIT_WHEEL)
return "Wheel" ;
else if (unit == GDK_SCROLL_UNIT_SURFACE)
return "Surface" ;
else
return "Incorrect value" ;
}
static void
populate_event_properties (GListStore *store,
GskRenderNode *node,
GtkInspectorEventRecording *recording)
{
GdkEventType type;
GdkDevice *device;
GdkDeviceTool *tool;
double x, y;
double dx, dy;
GdkModifierType state;
GdkScrollUnit scroll_unit;
GdkEvent *event;
EventTrace *traces;
GType target_type;
gsize n_traces;
GskRenderNode *temp;
g_list_store_remove_all (store);
event = gtk_inspector_event_recording_get_event (recording);
traces = gtk_inspector_event_recording_get_traces (recording, &n_traces);
target_type = gtk_inspector_event_recording_get_target_type (recording);
type = gdk_event_get_event_type (event);
add_text_row (store, "Type" , "%s" , event_type_name (type));
if (gdk_event_get_event_sequence (event) != NULL)
{
add_text_row (store, "Sequence" , "%p" , gdk_event_get_event_sequence (event));
}
add_int_row (store, "Timestamp" , gdk_event_get_time (event));
device = gdk_event_get_device (event);
if (device)
add_text_row (store, "Device" , "%s" , gdk_device_get_name (device));
tool = gdk_event_get_device_tool (event);
if (tool)
add_text_row (store, "Device Tool" , "%s" , device_tool_name (tool));
if (gdk_event_get_position (event, &x, &y))
{
add_text_row (store, "Position" , "%.2f %.2f" , x, y);
}
if (tool)
{
GdkAxisFlags axes = gdk_device_tool_get_axes (tool);
/* We report position and scroll delta separately, so skip them here */
axes &= ~(GDK_AXIS_FLAG_X|GDK_AXIS_FLAG_Y|GDK_AXIS_FLAG_DELTA_X|GDK_AXIS_FLAG_DELTA_Y);
for (int i = 1 ; i < GDK_AXIS_LAST; i++)
{
if (axes & (1 << i))
{
double val;
gdk_event_get_axis (event, i, &val);
add_text_row (store, axis_name (i), "%.2f" , val);
}
}
}
state = gdk_event_get_modifier_state (event);
if (state != 0 )
{
char *tmp = modifier_names (state);
add_text_row (store, "State" , "%s" , tmp);
g_free (tmp);
}
switch ((int )type)
{
case GDK_BUTTON_PRESS:
case GDK_BUTTON_RELEASE:
add_int_row (store, "Button" , gdk_button_event_get_button (event));
break ;
case GDK_KEY_PRESS:
case GDK_KEY_RELEASE:
{
char *tmp;
tmp = modifier_names (gdk_key_event_get_consumed_modifiers (event));
add_text_row (store, "Consumed modifiers" , "%s" , tmp);
g_free (tmp);
add_int_row (store, "Keycode" , gdk_key_event_get_keycode (event));
add_int_row (store, "Keyval" , gdk_key_event_get_keyval (event));
tmp = key_event_string (event);
add_text_row (store, "Key" , "%s" , tmp);
g_free (tmp);
add_int_row (store, "Layout" , gdk_key_event_get_layout (event));
add_int_row (store, "Level" , gdk_key_event_get_level (event));
add_boolean_row (store, "Is Modifier" , gdk_key_event_is_modifier (event));
}
break ;
case GDK_SCROLL:
if (gdk_scroll_event_get_direction (event) == GDK_SCROLL_SMOOTH)
{
gdk_scroll_event_get_deltas (event, &x, &y);
add_text_row (store, "Delta" , "%.2f %.2f" , x, y);
scroll_unit = gdk_scroll_event_get_unit (event);
add_text_row (store, "Unit" , "%s" , scroll_unit_name (scroll_unit));
}
else
{
add_text_row (store, "Direction" , "%s" , scroll_direction_name (gdk_scroll_event_get_direction (event)));
}
add_boolean_row (store, "Is Stop" , gdk_scroll_event_is_stop (event));
break ;
case GDK_FOCUS_CHANGE:
add_text_row (store, "Direction" , gdk_focus_event_get_in (event) ? "In" : "Out" );
break ;
case GDK_ENTER_NOTIFY:
case GDK_LEAVE_NOTIFY:
add_int_row (store, "Mode" , gdk_crossing_event_get_mode (event));
add_int_row (store, "Detail" , gdk_crossing_event_get_detail (event));
add_boolean_row (store, "Is Focus" , gdk_crossing_event_get_focus (event));
break ;
case GDK_GRAB_BROKEN:
add_boolean_row (store, "Implicit" , gdk_grab_broken_event_get_implicit (event));
break ;
case GDK_TOUCHPAD_SWIPE:
case GDK_TOUCHPAD_PINCH:
add_text_row (store, "Phase" , "%s" , gesture_phase_name (gdk_touchpad_event_get_gesture_phase (event)));
add_int_row (store, "Fingers" , gdk_touchpad_event_get_n_fingers (event));
gdk_touchpad_event_get_deltas (event, &dx, &dy);
add_text_row (store, "Delta" , "%.2f %.f2" , dx, dy);
if (type == GDK_TOUCHPAD_PINCH)
{
add_text_row (store, "Angle Delta" , "%.2f" , gdk_touchpad_event_get_pinch_angle_delta (event));
add_text_row (store, "Scale" , "%.2f" , gdk_touchpad_event_get_pinch_scale (event));
}
break ;
default :
/* FIXME */
;
}
if (type == GDK_MOTION_NOTIFY || type == GDK_SCROLL)
{
GdkTimeCoord *history;
guint n_coords;
history = gdk_event_get_history (event, &n_coords);
if (history)
{
GString *s = g_string_new ("" );
for (int i = 0 ; i < n_coords; i++)
{
if (i > 0 )
g_string_append (s, "\n" );
g_string_append_printf (s, "%d" , history[i].time);
if (history[i].flags & (GDK_AXIS_FLAG_X|GDK_AXIS_FLAG_Y))
g_string_append_printf (s, " Position %.2f %.2f" , history[i].axes[GDK_AXIS_X], history[i].axes[GDK_AXIS_Y]);
if (history[i].flags & (GDK_AXIS_FLAG_DELTA_X|GDK_AXIS_FLAG_DELTA_Y))
g_string_append_printf (s, " Delta %.2f %.2f" , history[i].axes[GDK_AXIS_DELTA_X], history[i].axes[GDK_AXIS_DELTA_Y]);
for (int j = GDK_AXIS_PRESSURE; j < GDK_AXIS_LAST; j++)
{
if (history[i].flags & (1 << j))
g_string_append_printf (s, " %s %.2f" , axis_name (j), history[i].axes[j]);
}
}
add_text_row (store, "History" , "%s" , s->str);
g_string_free (s, TRUE );
g_free (history);
}
}
temp = make_event_node (event, node, NULL);
if (temp)
{
for (gsize i = 0 ; i < g_list_model_get_n_items (G_LIST_MODEL (store)); i++)
{
ObjectProperty *prop = g_list_model_get_item (G_LIST_MODEL (store), i);
prop->node = gsk_render_node_ref (temp);
g_object_unref (prop);
}
}
g_clear_pointer (&temp, gsk_render_node_unref);
temp = make_event_node (event, node, &recording->bounds);
list_store_add_object_property (store, "Target" , g_type_name (target_type), NULL, temp);
g_clear_pointer (&temp, gsk_render_node_unref);
if (n_traces > 0 )
{
const char *phase_name[] = { "" , "↘" , "↙" , "⊙" };
for (gsize i = 0 ; i < n_traces; i++)
{
EventTrace *t = &traces[i];
char val[256 ];
g_snprintf (val, sizeof (val), "%s %s %s %s" ,
phase_name[t->phase],
g_type_name (t->widget_type),
g_type_name (t->controller_type),
t->handled ? "✓" : "" );
temp = make_event_node (event, node, &traces[i].bounds);
list_store_add_object_property (store, i == 0 ? "Trace" : "" , val, NULL, temp);
g_clear_pointer (&temp, gsk_render_node_unref);
}
}
}
static GskRenderNode *
get_selected_node (GtkInspectorRecorder *recorder)
{
GtkTreeListRow *row_item;
GtkInspectorNodeWrapper *wrapper;
GskRenderNode *node;
row_item = gtk_single_selection_get_selected_item (recorder->render_node_selection);
if (row_item == NULL)
return NULL;
wrapper = gtk_tree_list_row_get_item (row_item);
node = gtk_inspector_node_wrapper_get_profile_node (wrapper);
if (node == NULL)
node = gtk_inspector_node_wrapper_get_node (wrapper);
g_object_unref (wrapper);
return node;
}
static void
render_node_list_selection_changed (GtkSingleSelection *selection,
GParamSpec *pspec,
GtkInspectorRecorder *recorder)
{
GskRenderNode *draw_node;
GdkPaintable *paintable;
GtkTreeListRow *row_item;
GtkInspectorNodeWrapper *wrapper;
graphene_rect_t bounds;
row_item = gtk_single_selection_get_selected_item (recorder->render_node_selection);
gtk_widget_set_sensitive (recorder->render_node_save_button, row_item != NULL);
gtk_widget_set_sensitive (recorder->render_node_clip_button, row_item != NULL);
if (row_item == NULL)
return ;
wrapper = gtk_tree_list_row_get_item (row_item);
draw_node = gtk_inspector_node_wrapper_render (wrapper,
gtk_drop_down_get_selected (GTK_DROP_DOWN (recorder->rendering_mode_dropdown)));
if (draw_node == NULL)
draw_node = gsk_render_node_ref (gtk_inspector_node_wrapper_get_draw_node (wrapper));
gsk_render_node_get_bounds (draw_node, &bounds);
paintable = gtk_render_node_paintable_new (draw_node, &bounds);
gtk_picture_set_paintable (GTK_PICTURE (recorder->render_node_view), paintable);
populate_render_node_properties (recorder->render_node_properties,
gtk_inspector_node_wrapper_get_node (wrapper),
gtk_inspector_node_wrapper_get_role (wrapper),
gtk_inspector_node_wrapper_get_profile (wrapper));
g_object_unref (paintable);
gsk_render_node_unref (draw_node);
}
static void
event_properties_list_selection_changed (GtkSelectionModel *model,
GParamSpec *pspec,
GtkInspectorRecorder *recorder)
{
ObjectProperty *prop;
prop = gtk_single_selection_get_selected_item (recorder->event_properties_selection);
if (prop == NULL)
return ;
if (prop->node)
show_render_node (recorder, prop->node, NULL);
}
static void
render_node_save_response (GObject *source,
GAsyncResult *result,
gpointer data)
{
GtkFileDialog *dialog = GTK_FILE_DIALOG (source);
GskRenderNode *node = data;
GFile *file;
GError *error = NULL;
file = gtk_file_dialog_save_finish (dialog, result, &error);
if (file)
{
GBytes *bytes = gsk_render_node_serialize (node);
if (!g_file_replace_contents (file,
g_bytes_get_data (bytes, NULL),
g_bytes_get_size (bytes),
NULL,
FALSE ,
0 ,
NULL,
NULL,
&error))
{
GtkAlertDialog *alert;
alert = gtk_alert_dialog_new (_("Saving RenderNode failed" ));
gtk_alert_dialog_set_detail (alert, error->message);
gtk_alert_dialog_show (alert, GTK_WINDOW (gtk_window_get_transient_for (GTK_WINDOW (dialog))));
g_object_unref (alert);
g_error_free (error);
}
g_bytes_unref (bytes);
g_object_unref (file);
}
else
{
g_print ("Error saving nodes: %s\n" , error->message);
g_error_free (error);
}
}
static void
render_node_save (GtkButton *button,
GtkInspectorRecorder *recorder)
{
GskRenderNode *node;
GtkFileDialog *dialog;
char *filename, *nodename;
node = get_selected_node (recorder);
if (node == NULL)
return ;
nodename = node_name (node);
filename = g_strdup_printf ("%s.node" , nodename);
dialog = gtk_file_dialog_new ();
gtk_file_dialog_set_initial_name (dialog, filename);
gtk_file_dialog_save (dialog,
GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (recorder))),
NULL,
render_node_save_response, node);
g_object_unref (dialog);
g_free (filename);
g_free (nodename);
}
static void
render_node_clip (GtkButton *button,
GtkInspectorRecorder *recorder)
{
GskRenderNode *node;
GdkClipboard *clipboard;
node = get_selected_node (recorder);
if (node == NULL)
return ;
clipboard = gtk_widget_get_clipboard (GTK_WIDGET (recorder));
gdk_clipboard_set (clipboard, GSK_TYPE_RENDER_NODE, node);
}
static void
rendering_mode_update (GtkWidget *dropdown,
GParamSpec *pspec,
GtkInspectorRecorder *recorder)
{
render_node_list_selection_changed (recorder->render_node_selection, NULL, recorder);
}
static void
setup_widget_for_recording (GtkListItemFactory *factory,
GtkListItem *item,
gpointer data)
{
GtkWidget *row, *box, *label;
row = g_object_new (GTK_TYPE_INSPECTOR_RECORDER_ROW, NULL);
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6 );
label = gtk_label_new ("" );
gtk_label_set_xalign (GTK_LABEL (label), 0 .0 f);
gtk_widget_set_hexpand (label, TRUE );
gtk_box_append (GTK_BOX (box), label);
label = gtk_label_new ("" );
gtk_box_append (GTK_BOX (box), label);
gtk_widget_set_margin_start (box, 6 );
gtk_widget_set_margin_end (box, 6 );
gtk_widget_set_margin_top (box, 6 );
gtk_widget_set_margin_bottom (box, 6 );
gtk_widget_set_parent (box, row);
gtk_list_item_set_child (item, row);
}
static char *
get_event_summary (GdkEvent *event)
{
double x, y;
GdkEventType type;
const char *name;
gdk_event_get_position (event, &x, &y);
type = gdk_event_get_event_type (event);
name = event_type_name (type);
switch (type)
{
case GDK_ENTER_NOTIFY:
case GDK_LEAVE_NOTIFY:
case GDK_MOTION_NOTIFY:
case GDK_DRAG_ENTER:
case GDK_DRAG_LEAVE:
case GDK_DRAG_MOTION:
case GDK_DROP_START:
case GDK_TOUCH_BEGIN:
case GDK_TOUCH_UPDATE:
case GDK_TOUCH_END:
case GDK_TOUCH_CANCEL:
case GDK_TOUCHPAD_SWIPE:
case GDK_TOUCHPAD_PINCH:
case GDK_TOUCHPAD_HOLD:
case GDK_BUTTON_PRESS:
case GDK_BUTTON_RELEASE:
return g_strdup_printf ("%s (%.2f %.2f)" , name, x, y);
case GDK_KEY_PRESS:
case GDK_KEY_RELEASE:
{
char *tmp, *ret;
tmp = key_event_string (event);
ret = g_strdup_printf ("%s %s\n" , name, tmp);
g_free (tmp);
return ret;
}
case GDK_FOCUS_CHANGE:
return g_strdup_printf ("%s %s" , name, gdk_focus_event_get_in (event) ? "In" : "Out" );
case GDK_GRAB_BROKEN:
case GDK_PROXIMITY_IN:
case GDK_PROXIMITY_OUT:
case GDK_PAD_BUTTON_PRESS:
case GDK_PAD_BUTTON_RELEASE:
case GDK_PAD_RING:
case GDK_PAD_STRIP:
case GDK_PAD_DIAL:
case GDK_PAD_GROUP_MODE:
case GDK_DELETE:
return g_strdup_printf ("%s" , name);
case GDK_SCROLL:
if (gdk_scroll_event_get_direction (event) == GDK_SCROLL_SMOOTH)
{
gdk_scroll_event_get_deltas (event, &x, &y);
return g_strdup_printf ("%s %.2f %.2f" , name, x, y);
}
else
{
return g_strdup_printf ("%s %s" , name, scroll_direction_name (gdk_scroll_event_get_direction (event)));
}
break ;
case GDK_EVENT_LAST:
default :
g_assert_not_reached ();
}
}
static void
bind_widget_for_recording (GtkListItemFactory *factory,
GtkListItem *item,
gpointer data)
{
GtkInspectorRecorder *recorder = GTK_INSPECTOR_RECORDER (data);
GtkInspectorRecording *recording = gtk_list_item_get_item (item);
GtkWidget *row, *box, *label, *label2;
char *text;
row = gtk_list_item_get_child (item);
box = gtk_widget_get_first_child (row);
label = gtk_widget_get_first_child (box);
label2 = gtk_widget_get_next_sibling (label);
g_object_set (row, "sequence" , NULL, NULL);
g_object_bind_property (recorder, "selected-sequence" , row, "match-sequence" , G_BINDING_SYNC_CREATE);
gtk_label_set_use_markup (GTK_LABEL (label), FALSE );
if (GTK_INSPECTOR_IS_RENDER_RECORDING (recording))
{
gtk_label_set_label (GTK_LABEL (label), _("Frame" ));
gtk_label_set_use_markup (GTK_LABEL (label), FALSE );
text = g_strdup_printf ("%.3f" , gtk_inspector_recording_get_timestamp (recording) / 1000 .0 );
gtk_label_set_label (GTK_LABEL (label2), text);
g_free (text);
}
else if (GTK_INSPECTOR_IS_EVENT_RECORDING (recording))
{
GdkEvent *event = gtk_inspector_event_recording_get_event (GTK_INSPECTOR_EVENT_RECORDING (recording));
g_object_set (row, "sequence" , gdk_event_get_event_sequence (event), NULL);
text = get_event_summary (event);
gtk_label_set_label (GTK_LABEL (label), text);
g_free (text);
text = g_strdup_printf ("%.3f" , gtk_inspector_recording_get_timestamp (recording) / 1000 .0 );
gtk_label_set_label (GTK_LABEL (label2), text);
g_free (text);
}
else
{
gtk_label_set_label (GTK_LABEL (label), _("<b>Start of Recording</b>" ));
gtk_label_set_use_markup (GTK_LABEL (label), TRUE );
gtk_label_set_label (GTK_LABEL (label2), "" );
}
}
static void
recorder_set_dark (GtkInspectorRecorder *recorder,
gboolean dark)
{
GtkWidget *picture = recorder->render_node_view;
if (recorder->dark == dark)
return ;
recorder->dark = dark;
if (dark)
{
gtk_widget_add_css_class (picture, "dark" );
gtk_widget_remove_css_class (picture, "light" );
}
else
{
gtk_widget_remove_css_class (picture, "dark" );
gtk_widget_add_css_class (picture, "light" );
}
g_object_notify_by_pspec (G_OBJECT (recorder), props[PROP_DARK]);
}
static void
gtk_inspector_recorder_get_property (GObject *object,
guint param_id,
GValue *value,
GParamSpec *pspec)
{
GtkInspectorRecorder *recorder = GTK_INSPECTOR_RECORDER (object);
switch (param_id)
{
case PROP_RECORDING:
g_value_set_boolean (value, recorder->recording != NULL);
break ;
case PROP_RECORD_EVENTS:
g_value_set_boolean (value, recorder->record_events);
break ;
case PROP_DEBUG_NODES:
g_value_set_boolean (value, recorder->debug_nodes);
break ;
case PROP_HIGHLIGHT_SEQUENCES:
g_value_set_boolean (value, recorder->highlight_sequences);
break ;
case PROP_SELECTED_SEQUENCE:
g_value_set_pointer (value, recorder->selected_sequence);
break ;
case PROP_DARK:
g_value_set_boolean (value, recorder->dark);
break ;
default :
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
break ;
}
}
static void
gtk_inspector_recorder_set_property (GObject *object,
guint param_id,
const GValue *value,
GParamSpec *pspec)
{
GtkInspectorRecorder *recorder = GTK_INSPECTOR_RECORDER (object);
switch (param_id)
{
case PROP_RECORDING:
gtk_inspector_recorder_set_recording (recorder, g_value_get_boolean (value));
break ;
case PROP_RECORD_EVENTS:
recorder->record_events = g_value_get_boolean (value);
break ;
case PROP_DEBUG_NODES:
gtk_inspector_recorder_set_debug_nodes (recorder, g_value_get_boolean (value));
break ;
case PROP_HIGHLIGHT_SEQUENCES:
gtk_inspector_recorder_set_highlight_sequences (recorder, g_value_get_boolean (value));
break ;
case PROP_SELECTED_SEQUENCE:
recorder->selected_sequence = g_value_get_pointer (value);
break ;
case PROP_DARK:
recorder_set_dark (recorder, g_value_get_boolean (value));
break ;
default :
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
break ;
}
}
static void
gtk_inspector_recorder_dispose (GObject *object)
{
GtkInspectorRecorder *recorder = GTK_INSPECTOR_RECORDER (object);
g_clear_object (&recorder->render_node_model);
g_clear_object (&recorder->render_node_root_model);
g_clear_object (&recorder->render_node_selection);
g_clear_object (&recorder->event_properties_selection);
gtk_widget_dispose_template (GTK_WIDGET (recorder), GTK_TYPE_INSPECTOR_RECORDER);
g_clear_object (&recorder->settings);
G_OBJECT_CLASS (gtk_inspector_recorder_parent_class)->dispose (object);
}
static void
gtk_inspector_recorder_class_init (GtkInspectorRecorderClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->get_property = gtk_inspector_recorder_get_property;
object_class->set_property = gtk_inspector_recorder_set_property;
object_class->dispose = gtk_inspector_recorder_dispose;
props[PROP_RECORDING] = g_param_spec_boolean ("recording" , NULL, NULL, FALSE , G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
props[PROP_RECORD_EVENTS] = g_param_spec_boolean ("record-events" , NULL, NULL, TRUE , G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
props[PROP_DEBUG_NODES] = g_param_spec_boolean ("debug-nodes" , NULL, NULL, FALSE , G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
props[PROP_HIGHLIGHT_SEQUENCES] = g_param_spec_boolean ("highlight-sequences" , NULL, NULL, FALSE , G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
props[PROP_SELECTED_SEQUENCE] = g_param_spec_pointer ("selected-sequence" , NULL, NULL, G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
props[PROP_DARK] = g_param_spec_boolean ("dark" , NULL, NULL, FALSE , G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
g_object_class_install_properties (object_class, LAST_PROP, props);
gtk_widget_class_install_property_action (widget_class, "record.record-events" , "record-events" );
gtk_widget_class_install_property_action (widget_class, "record.debug-nodes" , "debug-nodes" );
gtk_widget_class_install_property_action (widget_class, "record.highlight-sequences" , "highlight-sequences" );
gtk_widget_class_install_property_action (widget_class, "record.toggle-dark" , "dark" );
gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/inspector/recorder.ui" );
gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, box);
gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, recordings);
gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, recordings_list);
gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, render_node_view);
gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, render_node_list);
gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, render_node_save_button);
gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, render_node_clip_button);
gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, rendering_mode_dropdown);
gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, node_property_tree);
gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, recording_data_stack);
gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, event_view);
gtk_widget_class_bind_template_child (widget_class, GtkInspectorRecorder, event_property_tree);
gtk_widget_class_bind_template_callback (widget_class, recordings_clear_all);
gtk_widget_class_bind_template_callback (widget_class, recording_selected);
gtk_widget_class_bind_template_callback (widget_class, render_node_save);
gtk_widget_class_bind_template_callback (widget_class, render_node_clip);
gtk_widget_class_bind_template_callback (widget_class, rendering_mode_update);
//gtk_widget_class_bind_template_callback (widget_class, node_property_activated);
gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
}
static void
gtk_inspector_recorder_init (GtkInspectorRecorder *recorder)
{
GtkListItemFactory *factory;
GtkSelectionModel *model;
GtkColumnViewColumn *column;
GSettingsSchema *schema;
recorder->record_events = TRUE ;
gtk_widget_init_template (GTK_WIDGET (recorder));
if (GSK_DEBUG_CHECK (PROFILE))
gtk_widget_set_sensitive (recorder->rendering_mode_dropdown, TRUE );
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup" , G_CALLBACK (setup_widget_for_recording), recorder);
g_signal_connect (factory, "bind" , G_CALLBACK (bind_widget_for_recording), recorder);
gtk_list_view_set_factory (GTK_LIST_VIEW (recorder->recordings_list), factory);
g_object_unref (factory);
recorder->render_node_root_model = g_list_store_new (GTK_TYPE_INSPECTOR_NODE_WRAPPER);
recorder->render_node_model = gtk_tree_list_model_new (g_object_ref (G_LIST_MODEL (recorder->render_node_root_model)),
FALSE ,
TRUE ,
(GtkTreeListModelCreateModelFunc) gtk_inspector_node_wrapper_create_children_model,
NULL, NULL);
recorder->render_node_selection = gtk_single_selection_new (g_object_ref (G_LIST_MODEL (recorder->render_node_model)));
g_signal_connect (recorder->render_node_selection, "notify::selected-item" , G_CALLBACK (render_node_list_selection_changed), recorder);
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup" , G_CALLBACK (setup_widget_for_render_node), NULL);
g_signal_connect (factory, "bind" , G_CALLBACK (bind_widget_for_render_node), NULL);
gtk_list_view_set_factory (GTK_LIST_VIEW (recorder->render_node_list), factory);
g_object_unref (factory);
gtk_list_view_set_model (GTK_LIST_VIEW (recorder->render_node_list),
GTK_SELECTION_MODEL (recorder->render_node_selection));
recorder->render_node_properties = g_list_store_new (object_property_get_type ());
model = GTK_SELECTION_MODEL (gtk_no_selection_new (G_LIST_MODEL (recorder->render_node_properties)));
gtk_column_view_set_model (GTK_COLUMN_VIEW (recorder->node_property_tree), model);
g_object_unref (model);
column = g_list_model_get_item (gtk_column_view_get_columns (GTK_COLUMN_VIEW (recorder->node_property_tree)), 0 );
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup" , G_CALLBACK (setup_label), NULL);
g_signal_connect (factory, "bind" , G_CALLBACK (bind_property_name), NULL);
gtk_column_view_column_set_factory (column, factory);
g_object_unref (factory);
g_object_unref (column);
column = g_list_model_get_item (gtk_column_view_get_columns (GTK_COLUMN_VIEW (recorder->node_property_tree)), 1 );
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup" , G_CALLBACK (setup_value_widgets), NULL);
g_signal_connect (factory, "bind" , G_CALLBACK (bind_value_widgets), NULL);
gtk_column_view_column_set_factory (column, factory);
g_object_unref (factory);
g_object_unref (column);
recorder->event_properties = g_list_store_new (object_property_get_type ());
recorder->event_properties_selection = gtk_single_selection_new (G_LIST_MODEL (recorder->event_properties));
g_signal_connect (recorder->event_properties_selection, "notify::selected-item" , G_CALLBACK (event_properties_list_selection_changed), recorder);
gtk_column_view_set_model (GTK_COLUMN_VIEW (recorder->event_property_tree), GTK_SELECTION_MODEL (recorder->event_properties_selection));
column = g_list_model_get_item (gtk_column_view_get_columns (GTK_COLUMN_VIEW (recorder->event_property_tree)), 0 );
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup" , G_CALLBACK (setup_label), NULL);
g_signal_connect (factory, "bind" , G_CALLBACK (bind_property_name), NULL);
gtk_column_view_column_set_factory (column, factory);
g_object_unref (factory);
g_object_unref (column);
column = g_list_model_get_item (gtk_column_view_get_columns (GTK_COLUMN_VIEW (recorder->event_property_tree)), 1 );
factory = gtk_signal_list_item_factory_new ();
g_signal_connect (factory, "setup" , G_CALLBACK (setup_value_widgets), NULL);
g_signal_connect (factory, "bind" , G_CALLBACK (bind_value_widgets), NULL);
gtk_column_view_column_set_factory (column, factory);
g_object_unref (factory);
g_object_unref (column);
schema = g_settings_schema_source_lookup (g_settings_schema_source_get_default (),
"org.gtk.gtk4.Inspector.Recorder" ,
TRUE );
if (schema)
{
recorder->settings = g_settings_new_full (schema, NULL, NULL);
g_settings_bind (recorder->settings, "debug-nodes" , recorder, "debug-nodes" , G_SETTINGS_BIND_DEFAULT);
g_settings_bind (recorder->settings, "record-events" , recorder, "record-events" , G_SETTINGS_BIND_DEFAULT);
g_settings_bind (recorder->settings, "highlight-sequences" , recorder, "highlight-sequences" , G_SETTINGS_BIND_DEFAULT);
g_settings_bind (recorder->settings, "dark" , recorder, "dark" , G_SETTINGS_BIND_DEFAULT);
g_settings_schema_unref (schema);
}
}
static void
gtk_inspector_recorder_add_recording (GtkInspectorRecorder *recorder,
GtkInspectorRecording *recording)
{
g_list_store_append (G_LIST_STORE (recorder->recordings), recording);
}
void
gtk_inspector_recorder_set_recording (GtkInspectorRecorder *recorder,
gboolean recording)
{
if (gtk_inspector_recorder_is_recording (recorder) == recording)
return ;
if (recording)
{
recorder->recording = gtk_inspector_start_recording_new ();
recorder->start_time = 0 ;
gtk_inspector_recorder_add_recording (recorder, recorder->recording);
}
else
{
g_clear_object (&recorder->recording);
}
g_object_notify_by_pspec (G_OBJECT (recorder), props[PROP_RECORDING]);
}
void
gtk_inspector_recorder_record_single_frame (GtkInspectorRecorder *recorder,
gboolean save_to_file)
{
if (gtk_inspector_recorder_is_recording (recorder))
return ;
recorder->recording = gtk_inspector_start_recording_new ();
recorder->start_time = 0 ;
recorder->record_events = FALSE ;
recorder->stop_after_next_frame = TRUE ;
recorder->save_to_file = save_to_file;
gtk_inspector_recorder_add_recording (recorder, recorder->recording);
}
gboolean
gtk_inspector_recorder_is_recording (GtkInspectorRecorder *recorder)
{
return recorder->recording != NULL;
}
static gboolean
gtk_inspector_recorder_is_recording_events (GtkInspectorRecorder *recorder)
{
return recorder->recording != NULL && recorder->record_events;
}
void
gtk_inspector_recorder_record_render (GtkInspectorRecorder *recorder,
GtkWidget *widget,
GskRenderer *renderer,
GdkSurface *surface,
const cairo_region_t *region,
GskRenderNode *node)
{
GtkInspectorRecording *recording;
GdkFrameClock *frame_clock;
gint64 frame_time;
if (!gtk_inspector_recorder_is_recording (recorder))
return ;
frame_clock = gtk_widget_get_frame_clock (widget);
frame_time = gdk_frame_clock_get_frame_time (frame_clock);
if (recorder->start_time == 0 )
{
recorder->start_time = frame_time;
frame_time = 0 ;
}
else
{
frame_time = frame_time - recorder->start_time;
}
recording = gtk_inspector_render_recording_new (frame_time,
&(GdkRectangle) { 0 , 0 ,
gdk_surface_get_width (surface),
gdk_surface_get_height (surface) },
region,
node,
surface);
gtk_inspector_recorder_add_recording (recorder, recording);
g_object_unref (recording);
if (recorder->stop_after_next_frame)
{
GdkClipboard *clipboard;
recorder->stop_after_next_frame = FALSE ;
gtk_inspector_recorder_set_recording (recorder, FALSE );
clipboard = gtk_widget_get_clipboard (GTK_WIDGET (recorder));
gdk_clipboard_set (clipboard, GSK_TYPE_RENDER_NODE, node);
if (recorder->save_to_file)
{
char *filename;
static int counter = 0 ;
filename = g_strdup_printf ("gtk-frame%d.node" , counter++);
gsk_render_node_write_to_file (node, filename, NULL);
g_free (filename);
}
}
}
void
gtk_inspector_recorder_record_event (GtkInspectorRecorder *recorder,
GtkWidget *widget,
GdkEvent *event)
{
GtkInspectorRecording *recording;
GdkFrameClock *frame_clock;
gint64 frame_time;
if (!gtk_inspector_recorder_is_recording_events (recorder))
return ;
frame_clock = gtk_widget_get_frame_clock (widget);
frame_time = gdk_frame_clock_get_frame_time (frame_clock);
if (recorder->start_time == 0 )
{
recorder->start_time = frame_time;
frame_time = 0 ;
}
else
{
frame_time = frame_time - recorder->start_time;
}
recording = gtk_inspector_event_recording_new (frame_time, event);
gtk_inspector_recorder_add_recording (recorder, recording);
recorder->last_event_recording = (GtkInspectorEventRecording *) recording;
g_object_unref (recording);
}
void
gtk_inspector_recorder_trace_event (GtkInspectorRecorder *recorder,
GdkEvent *event,
GtkPropagationPhase phase,
GtkWidget *widget,
GtkEventController *controller,
GtkWidget *target,
gboolean handled)
{
GtkInspectorEventRecording *recording = recorder->last_event_recording;
if (!gtk_inspector_recorder_is_recording_events (recorder))
return ;
if (recording == NULL || recording->event != event)
return ;
gtk_inspector_event_recording_add_trace (recording, phase, widget, controller, target, handled);
}
void
gtk_inspector_recorder_set_debug_nodes (GtkInspectorRecorder *recorder,
gboolean debug_nodes)
{
guint flags;
if (recorder->debug_nodes == debug_nodes)
return ;
recorder->debug_nodes = debug_nodes;
flags = gtk_get_debug_flags ();
if (debug_nodes)
flags |= GTK_DEBUG_SNAPSHOT;
else
flags &= ~GTK_DEBUG_SNAPSHOT;
gtk_set_debug_flags (flags);
g_object_notify_by_pspec (G_OBJECT (recorder), props[PROP_DEBUG_NODES]);
}
void
gtk_inspector_recorder_set_highlight_sequences (GtkInspectorRecorder *recorder,
gboolean highlight_sequences)
{
GdkEventSequence *sequence = NULL;
if (recorder->highlight_sequences == highlight_sequences)
return ;
recorder->highlight_sequences = highlight_sequences;
if (highlight_sequences)
{
GtkSingleSelection *selection;
GtkInspectorRecording *recording;
GdkEvent *event;
selection = GTK_SINGLE_SELECTION (gtk_list_view_get_model (GTK_LIST_VIEW (recorder->recordings_list)));
recording = gtk_single_selection_get_selected_item (selection);
if (GTK_INSPECTOR_IS_EVENT_RECORDING (recording))
{
event = gtk_inspector_event_recording_get_event (GTK_INSPECTOR_EVENT_RECORDING (recording));
sequence = gdk_event_get_event_sequence (event);
}
}
gtk_inspector_recorder_set_selected_sequence (recorder, sequence);
g_object_notify_by_pspec (G_OBJECT (recorder), props[PROP_HIGHLIGHT_SEQUENCES]);
}
void
gtk_inspector_recorder_set_selected_sequence (GtkInspectorRecorder *recorder,
GdkEventSequence *sequence)
{
if (recorder->selected_sequence == sequence)
return ;
recorder->selected_sequence = sequence;
g_object_notify_by_pspec (G_OBJECT (recorder), props[PROP_SELECTED_SEQUENCE]);
}
void
gtk_inspector_recorder_add_profile_node (GtkInspectorRecorder *self,
GskRenderNode *node,
GskRenderNode *profile_node)
{
guint i;
for (i = g_list_model_get_n_items (self->recordings);
i-- > 0 ;)
{
GtkInspectorRecording *rec = g_list_model_get_item (self->recordings, i);
if (!GTK_INSPECTOR_IS_RENDER_RECORDING (rec))
{
g_object_unref (rec);
continue ;
}
if (node == gtk_inspector_render_recording_get_node (GTK_INSPECTOR_RENDER_RECORDING (rec)))
gtk_inspector_render_recording_set_profile_node (GTK_INSPECTOR_RENDER_RECORDING (rec), profile_node);
g_object_unref (rec);
}
}
/* vim:set foldmethod=marker: */
Messung V0.5 in Prozent C=98 H=95 G=96
¤ Dauer der Verarbeitung: 0.50 Sekunden
(vorverarbeitet am 2026-07-02)
¤
*© Formatika GbR, Deutschland