175 lines
7.2 KiB
Markdown
175 lines
7.2 KiB
Markdown
Title: Paths
|
|
Slug: paths
|
|
|
|
GSK provides a path API that can be used to render more complex
|
|
shapes than lines or rounded rectangles. It is comparable to cairos
|
|
[path API](https://www.cairographics.org/manual/cairo-Paths.html),
|
|
with some notable differences.
|
|
|
|
In general, a path consists of one or more connected **_contours_**,
|
|
each of which may have multiple **_operations_**, and may or may not be closed.
|
|
Operations can be straight lines or curves of various kinds. At the points
|
|
where the operations connect, the path can have sharp turns.
|
|
|
|
<figure>
|
|
<picture>
|
|
<source srcset="path-dark.png" media="(prefers-color-scheme: dark)">
|
|
<img alt="A path with multiple contours" src="path-light.png">
|
|
</picture>
|
|
<figcaption>A path with one closed, and one open contour</figcaption>
|
|
</figure>
|
|
|
|
The central object of the GSK path API is the immutable [struct@Gsk.Path]
|
|
struct, which contains path data in compact form suitable for rendering.
|
|
|
|
## Creating Paths
|
|
|
|
Since `GskPath` is immutable, the auxiliary [struct@Gsk.PathBuilder] struct
|
|
can be used to construct a path piece by piece. The pieces are specified with
|
|
points, some of which will be on the path, while others are just **_control points_**
|
|
that are used to influence the shape of the resulting path.
|
|
|
|
<figure>
|
|
<picture>
|
|
<source srcset="cubic-dark.png" media="(prefers-color-scheme: dark)">
|
|
<img alt="An cubic Bézier" src="cubic-light.png">
|
|
</picture>
|
|
<figcaption>A cubic Bézier operation, with 2 control points</figcaption>
|
|
</figure>
|
|
|
|
The `GskPathBuilder` API has three distinct groups of functions:
|
|
|
|
- Functions for building contours from individual operations, like [method@Gsk.PathBuilder.move_to],
|
|
[method@Gsk.PathBuilder.line_to], [method@Gsk.PathBuilder.cubic_to], [method@Gsk.PathBuilder.close]. `GskPathBuilder` maintains a **_current point_**, so these methods all
|
|
take one less points than necessary for the operation (e.g. `gsk_path_builder_line_to`
|
|
only takes a single point and draws a line from the current point to the new point).
|
|
|
|
- Functions for adding complete contours, such as [method@Gsk.PathBuilder.add_rect],
|
|
[method@Gsk.PathBuilder.add_rounded_rect], [method@Gsk.PathBuilder.add_circle].
|
|
|
|
- Adding parts of a preexisting path. Functions in this group include
|
|
[method@Gsk.PathBuilder.add_path] and [method@Gsk.PathBuilder.add_segment].
|
|
|
|
When you are done with building a path, you can convert the accumulated path
|
|
data into a `GskPath` struct with [method@Gsk.PathBuilder.free_to_path].
|
|
|
|
A sometimes convenient alternative is to create a path from a serialized form,
|
|
with [func@Gsk.Path.parse]. This function interprets strings in SVG path syntax,
|
|
such as:
|
|
|
|
M 100 100 C 100 200 200 200 200 100 Z
|
|
|
|
## Rendering with Paths
|
|
|
|
There are two main ways to render with paths. The first is to **_fill_** the
|
|
interior of a path with a color or more complex content, such as a gradient.
|
|
GSK supports different ways of determining what part of the plane are interior
|
|
to the path, which can be selected with a [enum@Gsk.FillRule] value.
|
|
|
|
<figure>
|
|
<picture>
|
|
<img alt="A filled path" src="fill-winding.png">
|
|
</picture>
|
|
<figcaption>A path filled with GSK_FILL_RULE_WINDING</figcaption>
|
|
</figure>
|
|
|
|
<figure>
|
|
<picture>
|
|
<img alt="A filled path" src="fill-even-odd.png">
|
|
</picture>
|
|
<figcaption>The same path, filled with GSK_FILL_RULE_EVEN_ODD</figcaption>
|
|
</figure>
|
|
|
|
To fill a path, use [gtk_snapshot_append_fill()](../gtk4/method.Snapshot.append_fill.html)
|
|
or the more general [gtk_snapshot_push_fill()](../gtk4/method.Snapshot.push_fill.html).
|
|
|
|
Alternatively, a path can be **_stroked_**, which means to emulate drawing
|
|
with an idealized pen along the path. The result of stroking a path is another
|
|
path (the **_stroke path_**), which is then filled.
|
|
|
|
The stroke operation can be influenced with the [struct@Gsk.Stroke] struct
|
|
that collects various stroke parameters, such as the line width, the style
|
|
of line joins and line caps, and a dash pattern.
|
|
|
|
<figure>
|
|
<picture>
|
|
<img alt="A stroked path" src="stroke-miter.png">
|
|
</picture>
|
|
<figcaption>The same path, stroked with GSK_LINE_JOIN_MITER</figcaption>
|
|
</figure>
|
|
|
|
<figure>
|
|
<picture>
|
|
<img alt="A stroked path" src="stroke-round.png">
|
|
</picture>
|
|
<figcaption>The same path, stroked with GSK_LINE_JOIN_ROUND</figcaption>
|
|
</figure>
|
|
|
|
To stroke a path, use
|
|
[gtk_snapshot_append_stroke()](../gtk4/method.Snapshot.append_stroke.html)
|
|
or [gtk_snapshot_push_stroke()](../gtk4/method.Snapshot.push_stroke.html).
|
|
|
|
## Hit testing
|
|
|
|
When paths are rendered as part of an interactive interface, it is sometimes
|
|
necessary to determine whether the mouse points is over the path. GSK provides
|
|
[method@Gsk.Path.in_fill] for this purpose.
|
|
|
|
## Path length
|
|
|
|
An important property of paths is their **_length_**. Computing it efficiently
|
|
requires caching, therefore GSK provides a separate [struct@Gsk.PathMeasure] object
|
|
to deal with path lengths. After constructing a `GskPathMeasure` object for a path,
|
|
it can be used to determine the length of the path with [method@Gsk.PathMeasure.get_length]
|
|
and locate points at a given distance into the path with [method@Gsk.PathMeasure.get_point].
|
|
|
|
## Other Path APIs
|
|
|
|
Paths have uses beyond rendering, for example as trajectories in animations.
|
|
In such uses, it is often important to access properties of paths, such as
|
|
their tangents at certain points. GSK provides an abstract representation
|
|
for points on a path in the form of the [struct@Gsk.PathPoint] struct.
|
|
You can query properties of a path at certain point once you have a
|
|
`GskPathPoint` representing that point.
|
|
|
|
`GskPathPoint` structs can be compared for equality with [method@Gsk.PathPoint.equal]
|
|
and ordered wrt. to which one comes first, using [method@Gsk.PathPoint.compare].
|
|
|
|
To obtain a `GskPathPoint`, use [method@Gsk.Path.get_closest_point],
|
|
[method@Gsk.Path.get_start_point], [method@Gsk.Path.get_end_point] or
|
|
[method@Gsk.PathMeasure.get_point].
|
|
|
|
To query properties of the path at a point, use [method@Gsk.PathPoint.get_position],
|
|
[method@Gsk.PathPoint.get_tangent], [method@Gsk.PathPoint.get_rotation],
|
|
[method@Gsk.PathPoint.get_curvature] and [method@Gsk.PathPoint.get_distance].
|
|
|
|
Some of the properties can have different values for the path going into
|
|
the point and the path leaving the point, typically at points where the
|
|
path takes sharp turns. Examples for this are tangents (which can have up
|
|
to 4 different values) and curvatures (which can have two different values).
|
|
|
|
<figure>
|
|
<picture>
|
|
<source srcset="directions-dark.png" media="(prefers-color-scheme: dark)">
|
|
<img alt="Path Tangents" src="directions-light.png">
|
|
</picture>
|
|
<figcaption>Path Tangents</figcaption>
|
|
</figure>
|
|
|
|
## Going beyond GskPath
|
|
|
|
Lots of powerful functionality can be implemented for paths:
|
|
|
|
- Finding intersections
|
|
- Offsetting curves
|
|
- Turning stroke outlines into paths
|
|
- Molding curves (making them pass through a given point)
|
|
|
|
GSK does not provide API for all of these, but it does offer a way to get at
|
|
the underlying Bézier curves, so you can implement such functionality yourself.
|
|
You can use [method@Gsk.Path.foreach] to iterate over the operations of the
|
|
path, and get the points needed to reconstruct or modify the path piece by piece.
|
|
|
|
See e.g. the [Primer on Bézier curves](https://pomax.github.io/bezierinfo/)
|
|
for inspiration of useful things to explore.
|