1 | #include <gtk/gtk.h> |
2 | |
3 | static char *write_to_filename = NULL; |
4 | static gboolean compare_node; |
5 | |
6 | static GOptionEntry options[] = { |
7 | { "write" , 'o', 0, G_OPTION_ARG_STRING, &write_to_filename, "Write PNG file" , NULL }, |
8 | { "compare" , 'c', 0, G_OPTION_ARG_NONE, &compare_node, "Compare render to render_texture" , NULL }, |
9 | { NULL } |
10 | }; |
11 | |
12 | |
13 | |
14 | typedef struct _GtkNodeView GtkNodeView; |
15 | typedef struct _GtkNodeViewClass GtkNodeViewClass; |
16 | |
17 | #define GTK_TYPE_NODE_VIEW (gtk_node_view_get_type ()) |
18 | #define GTK_NODE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj, GTK_TYPE_NODE_VIEW, GtkNodeView)) |
19 | #define GTK_NODE_VIEW_CLASS(cls) (G_TYPE_CHECK_CLASS_CAST(cls, GTK_TYPE_NODE_VIEW, GtkNodeViewClass)) |
20 | struct _GtkNodeView |
21 | { |
22 | GtkWidget parent_instance; |
23 | |
24 | GskRenderNode *node; |
25 | GFileMonitor *file_monitor; |
26 | }; |
27 | |
28 | struct _GtkNodeViewClass |
29 | { |
30 | GtkWidgetClass parent_class; |
31 | }; |
32 | |
33 | GType gtk_node_view_get_type (void) G_GNUC_CONST; |
34 | |
35 | |
36 | G_DEFINE_TYPE(GtkNodeView, gtk_node_view, GTK_TYPE_WIDGET) |
37 | |
38 | static void |
39 | deserialize_error_func (const GskParseLocation *start, |
40 | const GskParseLocation *end, |
41 | const GError *error, |
42 | gpointer user_data) |
43 | { |
44 | GString *string = g_string_new (init: "<data>" ); |
45 | |
46 | g_string_append_printf (string, format: ":%zu:%zu" , |
47 | start->lines + 1, start->line_chars + 1); |
48 | if (start->lines != end->lines || start->line_chars != end->line_chars) |
49 | { |
50 | g_string_append (string, val: "-" ); |
51 | if (start->lines != end->lines) |
52 | g_string_append_printf (string, format: "%zu:" , end->lines + 1); |
53 | g_string_append_printf (string, format: "%zu" , end->line_chars + 1); |
54 | } |
55 | |
56 | g_warning ("Error at %s: %s" , string->str, error->message); |
57 | |
58 | g_string_free (string, TRUE); |
59 | } |
60 | |
61 | static void |
62 | load_file_contents (GtkNodeView *self, |
63 | GFile *file) |
64 | { |
65 | GBytes *bytes; |
66 | GError *error = NULL; |
67 | |
68 | bytes = g_file_load_bytes (file, NULL, NULL, NULL); |
69 | if (bytes == NULL) |
70 | return; |
71 | |
72 | if (!g_utf8_validate (str: g_bytes_get_data (bytes, NULL), max_len: g_bytes_get_size (bytes), NULL)) |
73 | { |
74 | g_bytes_unref (bytes); |
75 | return; |
76 | } |
77 | |
78 | self->node = gsk_render_node_deserialize (bytes, error_func: deserialize_error_func, user_data: &error); |
79 | |
80 | if (error) |
81 | { |
82 | g_critical ("Invalid node file: %s" , error->message); |
83 | g_clear_error (err: &error); |
84 | return; |
85 | } |
86 | |
87 | gtk_widget_queue_draw (GTK_WIDGET (self)); |
88 | |
89 | g_bytes_unref (bytes); |
90 | } |
91 | |
92 | static void |
93 | file_changed_cb (GFileMonitor *monitor, |
94 | GFile *file, |
95 | GFile *other_file, |
96 | GFileMonitorEvent event_type, |
97 | gpointer user_data) |
98 | { |
99 | GtkNodeView *self = user_data; |
100 | |
101 | if (event_type == G_FILE_MONITOR_EVENT_CHANGED) |
102 | load_file_contents (self, file); |
103 | } |
104 | |
105 | static void |
106 | gtk_node_view_measure (GtkWidget *widget, |
107 | GtkOrientation orientation, |
108 | int for_size, |
109 | int *minimum, |
110 | int *natural, |
111 | int *minimum_baseline, |
112 | int *natural_baseline) |
113 | { |
114 | GtkNodeView *self = GTK_NODE_VIEW (widget); |
115 | graphene_rect_t bounds; |
116 | |
117 | |
118 | if (self->node == NULL) |
119 | return; |
120 | |
121 | gsk_render_node_get_bounds (node: self->node, bounds: &bounds); |
122 | |
123 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
124 | { |
125 | *minimum = *natural = bounds.origin.x + bounds.size.width; |
126 | } |
127 | else /* VERTICAL */ |
128 | { |
129 | *minimum = *natural = bounds.origin.y + bounds.size.height; |
130 | } |
131 | } |
132 | |
133 | static void |
134 | gtk_node_view_snapshot (GtkWidget *widget, |
135 | GtkSnapshot *snapshot) |
136 | { |
137 | GtkNodeView *self = GTK_NODE_VIEW (widget); |
138 | |
139 | if (self->node != NULL) |
140 | gtk_snapshot_append_node (snapshot, node: self->node); |
141 | } |
142 | |
143 | static void |
144 | gtk_node_view_finalize (GObject *object) |
145 | { |
146 | GtkNodeView *self = GTK_NODE_VIEW (object); |
147 | |
148 | if (self->node) |
149 | gsk_render_node_unref (node: self->node); |
150 | |
151 | G_OBJECT_CLASS (gtk_node_view_parent_class)->finalize (object); |
152 | } |
153 | |
154 | static void |
155 | gtk_node_view_init (GtkNodeView *self) |
156 | { |
157 | gtk_widget_set_overflow (GTK_WIDGET (self), overflow: GTK_OVERFLOW_HIDDEN); |
158 | } |
159 | |
160 | static void |
161 | gtk_node_view_class_init (GtkNodeViewClass *klass) |
162 | { |
163 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
164 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); |
165 | |
166 | object_class->finalize = gtk_node_view_finalize; |
167 | |
168 | widget_class->measure = gtk_node_view_measure; |
169 | widget_class->snapshot = gtk_node_view_snapshot; |
170 | } |
171 | |
172 | static void |
173 | quit_cb (GtkWidget *widget, |
174 | gpointer data) |
175 | { |
176 | gboolean *done = data; |
177 | |
178 | *done = TRUE; |
179 | |
180 | g_main_context_wakeup (NULL); |
181 | } |
182 | |
183 | int |
184 | main (int argc, char **argv) |
185 | { |
186 | GtkWidget *window; |
187 | GtkWidget *nodeview; |
188 | graphene_rect_t node_bounds; |
189 | GOptionContext *option_context; |
190 | GError *error = NULL; |
191 | gboolean done = FALSE; |
192 | GFile *file; |
193 | |
194 | option_context = g_option_context_new (parameter_string: "NODE-FILE [-o OUTPUT] [--compare]" ); |
195 | g_option_context_add_main_entries (context: option_context, entries: options, NULL); |
196 | |
197 | if (argc < 2) |
198 | { |
199 | printf (format: "Usage: showrendernode NODEFILE [-o OUTPUT] [--compare]\n" ); |
200 | return 0; |
201 | } |
202 | |
203 | if (!g_option_context_parse (context: option_context, argc: &argc, argv: &argv, error: &error)) |
204 | { |
205 | g_printerr (format: "Option parsing failed: %s\n" , error->message); |
206 | return 1; |
207 | } |
208 | |
209 | g_option_context_free (context: option_context); |
210 | option_context = NULL; |
211 | |
212 | g_message ("Compare: %d, write to filename: %s" , compare_node, write_to_filename); |
213 | |
214 | gtk_init (); |
215 | |
216 | window = gtk_window_new (); |
217 | nodeview = g_object_new (GTK_TYPE_NODE_VIEW, NULL); |
218 | |
219 | gtk_window_set_decorated (GTK_WINDOW (window), FALSE); |
220 | |
221 | file = g_file_new_for_path (path: argv[1]); |
222 | load_file_contents (GTK_NODE_VIEW (nodeview), file); |
223 | GTK_NODE_VIEW (nodeview)->file_monitor = g_file_monitor_file (file, flags: G_FILE_MONITOR_NONE, NULL, error: &error); |
224 | g_object_unref (object: file); |
225 | |
226 | if (error) |
227 | { |
228 | g_warning ("%s" , error->message); |
229 | return -1; |
230 | } |
231 | |
232 | g_signal_connect (GTK_NODE_VIEW (nodeview)->file_monitor, |
233 | "changed" , G_CALLBACK (file_changed_cb), nodeview); |
234 | |
235 | if (write_to_filename != NULL) |
236 | { |
237 | GdkSurface *surface = gdk_surface_new_toplevel (display: gdk_display_get_default()); |
238 | GskRenderer *renderer = gsk_renderer_new_for_surface (surface); |
239 | GdkTexture *texture = gsk_renderer_render_texture (renderer, GTK_NODE_VIEW (nodeview)->node, NULL); |
240 | |
241 | g_message ("Writing .node file to .png using %s" , G_OBJECT_TYPE_NAME (renderer)); |
242 | |
243 | g_assert (texture != NULL); |
244 | |
245 | gdk_texture_save_to_png (texture, filename: write_to_filename); |
246 | |
247 | gsk_renderer_unrealize (renderer); |
248 | |
249 | g_object_unref (object: texture); |
250 | g_object_unref (object: renderer); |
251 | g_object_unref (object: surface); |
252 | } |
253 | |
254 | if (compare_node) |
255 | { |
256 | GtkWidget *box = gtk_box_new (orientation: GTK_ORIENTATION_VERTICAL, spacing: 12); |
257 | GdkSurface *gdk_surface = gdk_surface_new_toplevel (display: gdk_display_get_default()); |
258 | GskRenderer *renderer = gsk_renderer_new_for_surface (surface: gdk_surface); |
259 | GdkTexture *texture = gsk_renderer_render_texture (renderer, GTK_NODE_VIEW (nodeview)->node, NULL); |
260 | GtkWidget *image = gtk_image_new_from_paintable (paintable: GDK_PAINTABLE (ptr: texture)); |
261 | |
262 | gtk_widget_set_size_request (widget: image, |
263 | width: gdk_texture_get_width (texture), |
264 | height: gdk_texture_get_height (texture)); |
265 | |
266 | gtk_box_append (GTK_BOX (box), child: nodeview); |
267 | gtk_box_append (GTK_BOX (box), child: image); |
268 | gtk_window_set_child (GTK_WINDOW (window), child: box); |
269 | |
270 | gsk_renderer_unrealize (renderer); |
271 | g_object_unref (object: texture); |
272 | g_object_unref (object: renderer); |
273 | g_object_unref (object: gdk_surface); |
274 | } |
275 | else |
276 | { |
277 | gtk_window_set_child (GTK_WINDOW (window), child: nodeview); |
278 | } |
279 | |
280 | gsk_render_node_get_bounds (GTK_NODE_VIEW (nodeview)->node, bounds: &node_bounds); |
281 | gtk_window_set_default_size (GTK_WINDOW (window), |
282 | MAX (600, node_bounds.size.width), |
283 | MAX (500, node_bounds.size.height)); |
284 | |
285 | g_signal_connect (window, "destroy" , G_CALLBACK (quit_cb), &done); |
286 | gtk_widget_show (widget: window); |
287 | |
288 | while (!done) |
289 | g_main_context_iteration (NULL, TRUE); |
290 | |
291 | return 0; |
292 | } |
293 | |