From 11f48f3c5bf01cf74837ffd3ed68eca70abf9734 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 12 Aug 2023 15:34:54 -0400 Subject: [PATCH 1/3] Add gsk_path_builder_add_rounded_rect For now, this is using gsk_path_builder_svg_arc_to to approximate elliptical arcs. --- gsk/gskpathbuilder.c | 70 ++++++++++++++++++++++++++++++++++++++++++++ gsk/gskpathbuilder.h | 3 ++ 2 files changed, 73 insertions(+) diff --git a/gsk/gskpathbuilder.c b/gsk/gskpathbuilder.c index dab74ead20..d0cd38a598 100644 --- a/gsk/gskpathbuilder.c +++ b/gsk/gskpathbuilder.c @@ -470,6 +470,76 @@ gsk_path_builder_add_rect (GskPathBuilder *self, self->current_point = current; } +/** + * gsk_path_builder_add_rounded_rect: + * @self: a #GskPathBuilder + * @rect: the rounded rect + * + * Adds @rect as a new contour to the path built in @self. + **/ +void +gsk_path_builder_add_rounded_rect (GskPathBuilder *self, + const GskRoundedRect *rect) +{ + graphene_point_t current; + + g_return_if_fail (self != NULL); + g_return_if_fail (rect != NULL); + + current = self->current_point; + + gsk_path_builder_move_to (self, + rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width, + rect->bounds.origin.y); + /* top */ + gsk_path_builder_line_to (self, + rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_TOP_RIGHT].width, + rect->bounds.origin.y); + /* topright corner */ + gsk_path_builder_svg_arc_to (self, + rect->corner[GSK_CORNER_TOP_RIGHT].width, + rect->corner[GSK_CORNER_TOP_RIGHT].height, + 0, FALSE, TRUE, + rect->bounds.origin.x + rect->bounds.size.width, + rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_RIGHT].height); + /* right */ + gsk_path_builder_line_to (self, + rect->bounds.origin.x + rect->bounds.size.width, + rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_RIGHT].height); + /* bottomright corner */ + gsk_path_builder_svg_arc_to (self, + rect->corner[GSK_CORNER_BOTTOM_RIGHT].width, + rect->corner[GSK_CORNER_BOTTOM_RIGHT].height, + 0, FALSE, TRUE, + rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_BOTTOM_RIGHT].width, + rect->bounds.origin.y + rect->bounds.size.height); + /* bottom */ + gsk_path_builder_line_to (self, + rect->bounds.origin.x + rect->corner[GSK_CORNER_BOTTOM_LEFT].width, + rect->bounds.origin.y + rect->bounds.size.height); + /* bottomleft corner */ + gsk_path_builder_svg_arc_to (self, + rect->corner[GSK_CORNER_BOTTOM_LEFT].width, + rect->corner[GSK_CORNER_BOTTOM_LEFT].height, + 0, FALSE, TRUE, + rect->bounds.origin.x, + rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_LEFT].height); + /* left */ + gsk_path_builder_line_to (self, + rect->bounds.origin.x, + rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_LEFT].height); + /* topleft corner */ + gsk_path_builder_svg_arc_to (self, + rect->corner[GSK_CORNER_TOP_LEFT].width, + rect->corner[GSK_CORNER_TOP_LEFT].height, + 0, FALSE, TRUE, + rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width, + rect->bounds.origin.y); + /* done */ + gsk_path_builder_close (self); + self->current_point = current; +} + static gboolean circle_contour_curve (const graphene_point_t pts[4], gpointer data) diff --git a/gsk/gskpathbuilder.h b/gsk/gskpathbuilder.h index dc5ae03444..5eda90b00a 100644 --- a/gsk/gskpathbuilder.h +++ b/gsk/gskpathbuilder.h @@ -65,6 +65,9 @@ GDK_AVAILABLE_IN_4_14 void gsk_path_builder_add_rect (GskPathBuilder *self, const graphene_rect_t *rect); GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_add_rounded_rect (GskPathBuilder *self, + const GskRoundedRect *rect); +GDK_AVAILABLE_IN_4_14 void gsk_path_builder_add_circle (GskPathBuilder *self, const graphene_point_t *center, float radius); From 44bc5a139cb8ac5ba03feb2e7e32671b729cede6 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 12 Aug 2023 16:09:31 -0400 Subject: [PATCH 2/3] Add rounded rects to the tests --- testsuite/gsk/path-special-cases.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/testsuite/gsk/path-special-cases.c b/testsuite/gsk/path-special-cases.c index 4bfe18c811..da279a83ae 100644 --- a/testsuite/gsk/path-special-cases.c +++ b/testsuite/gsk/path-special-cases.c @@ -601,7 +601,7 @@ test_path_builder_add (void) PangoLayout *layout; GskPathPoint point1, point2; -#define N_ADD_METHODS 7 +#define N_ADD_METHODS 8 path = gsk_path_parse ("M 10 10 L 100 100"); @@ -649,6 +649,20 @@ test_path_builder_add (void) break; case 6: + { + GskRoundedRect rect; + + gsk_rounded_rect_init (&rect, + &GRAPHENE_RECT_INIT (0, 0, 100, 100), + &GRAPHENE_SIZE_INIT (10, 20), + &GRAPHENE_SIZE_INIT (20, 30), + &GRAPHENE_SIZE_INIT (0, 0), + &GRAPHENE_SIZE_INIT (10, 10)); + gsk_path_builder_add_rounded_rect (builder, &rect); + } + break; + + case 7: gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 0), 10); break; From 62176a3bd0550910866adf17f8508fd02da84a97 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 26 Jun 2023 08:41:08 -0400 Subject: [PATCH 3/3] tools: Add gtk4-path-tool This comes in handy for testing, among other things. For now, this supports decomposing, reversing, rendering, info and preview. --- docs/reference/gtk/gtk4-path-tool.rst | 121 ++++++++++++ docs/reference/gtk/meson.build | 1 + tools/gtk-path-tool-decompose.c | 131 +++++++++++++ tools/gtk-path-tool-info.c | 76 ++++++++ tools/gtk-path-tool-render.c | 208 ++++++++++++++++++++ tools/gtk-path-tool-show.c | 232 ++++++++++++++++++++++ tools/gtk-path-tool-utils.c | 139 +++++++++++++ tools/gtk-path-tool.c | 139 +++++++++++++ tools/gtk-path-tool.h | 13 ++ tools/meson.build | 7 + tools/path-view.c | 271 ++++++++++++++++++++++++++ tools/path-view.h | 9 + 12 files changed, 1347 insertions(+) create mode 100644 docs/reference/gtk/gtk4-path-tool.rst create mode 100644 tools/gtk-path-tool-decompose.c create mode 100644 tools/gtk-path-tool-info.c create mode 100644 tools/gtk-path-tool-render.c create mode 100644 tools/gtk-path-tool-show.c create mode 100644 tools/gtk-path-tool-utils.c create mode 100644 tools/gtk-path-tool.c create mode 100644 tools/gtk-path-tool.h create mode 100644 tools/path-view.c create mode 100644 tools/path-view.h diff --git a/docs/reference/gtk/gtk4-path-tool.rst b/docs/reference/gtk/gtk4-path-tool.rst new file mode 100644 index 0000000000..3ee0d31d8d --- /dev/null +++ b/docs/reference/gtk/gtk4-path-tool.rst @@ -0,0 +1,121 @@ +.. _gtk4-path-tool(1): + +================= +gtk4-path-tool +================= + +----------------------- +GskPath Utility +----------------------- + +SYNOPSIS +-------- +| **gtk4-path-tool** [OPTIONS...] +| +| **gtk4-path-tool** decompose [OPTIONS...] +| **gtk4-path-tool** restrict [OPTIONS...] +| **gtk4-path-tool** show [OPTIONS...] +| **gtk4-path-tool** render [OPTIONS...] +| **gtk4-path-tool** info [OPTIONS...] + +DESCRIPTION +----------- + +``gtk4-path-tool`` can perform various tasks on paths. Paths are specified +in SVG syntax, as strings like "M 100 100 C 100 200 200 200 200 100 Z". + +To read a path from a file, use a filename that starts with a '.' or a '/'. +To read a path from stdin, use '-'. + +COMMANDS +-------- + +Decomposing +^^^^^^^^^^^ + +The ``decompose`` command approximates the path by one with simpler elements. +When used without options, the curves of the path are approximated by line +segments. + +``--allow-curves`` + + Allow cubic Bézier curves to be used in the generated path. + +``--allow-conics`` + + Allow rational quadratic Bézier curves to be used in the generated path. + +Restricting +^^^^^^^^^^^ + +The ``restrict`` command creates a path that traces a segment of the original +path. Note that the start and the end of the segment are specified as +path length from the beginning of the path. + +``--start=LENGTH`` + + The distance from the beginning of the path where the segment begins. The + default values is 0. + +``--end=LENGTH`` + + The distance from the beginning of the path where the segment ends. The + default value is the length of path. + +Showing +^^^^^^^ + +The ``show`` command displays the given path in a window. The interior +of the path is filled. + +``--fill-rule=VALUE`` + + The fill rule that is used to determine what areas are inside the path. + The possible values are ``winding`` or ``even-odd``. The default is ``winding``. + +``--fg-color=COLOR`` + + The color that is used to fill the interior of the path. + If not specified, black is used. + +``--bg-color=COLOR`` + + The color that is used to render the background behind the path. + If not specified, white is used. + +Rendering +^^^^^^^^^ + +The ``render`` command renders the given path as a PNG image. +The interior of the path is filled. + +``--fill-rule=VALUE`` + + The fill rule that is used to determine what areas are inside the path. + The possible values are ``winding`` or ``even-odd``. The default is ``winding``. + +``--fg-color=COLOR`` + + The color that is used to fill the interior of the path. + If not specified, black is used. + +``--bg-color=COLOR`` + + The color that is used to render the background behind the path. + If not specified, white is used. + +``--output-file=FILE`` + + The file to save the PNG image to. + If not specified, "path.png" is used. + +Info +^^^^ + +The ``info`` command shows various information about the given path, +such as its bounding box and and its length. + +REFERENCES +---------- + +- SVG Path Specification, https://www.w3.org/TR/SVG2/paths.html diff --git a/docs/reference/gtk/meson.build b/docs/reference/gtk/meson.build index e5a72b100f..d185b1c524 100644 --- a/docs/reference/gtk/meson.build +++ b/docs/reference/gtk/meson.build @@ -77,6 +77,7 @@ if get_option('man-pages') and rst2man.found() [ 'gtk4-query-settings', '1', ], [ 'gtk4-rendernode-tool', '1' ], [ 'gtk4-update-icon-cache', '1', ], + [ 'gtk4-path-tool', '1', ], ] if get_option('demos') diff --git a/tools/gtk-path-tool-decompose.c b/tools/gtk-path-tool-decompose.c new file mode 100644 index 0000000000..54da81e217 --- /dev/null +++ b/tools/gtk-path-tool-decompose.c @@ -0,0 +1,131 @@ +/* Copyright 2023 Red Hat, Inc. + * + * GTK+ 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. + * + * GLib 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 GTK+; see the file COPYING. If not, + * see . + * + * Author: Matthias Clasen + */ + +#include "config.h" + +#include +#include "gtk-path-tool.h" + +#include + +static gboolean +foreach_cb (GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + gpointer user_data) +{ + GskPathBuilder *builder = user_data; + + switch (op) + { + case GSK_PATH_MOVE: + gsk_path_builder_move_to (builder, pts[0].x, pts[0].y); + break; + + case GSK_PATH_CLOSE: + gsk_path_builder_close (builder); + break; + + case GSK_PATH_LINE: + gsk_path_builder_line_to (builder, pts[1].x, pts[1].y); + break; + + case GSK_PATH_QUAD: + gsk_path_builder_quad_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y); + break; + + case GSK_PATH_CUBIC: + gsk_path_builder_cubic_to (builder, pts[1].x, pts[1].y, + pts[2].x, pts[2].y, + pts[3].x, pts[3].y); + break; + + default: + g_assert_not_reached (); + } + + return TRUE; +} + +void +do_decompose (int *argc, const char ***argv) +{ + GError *error = NULL; + gboolean allow_quad = FALSE; + gboolean allow_curve = FALSE; + char **args = NULL; + GOptionContext *context; + GOptionEntry entries[] = { + { "allow-quad", 0, 0, G_OPTION_ARG_NONE, &allow_quad, N_("Allow quadratic Bézier curves"), NULL }, + { "allow-cubic", 0, 0, G_OPTION_ARG_NONE, &allow_curve, N_("Allow cubic Bézier curves"), NULL }, + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &args, NULL, N_("PATH") }, + { NULL, }, + }; + GskPathForeachFlags flags; + GskPath *path, *result; + GskPathBuilder *builder; + + g_set_prgname ("gtk4-path-tool decompose"); + + context = g_option_context_new (NULL); + g_option_context_set_translation_domain (context, GETTEXT_PACKAGE); + g_option_context_add_main_entries (context, entries, NULL); + g_option_context_set_summary (context, _("Decompose a path.")); + + if (!g_option_context_parse (context, argc, (char ***)argv, &error)) + { + g_printerr ("%s\n", error->message); + g_error_free (error); + exit (1); + } + + g_option_context_free (context); + + if (args == NULL) + { + g_printerr ("%s\n", _("No paths given.")); + exit (1); + } + + path = get_path (args[0]); + + flags = 0; + if (allow_quad) + flags |= GSK_PATH_FOREACH_ALLOW_QUAD; + if (allow_curve) + flags |= GSK_PATH_FOREACH_ALLOW_CUBIC; + + builder = gsk_path_builder_new (); + + gsk_path_foreach (path, flags, foreach_cb, builder); + + result = gsk_path_builder_free_to_path (builder); + + if (result) + { + char *str = gsk_path_to_string (result); + g_print ("%s\n", str); + g_free (str); + } + else + { + g_printerr ("%s\n", _("That didn't work out.")); + exit (1); + } +} diff --git a/tools/gtk-path-tool-info.c b/tools/gtk-path-tool-info.c new file mode 100644 index 0000000000..2bca1f3123 --- /dev/null +++ b/tools/gtk-path-tool-info.c @@ -0,0 +1,76 @@ +/* Copyright 2023 Red Hat, Inc. + * + * GTK+ 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. + * + * GLib 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 GTK+; see the file COPYING. If not, + * see . + * + * Author: Matthias Clasen + */ + +#include "config.h" + +#include +#include "gtk-path-tool.h" + +#include + +void +do_info (int *argc, const char ***argv) +{ + GError *error = NULL; + char **args = NULL; + GOptionContext *context; + GOptionEntry entries[] = { + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &args, NULL, N_("PATH") }, + { NULL, }, + }; + GskPath *path; + graphene_rect_t bounds; + + g_set_prgname ("gtk4-path-tool info"); + + context = g_option_context_new (NULL); + g_option_context_set_translation_domain (context, GETTEXT_PACKAGE); + g_option_context_add_main_entries (context, entries, NULL); + g_option_context_set_summary (context, _("Print information about a path.")); + + if (!g_option_context_parse (context, argc, (char ***)argv, &error)) + { + g_printerr ("%s\n", error->message); + g_error_free (error); + exit (1); + } + + g_option_context_free (context); + + if (args == NULL) + { + g_printerr ("%s\n", _("No paths given.")); + exit (1); + } + + path = get_path (args[0]); + + if (gsk_path_is_empty (path)) + g_print ("%s\n", _("Path is empty.")); + else + { + if (gsk_path_is_closed (path)) + g_print ("%s\n", _("Path is closed")); + + if (gsk_path_get_bounds (path, &bounds)) + g_print ("%s: %g %g %g %g\n", _("Bounds"), + bounds.origin.x, bounds.origin.y, + bounds.size.width, bounds.size.height); + } +} diff --git a/tools/gtk-path-tool-render.c b/tools/gtk-path-tool-render.c new file mode 100644 index 0000000000..a6e194f7dd --- /dev/null +++ b/tools/gtk-path-tool-render.c @@ -0,0 +1,208 @@ +/* Copyright 2023 Red Hat, Inc. + * + * GTK+ 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. + * + * GLib 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 GTK+; see the file COPYING. If not, + * see . + * + * Author: Matthias Clasen + */ + +#include "config.h" + +#include +#include +#include + +#include +#include +#include +#include +#include "gtk-path-tool.h" + + +void +do_render (int *argc, + const char ***argv) +{ + GError *error = NULL; + const char *fill = "winding"; + const char *fg_color = "black"; + const char *bg_color = "white"; + gboolean do_stroke = FALSE; + double line_width = 1; + const char *cap = "butt"; + const char *join = "miter"; + double miter_limit = 4; + const char *dashes = NULL; + double dash_offset = 0; + const char *output_file = NULL; + char **args = NULL; + GOptionContext *context; + const GOptionEntry entries[] = { + { "fill-rule", 0, 0, G_OPTION_ARG_STRING, &fill, N_("Fill rule (winding, even-odd)"), N_("VALUE") }, + { "fg-color", 0, 0, G_OPTION_ARG_STRING, &fg_color, N_("Foreground color"), N_("COLOR") }, + { "bg-color", 0, 0, G_OPTION_ARG_STRING, &bg_color, N_("Background color"), N_("COLOR") }, + { "stroke", 0, 0, G_OPTION_ARG_NONE, &do_stroke, N_("Stroke the path instead of filling it"), NULL }, + { "line-width", 0, 0, G_OPTION_ARG_DOUBLE, &line_width, N_("Line width (number)"), N_("VALUE") }, + { "line-cap", 0, 0, G_OPTION_ARG_STRING, &cap, N_("Line cap (butt, round, square)"), N_("VALUE") }, + { "line-join", 0, 0, G_OPTION_ARG_STRING, &join, N_("Line join (miter, miter-clip, round, bevel, arcs)"), N_("VALUE") }, + { "miter-limit", 0, 0, G_OPTION_ARG_DOUBLE, &miter_limit, N_("Miter limit (number)"), N_("VALUE") }, + { "dashes", 0, 0, G_OPTION_ARG_STRING, &dashes, N_("Dash pattern (comma-separated numbers)"), N_("VALUE") }, + { "dash-offset", 0, 0, G_OPTION_ARG_DOUBLE, &dash_offset, N_("Dash offset (number)"), N_("VALUE") }, + { "output", 0, 0, G_OPTION_ARG_FILENAME, &output_file, N_("The output file"), N_("FILE") }, + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &args, NULL, N_("PATH") }, + { NULL, } + }; + GskPath *path; + GskFillRule fill_rule; + GdkRGBA fg, bg; + graphene_rect_t bounds; + GskRenderNode *fg_node, *nodes[2], *node; + GdkSurface *surface; + GskRenderer *renderer; + GdkTexture *texture; + const char *filename; + GskLineCap line_cap; + GskLineJoin line_join; + GskStroke *stroke; + + if (gdk_display_get_default () == NULL) + { + g_printerr ("%s\n", _("Could not initialize windowing system")); + exit (1); + } + + g_set_prgname ("gtk4-path-tool render"); + context = g_option_context_new (NULL); + g_option_context_set_translation_domain (context, GETTEXT_PACKAGE); + g_option_context_add_main_entries (context, entries, NULL); + g_option_context_set_summary (context, _("Render the path to a png image.")); + + if (!g_option_context_parse (context, argc, (char ***)argv, &error)) + { + g_printerr ("%s\n", error->message); + g_error_free (error); + exit (1); + } + + g_option_context_free (context); + + if (args == NULL) + { + g_printerr ("%s\n", _("No path specified")); + exit (1); + } + + if (g_strv_length (args) > 1) + { + g_printerr ("%s\n", _("Can only render a single path")); + exit (1); + } + + path = get_path (args[0]); + + fill_rule = get_enum_value (GSK_TYPE_FILL_RULE, _("fill rule"), fill); + get_color (&fg, fg_color); + get_color (&bg, bg_color); + + line_cap = get_enum_value (GSK_TYPE_LINE_CAP, _("line cap"), cap); + line_join = get_enum_value (GSK_TYPE_LINE_JOIN, _("line join"), join); + + stroke = gsk_stroke_new (line_width); + gsk_stroke_set_line_cap (stroke, line_cap); + gsk_stroke_set_line_join (stroke, line_join); + + gsk_stroke_set_miter_limit (stroke, miter_limit); + + if (dashes != NULL) + { + GArray *d = g_array_new (FALSE, FALSE, sizeof (float)); + char **strings; + + strings = g_strsplit (dashes, ",", 0); + + for (unsigned int i = 0; strings[i]; i++) + { + char *end = NULL; + float f; + + f = (float) g_ascii_strtod (strings[i], &end); + + if (*end != '\0') + { + char *msg = g_strdup_printf (_("Failed to parse '%s' as number"), strings[i]); + g_printerr ("%s\n", msg); + exit (1); + } + + g_array_append_val (d, f); + } + + g_strfreev (strings); + + gsk_stroke_set_dash (stroke, (const float *)d->data, d->len); + + g_array_unref (d); + } + + gsk_stroke_set_dash_offset (stroke, dash_offset); + + if (do_stroke) + gsk_path_get_stroke_bounds (path, stroke, &bounds); + else + gsk_path_get_bounds (path, &bounds); + graphene_rect_inset (&bounds, -10, -10); + + nodes[0] = gsk_color_node_new (&bg, &bounds); + fg_node = gsk_color_node_new (&fg, &bounds); + if (do_stroke) + nodes[1] = gsk_stroke_node_new (fg_node, path, stroke); + else + nodes[1] = gsk_fill_node_new (fg_node, path, fill_rule); + + node = gsk_container_node_new (nodes, 2); + + gsk_render_node_unref (fg_node); + gsk_render_node_unref (nodes[0]); + gsk_render_node_unref (nodes[1]); + + surface = gdk_surface_new_toplevel (gdk_display_get_default ()); + renderer = gsk_renderer_new_for_surface (surface); + + texture = gsk_renderer_render_texture (renderer, node, &bounds); + + filename = output_file ? output_file : "path.png"; + if (!gdk_texture_save_to_png (texture, filename)) + { + char *msg = g_strdup_printf (_("Saving png to '%s' failed"), filename); + g_printerr ("%s\n", msg); + exit (1); + } + + if (output_file == NULL) + { + char *msg = g_strdup_printf (_("Output written to '%s'."), filename); + g_print ("%s\n", msg); + g_free (msg); + } + + g_object_unref (texture); + gsk_renderer_unrealize (renderer); + g_object_unref (renderer); + g_object_unref (surface); + gsk_render_node_unref (node); + + gsk_path_unref (path); + + g_strfreev (args); +} diff --git a/tools/gtk-path-tool-show.c b/tools/gtk-path-tool-show.c new file mode 100644 index 0000000000..d80fbae3a6 --- /dev/null +++ b/tools/gtk-path-tool-show.c @@ -0,0 +1,232 @@ +/* Copyright 2023 Red Hat, Inc. + * + * GTK+ 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. + * + * GLib 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 GTK+; see the file COPYING. If not, + * see . + * + * Author: Matthias Clasen + */ + +#include "config.h" + +#include +#include +#include + +#include +#include +#include +#include +#include "gtk-path-tool.h" + +#include "path-view.h" + +static void +show_path_fill (GskPath *path, + GskFillRule fill_rule, + const GdkRGBA *fg_color, + const GdkRGBA *bg_color) +{ + GtkWidget *window, *sw, *child; + + window = gtk_window_new (); + gtk_window_set_title (GTK_WINDOW (window), _("Path Preview")); + +// gtk_window_set_default_size (GTK_WINDOW (window), 700, 500); + + sw = gtk_scrolled_window_new (); + gtk_scrolled_window_set_propagate_natural_width (GTK_SCROLLED_WINDOW (sw), TRUE); + gtk_scrolled_window_set_propagate_natural_height (GTK_SCROLLED_WINDOW (sw), TRUE); + gtk_window_set_child (GTK_WINDOW (window), sw); + + child = path_view_new (path); + g_object_set (child, + "do-fill", TRUE, + "fill-rule", fill_rule, + "fg-color", fg_color, + "bg-color", bg_color, + NULL); + gtk_widget_set_hexpand (child, TRUE); + gtk_widget_set_vexpand (child, TRUE); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), child); + + gtk_window_present (GTK_WINDOW (window)); + + while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0) + g_main_context_iteration (NULL, TRUE); +} + +static void +show_path_stroke (GskPath *path, + GskStroke *stroke, + const GdkRGBA *fg_color, + const GdkRGBA *bg_color) +{ + GtkWidget *window, *sw, *child; + + window = gtk_window_new (); + gtk_window_set_title (GTK_WINDOW (window), _("Path Preview")); + +// gtk_window_set_default_size (GTK_WINDOW (window), 700, 500); + + sw = gtk_scrolled_window_new (); + gtk_scrolled_window_set_propagate_natural_width (GTK_SCROLLED_WINDOW (sw), TRUE); + gtk_scrolled_window_set_propagate_natural_height (GTK_SCROLLED_WINDOW (sw), TRUE); + gtk_window_set_child (GTK_WINDOW (window), sw); + + child = path_view_new (path); + g_object_set (child, + "do-fill", FALSE, + "stroke", stroke, + "fg-color", fg_color, + "bg-color", bg_color, + NULL); + gtk_widget_set_hexpand (child, TRUE); + gtk_widget_set_vexpand (child, TRUE); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), child); + + gtk_window_present (GTK_WINDOW (window)); + + while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0) + g_main_context_iteration (NULL, TRUE); +} + +void +do_show (int *argc, + const char ***argv) +{ + GError *error = NULL; + gboolean do_stroke = FALSE; + const char *fill = "winding"; + const char *fg_color = "black"; + const char *bg_color = "white"; + double line_width = 1; + const char *cap = "butt"; + const char *join = "miter"; + double miter_limit = 4; + const char *dashes = NULL; + double dash_offset = 0; + char **args = NULL; + GOptionContext *context; + const GOptionEntry entries[] = { + { "fill-rule", 0, 0, G_OPTION_ARG_STRING, &fill, N_("Fill rule (winding, even-odd)"), N_("VALUE") }, + { "fg-color", 0, 0, G_OPTION_ARG_STRING, &fg_color, N_("Foreground color"), N_("COLOR") }, + { "bg-color", 0, 0, G_OPTION_ARG_STRING, &bg_color, N_("Background color"), N_("COLOR") }, + { "stroke", 0, 0, G_OPTION_ARG_NONE, &do_stroke, N_("Stroke the path instead of filling it"), NULL }, + { "line-width", 0, 0, G_OPTION_ARG_DOUBLE, &line_width, N_("Line width (number)"), N_("VALUE") }, + { "line-cap", 0, 0, G_OPTION_ARG_STRING, &cap, N_("Line cap (butt, round, square)"), N_("VALUE") }, + { "line-join", 0, 0, G_OPTION_ARG_STRING, &join, N_("Line join (miter, miter-clip, round, bevel, arcs)"), N_("VALUE") }, + { "miter-limit", 0, 0, G_OPTION_ARG_DOUBLE, &miter_limit, N_("Miter limit (number)"), N_("VALUE") }, + { "dashes", 0, 0, G_OPTION_ARG_STRING, &dashes, N_("Dash pattern (comma-separated numbers)"), N_("VALUE") }, + { "dash-offset", 0, 0, G_OPTION_ARG_DOUBLE, &dash_offset, N_("Dash offset (number)"), N_("VALUE") }, + { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &args, NULL, N_("PATH") }, + { NULL, } + }; + GskPath *path; + GskFillRule fill_rule; + GdkRGBA fg; + GdkRGBA bg; + GskLineCap line_cap; + GskLineJoin line_join; + GskStroke *stroke; + + if (gdk_display_get_default () == NULL) + { + g_printerr ("%s\n", _("Could not initialize windowing system")); + exit (1); + } + + g_set_prgname ("gtk4-path-tool show"); + context = g_option_context_new (NULL); + g_option_context_set_translation_domain (context, GETTEXT_PACKAGE); + g_option_context_add_main_entries (context, entries, NULL); + g_option_context_set_summary (context, _("Display the path.")); + + if (!g_option_context_parse (context, argc, (char ***)argv, &error)) + { + g_printerr ("%s\n", error->message); + g_error_free (error); + exit (1); + } + + g_option_context_free (context); + + if (args == NULL) + { + g_printerr ("%s\n", _("No path specified")); + exit (1); + } + + if (g_strv_length (args) > 1) + { + g_printerr ("%s\n", _("Can only show a single path")); + exit (1); + } + + path = get_path (args[0]); + + fill_rule = get_enum_value (GSK_TYPE_FILL_RULE, _("fill rule"), fill); + get_color (&fg, fg_color); + get_color (&bg, bg_color); + + line_cap = get_enum_value (GSK_TYPE_LINE_CAP, _("line cap"), cap); + line_join = get_enum_value (GSK_TYPE_LINE_JOIN, _("line join"), join); + + stroke = gsk_stroke_new (line_width); + gsk_stroke_set_line_cap (stroke, line_cap); + gsk_stroke_set_line_join (stroke, line_join); + + gsk_stroke_set_miter_limit (stroke, miter_limit); + + if (dashes != NULL) + { + GArray *d = g_array_new (FALSE, FALSE, sizeof (float)); + char **strings; + + strings = g_strsplit (dashes, ",", 0); + + for (unsigned int i = 0; strings[i]; i++) + { + char *end = NULL; + float f; + + f = (float) g_ascii_strtod (strings[i], &end); + + if (*end != '\0') + { + char *msg = g_strdup_printf (_("Failed to parse '%s' as number"), strings[i]); + g_printerr ("%s\n", msg); + exit (1); + } + + g_array_append_val (d, f); + } + + g_strfreev (strings); + + gsk_stroke_set_dash (stroke, (const float *)d->data, d->len); + + g_array_unref (d); + } + + gsk_stroke_set_dash_offset (stroke, dash_offset); + + if (do_stroke) + show_path_stroke (path, stroke, &fg, &bg); + else + show_path_fill (path, fill_rule, &fg, &bg); + + gsk_path_unref (path); + + g_strfreev (args); +} diff --git a/tools/gtk-path-tool-utils.c b/tools/gtk-path-tool-utils.c new file mode 100644 index 0000000000..76888cadd6 --- /dev/null +++ b/tools/gtk-path-tool-utils.c @@ -0,0 +1,139 @@ +/* Copyright 2023 Red Hat, Inc. + * + * GTK+ 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. + * + * GLib 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 GTK+; see the file COPYING. If not, + * see . + * + * Author: Matthias Clasen + */ + +#include "config.h" + +#include +#include +#ifdef G_OS_UNIX +#include +#endif +#include "gtk-path-tool.h" + +#include + +GskPath * +get_path (const char *arg) +{ + char *buffer = NULL; + gsize len; + GError *error = NULL; + GskPath *path; + + if (arg[0] == '.' || arg[0] == '/') + { + if (!g_file_get_contents (arg, &buffer, &len, &error)) + { + g_printerr ("%s\n", error->message); + exit (1); + } + } +#ifdef G_OS_UNIX + else if (strcmp (arg, "-") == 0) + { + GInputStream *in; + GOutputStream *out; + + in = g_unix_input_stream_new (0, FALSE); + out = g_memory_output_stream_new (NULL, 0, g_realloc, g_free); + + if (g_output_stream_splice (out, in, 0, NULL, &error) < 0) + { + g_printerr (_("Failed to read from standard input: %s\n"), error->message); + exit (1); + } + + if (!g_output_stream_close (out, NULL, &error)) + { + g_printerr (_("Error reading from standard input: %s\n"), error->message); + exit (1); + } + + buffer = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (out)); + + g_object_unref (out); + g_object_unref (in); + } +#endif + else + buffer = g_strdup (arg); + + g_strstrip (buffer); + + path = gsk_path_parse (buffer); + + if (path == NULL) + { + g_printerr (_("Failed to parse '%s' as path.\n"), arg); + exit (1); + } + + g_free (buffer); + + return path; +} + +int +get_enum_value (GType type, + const char *type_nick, + const char *str) +{ + GEnumClass *class = g_type_class_ref (type); + GEnumValue *value; + int val; + + value = g_enum_get_value_by_nick (class, str); + if (value) + val = value->value; + else + { + GString *s; + + s = g_string_new (""); + g_string_append_printf (s, _("Failed to parse '%s' as %s."), str, type_nick); + g_string_append (s, "\n"); + g_string_append (s, _("Possible values: ")); + + for (unsigned int i = 0; i < class->n_values; i++) + { + if (i > 0) + g_string_append (s, ", "); + g_string_append (s, class->values[i].value_nick); + } + g_printerr ("%s\n", s->str); + g_string_free (s, TRUE); + exit (1); + } + + g_type_class_unref (class); + + return val; +} + +void +get_color (GdkRGBA *rgba, + const char *str) +{ + if (!gdk_rgba_parse (rgba, str)) + { + char *msg = g_strdup_printf (_("Could not parse '%s' as color"), str); + g_printerr ("%s\n", msg); + exit (1); + } +} diff --git a/tools/gtk-path-tool.c b/tools/gtk-path-tool.c new file mode 100644 index 0000000000..5bb41fe34a --- /dev/null +++ b/tools/gtk-path-tool.c @@ -0,0 +1,139 @@ +/* Copyright 2023 Red Hat, Inc. + * + * GTK+ 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. + * + * GLib 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 GTK+; see the file COPYING. If not, + * see . + * + * Author: Matthias Clasen + */ + +#include "config.h" + +#include +#include +#include + +#include +#include +#include +#include +#include "gtk-path-tool.h" + +static void G_GNUC_NORETURN +usage (void) +{ + g_print (_("Usage:\n" + " gtk4-path-tool [COMMAND] [OPTION…] PATH\n" + "\n" + "Perform various tasks on paths.\n" + "\n" + "Commands:\n" + " decompose Decompose the path\n" + " show Display the path in a window\n" + " render Render the path as an image\n" + " info Print information about the path\n" + "\n")); + exit (1); +} + +/* A simplified version of g_log_writer_default_would_drop(), to avoid + * bumping up the required version of GLib to 2.68 + */ +static gboolean +would_drop (GLogLevelFlags level, + const char *domain) +{ +#if GLIB_CHECK_VERSION (2, 68, 0) + return g_log_writer_default_would_drop (level, domain); +#else + return (level & (G_LOG_LEVEL_ERROR | + G_LOG_LEVEL_CRITICAL | + G_LOG_LEVEL_WARNING)) == 0; +#endif +} + +static GLogWriterOutput +log_writer_func (GLogLevelFlags level, + const GLogField *fields, + gsize n_fields, + gpointer user_data) +{ + gsize i; + const char *domain = NULL; + const char *message = NULL; + + for (i = 0; i < n_fields; i++) + { + if (g_strcmp0 (fields[i].key, "GLIB_DOMAIN") == 0) + domain = fields[i].value; + else if (g_strcmp0 (fields[i].key, "MESSAGE") == 0) + message = fields[i].value; + } + + if (message != NULL && !would_drop (level, domain)) + { + const char *prefix; + switch (level & G_LOG_LEVEL_MASK) + { + case G_LOG_LEVEL_ERROR: + prefix = "ERROR"; + break; + case G_LOG_LEVEL_CRITICAL: + prefix = "CRITICAL"; + break; + case G_LOG_LEVEL_WARNING: + prefix = "WARNING"; + break; + default: + prefix = "INFO"; + break; + } + g_printerr ("%s-%s: %s\n", domain, prefix, message); + } + + return G_LOG_WRITER_HANDLED; +} + +int +main (int argc, const char *argv[]) +{ + g_set_prgname ("gtk4-path-tool"); + + g_log_set_writer_func (log_writer_func, NULL, NULL); + + gtk_init_check (); + + gtk_test_register_all_types (); + + if (argc < 2) + usage (); + + if (strcmp (argv[1], "--help") == 0) + usage (); + + argv++; + argc--; + + if (strcmp (argv[0], "decompose") == 0) + do_decompose (&argc, &argv); + else if (strcmp (argv[0], "info") == 0) + do_info (&argc, &argv); + else if (strcmp (argv[0], "render") == 0) + do_render (&argc, &argv); + else if (strcmp (argv[0], "show") == 0) + do_show (&argc, &argv); + else + usage (); + + return 0; +} diff --git a/tools/gtk-path-tool.h b/tools/gtk-path-tool.h new file mode 100644 index 0000000000..4aadefdc84 --- /dev/null +++ b/tools/gtk-path-tool.h @@ -0,0 +1,13 @@ +#pragma once + +void do_info (int *argc, const char ***argv); +void do_decompose (int *argc, const char ***argv); +void do_render (int *argc, const char ***argv); +void do_show (int *argc, const char ***argv); + +GskPath *get_path (const char *arg); +int get_enum_value (GType type, + const char *type_nick, + const char *str); +void get_color (GdkRGBA *rgba, + const char *str); diff --git a/tools/meson.build b/tools/meson.build index 5023a38737..7ba5013e1a 100644 --- a/tools/meson.build +++ b/tools/meson.build @@ -23,6 +23,13 @@ if win32_enabled endif gtk_tools = [ + ['gtk4-path-tool', ['gtk-path-tool.c', + 'gtk-path-tool-decompose.c', + 'gtk-path-tool-info.c', + 'gtk-path-tool-render.c', + 'gtk-path-tool-show.c', + 'gtk-path-tool-utils.c', + 'path-view.c'], [libgtk_dep]], ['gtk4-query-settings', ['gtk-query-settings.c'], [libgtk_dep]], ['gtk4-builder-tool', ['gtk-builder-tool.c', 'gtk-builder-tool-simplify.c', diff --git a/tools/path-view.c b/tools/path-view.c new file mode 100644 index 0000000000..8e6f35477e --- /dev/null +++ b/tools/path-view.c @@ -0,0 +1,271 @@ +/* Copyright 2023 Red Hat, Inc. + * + * GTK+ 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. + * + * GLib 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 GTK+; see the file COPYING. If not, + * see . + * + * Author: Matthias Clasen + */ + +#include "config.h" + +#include "path-view.h" + +struct _PathView +{ + GtkWidget parent_instance; + + GskPath *path; + GskStroke *stroke; + graphene_rect_t bounds; + GskFillRule fill_rule; + GdkRGBA fg; + GdkRGBA bg; + int padding; + gboolean do_fill; +}; + +enum { + PROP_PATH = 1, + PROP_DO_FILL, + PROP_STROKE, + PROP_FILL_RULE, + PROP_FG_COLOR, + PROP_BG_COLOR, + N_PROPERTIES +}; + +static GParamSpec *properties[N_PROPERTIES] = { NULL, }; + +struct _PathViewClass +{ + GtkWidgetClass parent_class; +}; + +G_DEFINE_TYPE (PathView, path_view, GTK_TYPE_WIDGET) + +static void +path_view_init (PathView *self) +{ + self->do_fill = TRUE; + self->stroke = gsk_stroke_new (1); + self->fill_rule = GSK_FILL_RULE_WINDING; + self->fg = (GdkRGBA) { 0, 0, 0, 1}; + self->bg = (GdkRGBA) { 1, 1, 1, 1}; + self->padding = 10; +} + +static void +path_view_dispose (GObject *object) +{ + PathView *self = PATH_VIEW (object); + + g_clear_pointer (&self->path, gsk_path_unref); + g_clear_pointer (&self->stroke, gsk_stroke_free); + + G_OBJECT_CLASS (path_view_parent_class)->dispose (object); +} + +static void +path_view_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + PathView *self = PATH_VIEW (object); + + switch (prop_id) + { + case PROP_PATH: + g_value_set_boxed (value, self->path); + break; + + case PROP_DO_FILL: + g_value_set_boolean (value, self->do_fill); + break; + + case PROP_STROKE: + g_value_set_boxed (value, self->stroke); + break; + + case PROP_FILL_RULE: + g_value_set_enum (value, self->fill_rule); + break; + + case PROP_FG_COLOR: + g_value_set_boxed (value, &self->fg); + break; + + case PROP_BG_COLOR: + g_value_set_boxed (value, &self->bg); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +update_bounds (PathView *self) +{ + if (self->do_fill) + gsk_path_get_bounds (self->path, &self->bounds); + else + gsk_path_get_stroke_bounds (self->path, self->stroke, &self->bounds); +} + +static void +path_view_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + PathView *self = PATH_VIEW (object); + + switch (prop_id) + { + + case PROP_PATH: + g_clear_pointer (&self->path, gsk_path_unref); + self->path = g_value_dup_boxed (value); + update_bounds (self); + gtk_widget_queue_resize (GTK_WIDGET (self)); + break; + + case PROP_DO_FILL: + self->do_fill = g_value_get_boolean (value); + update_bounds (self); + gtk_widget_queue_resize (GTK_WIDGET (self)); + break; + + case PROP_STROKE: + gsk_stroke_free (self->stroke); + self->stroke = g_value_get_boxed (value); + update_bounds (self); + gtk_widget_queue_resize (GTK_WIDGET (self)); + break; + + case PROP_FILL_RULE: + self->fill_rule = g_value_get_enum (value); + gtk_widget_queue_draw (GTK_WIDGET (self)); + break; + + case PROP_FG_COLOR: + self->fg = *(GdkRGBA *) g_value_get_boxed (value); + gtk_widget_queue_draw (GTK_WIDGET (self)); + break; + + case PROP_BG_COLOR: + self->bg = *(GdkRGBA *) g_value_get_boxed (value); + gtk_widget_queue_draw (GTK_WIDGET (self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +path_view_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + PathView *self = PATH_VIEW (widget); + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + *minimum = *natural = (int) ceilf (self->bounds.size.width) + 2 * self->padding; + else + *minimum = *natural = (int) ceilf (self->bounds.size.height) + 2 * self->padding; +} + +static void +path_view_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + PathView *self = PATH_VIEW (widget); + graphene_rect_t bounds = self->bounds; + + graphene_rect_inset (&bounds, - self->padding, - self->padding); + + gtk_snapshot_save (snapshot); + gtk_snapshot_append_color (snapshot, &self->bg, &self->bounds); + gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (self->padding, self->padding)); + if (self->do_fill) + gtk_snapshot_push_fill (snapshot, self->path, self->fill_rule); + else + gtk_snapshot_push_stroke (snapshot, self->path, self->stroke); + gtk_snapshot_append_color (snapshot, &self->fg, &self->bounds); + gtk_snapshot_pop (snapshot); + gtk_snapshot_restore (snapshot); +} + +static void +path_view_class_init (PathViewClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + + object_class->dispose = path_view_dispose; + object_class->get_property = path_view_get_property; + object_class->set_property = path_view_set_property; + + widget_class->measure = path_view_measure; + widget_class->snapshot = path_view_snapshot; + + properties[PROP_PATH] + = g_param_spec_boxed ("path", NULL, NULL, + GSK_TYPE_PATH, + G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + properties[PROP_DO_FILL] + = g_param_spec_boolean ("do-fill", NULL, NULL, + TRUE, + G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + properties[PROP_STROKE] + = g_param_spec_boxed ("stroke", NULL, NULL, + GSK_TYPE_STROKE, + G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + properties[PROP_FILL_RULE] + = g_param_spec_enum ("fill-rule", NULL, NULL, + GSK_TYPE_FILL_RULE, + GSK_FILL_RULE_WINDING, + G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + properties[PROP_FG_COLOR] + = g_param_spec_boxed ("fg-color", NULL, NULL, + GDK_TYPE_RGBA, + G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + properties[PROP_BG_COLOR] + = g_param_spec_boxed ("bg-color", NULL, NULL, + GDK_TYPE_RGBA, + G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, N_PROPERTIES, properties); +} + +GtkWidget * +path_view_new (GskPath *path) +{ + return g_object_new (PATH_TYPE_VIEW, + "path", path, + NULL); +} diff --git a/tools/path-view.h b/tools/path-view.h new file mode 100644 index 0000000000..2998d727b8 --- /dev/null +++ b/tools/path-view.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#define PATH_TYPE_VIEW (path_view_get_type ()) + +G_DECLARE_FINAL_TYPE (PathView, path_view, PATH, VIEW, GtkWidget) + +GtkWidget * path_view_new (GskPath *path);